Как извлечь строку по шаблону с помощью grep, regex или perl

91

У меня есть файл, который выглядит примерно так:

    <table name="content_analyzer" primary-key="id">
      <type="global" />
    </table>
    <table name="content_analyzer2" primary-key="id">
      <type="global" />
    </table>
    <table name="content_analyzer_items" primary-key="id">
      <type="global" />
    </table>

Мне нужно извлечь что-нибудь в следующих кавычках name=, то есть content_analyzer, content_analyzer2и content_analyzer_items.

Я делаю это в системе Linux, поэтому решение с использованием sed, perl, grep или bash подойдет.

спорщик
источник
5
не нужно стесняться, добро пожаловать сюда!
Бенуа
8
Я считаю, что было бы неправильно не ссылаться на stackoverflow.com/questions/1732348/…
Кристоффер Хаммарстрём,
Спасибо всем за полезные комментарии. Прошу прощения за неправильное форматирование XML. Я удалил некоторые теги для упрощения.
wrangler

Ответы:

172

Поскольку вам нужно сопоставить контент, не включая его в результат (должен совпадать, name=" но не является частью желаемого результата), требуется некоторая форма сопоставления нулевой ширины или группового захвата. Это легко сделать с помощью следующих инструментов:

Perl

С Perl вы можете использовать n опцию для цикла построчно и распечатать содержимое группы захвата, если оно совпадает:

perl -ne 'print "$1\n" if /name="(.*?)"/' filename

GNU grep

Если у вас есть улучшенная версия grep, такая как GNU grep, у вас может быть -Pдоступная опция. Эта опция включит регулярное выражение, подобное Perl, что позволит вам использовать \Kсокращенный просмотр назад. Он сбросит положение совпадения, поэтому все, что до него, будет нулевой шириной.

grep -Po 'name="\K.*?(?=")' filename

Эта o опция заставляет grep печатать только совпавший текст, а не всю строку.

Vim - текстовый редактор

Другой способ - напрямую использовать текстовый редактор. В Vim одним из различных способов добиться этого было бы удаление строк без name=и последующее извлечение содержимого из полученных строк:

:v/.*name="\v([^"]+).*/d|%s//\1

Стандартный grep

Если по какой-то причине у вас нет доступа к этим инструментам, нечто подобное можно сделать с помощью стандартного grep. Однако без осмотра позже потребуется некоторая очистка:

grep -o 'name="[^"]*"' filename

Примечание о сохранении результатов

Результаты всех вышеперечисленных команд будут отправлены по адресу stdout. Важно помнить, что вы всегда можете сохранить их, подключив его к файлу, добавив:

> result

до конца команды.

Сидилл
источник
12
Обзоры (в GNU grep):grep -Po '.*name="\K.*?(?=".*)'
Деннис Уильямсон,
@ Деннис Уильямсон, отлично. Я соответственно обновил ответ, но оставил оба в .*стороне, надеюсь, вы не рассердитесь на меня. Я хотел бы спросить, видите ли вы какую-либо пользу от не жадного совпадения перед «чем угодно, кроме "»? Не воспринимайте это как борьбу, мне просто любопытно, и я не эксперт по регулярным выражениям. Кроме того, \Kчаевые, действительно хорошие. Спасибо, Деннис.
sidyll
2
Зачем мне злиться? Без него .*можно обойтись grep -Po '(?<=name=").*?(?=")'. \KМожет быть использован для стенографии, но это действительно необходимо только , если матч с его левым переменной длиной. В подобных случаях причина использования поиска довольно очевидна. Неладные операции выглядят немного аккуратнее ( [^"]*вместо .*?того, чтобы повторять характер привязки. Я не знаю о скорости. Думаю, это во многом зависит от контекста. Надеюсь, это поможет.
Деннис Уильямсон,
@ Деннис Уильямсон: конечно, сэр, здесь много полезной информации. Я думаю, что причина, по которой я сохранил \K(после исследования) и удалил его, .*была та же: сделать его красивее (проще). И я никогда не думал использовать .*?вместо "традиционный способ", которому я где-то научился. Но «нежадный» здесь действительно имеет смысл. Спасибо, Деннис, наилучшие пожелания.
sidyll
+1 за описание команды. Был бы признателен, если бы вы могли обновить свой ответ, чтобы объяснить часть «[...]» регулярного выражения.
lreeder 04
5

Регулярное выражение будет таким:

.+name="([^"]+)"

Тогда группировка будет в \ 1

Мэтт Шейвер
источник
5

Если вы используете Perl, загрузите модуль для анализа XML: XML :: Simple , XML :: Twig или XML :: LibXML . Не изобретайте колесо заново.

Shawnhcorey
источник
3
Обратите внимание, что приведенный пример OP не является правильно сформированным ( <type="global"например), поэтому большинство анализаторов XML просто жалуются и умирают.
bvr
5

Для этой цели следует использовать парсер HTML, а не регулярные выражения. Программа на Perl, которая использует HTML::TreeBuilder:

Программа

#!/usr/bin/env perl

use strict;
use warnings;

use HTML::TreeBuilder;

my $tree = HTML::TreeBuilder->new_from_file( \*DATA );
my @elements = $tree->look_down(
    sub { defined $_[0]->attr('name') }
);

for (@elements) {
    print $_->attr('name'), "\n";
}

__DATA__
<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>

Вывод

content_analyzer
content_analyzer2
content_analyzer_items
Алан Хаггай Алави
источник
2

это могло сделать это:

perl -ne 'if(m/name="(.*?)"/){ print $1 . "\n"; }'
Бенуа
источник
2

Вот решение с использованием HTML tidy и xmlstarlet:

htmlstr='
<table name="content_analyzer" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
<type="global" />
</table>
'

echo "$htmlstr" | tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
sed '/type="global"/d' |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n
митма
источник
1

Ой, конечно, команда sed должна предшествовать команде tidy:

echo "$htmlstr" | 
sed '/type="global"/d' |
tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n
митма
источник
0

Если структура вашего xml (или текста в целом) исправлена, самый простой способ - использовать cut. Для вашего конкретного случая:

echo '<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>' | grep name= | cut -f2 -d '"'
Карлос Линдадо
источник