Как получить несколько строк из файла с помощью регулярных выражений?

10

Как получить несколько строк из файла с помощью регулярных выражений?

Я часто хотел бы получить несколько строк / изменить несколько строк с помощью регулярных выражений. Пример дела:

Я пытаюсь прочитать часть файла XML / SGML (они не обязательно хорошо сформированы или имеют предсказуемый синтаксис, поэтому регулярное выражение будет более безопасным, чем правильный синтаксический анализатор. Кроме того, я хотел бы иметь возможность сделать это также полностью неструктурированные файлы, в которых известны только некоторые ключевые слова.) в сценарии оболочки (работающем в Solaris и Linux).

Пример XML:

<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>

Из этого я хотел бы прочитать, <tag1>если он содержит fooгде-то в нем.

Подобное регулярному выражению (<tag1>.*?foo.*?</tag1>)должно дать правильную часть, но инструменты, подобные grepи sedтолько для меня, работают с единственными строками. Как я могу получить

<tag1>
 <tag2>foo</tag2>
</tag1>

в этом примере?

логово
источник
@evilsoup Это правда, но мой вопрос касается не файлов XML / SGML, а каких-либо текстовых файлов.
Ден

Ответы:

7

Если у вас есть GNU Grep установлена , вы можете сделать многострочный поиск пропускания в -P(Perl-регулярные выражения) и флаге активации PCRE_DOTALLс(?s)

grep -oP '(?s)<tag1>(?:(?!tag1).)*?foo(?:(?!tag1).)*?</tag1>' file.txt
<tag1>
<tag2>foo</tag2>
</tag1>

Если вышеописанное не работает на вашей платформе, попробуйте передать -zфлаг дополнительно, это заставит grep рассматривать NUL как разделитель строк, в результате чего весь файл будет выглядеть как одна строка.

grep -ozP '(?s)<tag1>(?:(?!tag1).)*?foo(?:(?!tag1).)*?</tag1>' file.txt
Iruvar
источник
Это не дает вывода в моей системе при запуске из файла примера OP.
Terdon
Работает для меня. +1. Спасибо за (?s)совет
Натан Уоллес
@terdon, какую версию GNU grep вы используете?
iruvar
@ 1_CR (GNU grep) 2.14в Debian. Я скопировал пример OP как есть (добавив только последний перевод строки) и запустил ваш, grepно не получил результатов.
Тердон
1
@slm, я нахожусь на pcre 6.6, GNU grep 2.5.1 на RHEL. Вы не возражаете , пытаясь grep -ozPвместо grep -oPваших платформ?
iruvar
3
#begin command block
#append all lines between two addresses to hold space 
    sed -n -f - <<\SCRIPT file.xml
        \|<tag1>|,\|</tag1>|{ H 
#at last line of search block exchange hold and pattern space 
            \|</tag1>|{ x
#if not conditional ;  clear buffer ; branch to script end
                \|<tag2>[^<]*foo[^\n]*</tag2>|!{s/.*//;h;b}
#do work ; print result; clear buffer ; close blocks
    s?*?*?;p;s/.*//;h;b}}
SCRIPT

Если вы сделаете выше, учитывая данные, которые вы показываете, перед последней строкой очистки, вы должны работать с sedпространством шаблона, которое выглядит следующим образом:

 ^\n<tag1>\n<tag2>foo</tag2>\n</tag1>$

Вы можете распечатать свое пространство шаблона в любое время с помощью lOok. Затем вы можете обратиться по \nсимволам.

sed l <file

Покажет вам каждая строка sedобрабатывает ее на этапе, на котором lвызывается.

Итак, я только что проверил его, и он нуждался в еще одном \backslashпосле ,commaпервой строки, но в остальном работает как есть. Здесь я поместил это в _sed_functionтак, чтобы я мог легко вызвать это для демонстрационных целей в течение этого ответа: (работает с включенными комментариями, но здесь удален ради краткости)

_sed_function() { sed -n -f /dev/fd/3 
} 3<<\SCRIPT <<\FILE 
    \|<tag1>|,\|</tag1>|{ H
        \|</tag1>|{ x
            \|<tag2>[^<]*foo[^\n]*</tag2>|!{s/.*//;h;b}
    s?*?*?;p;s/.*//;h;b}}
#END
SCRIPT
<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>
FILE


_sed_function
#OUTPUT#
<tag1>
 <tag2>foo</tag2>
</tag1>

Теперь мы переключим параметр pна, lчтобы мы могли видеть, с чем мы работаем, когда мы разрабатываем наш сценарий, и удаляем неоперационную демонстрацию, s?чтобы последняя строка нашего sed 3<<\SCRIPTкода выглядела так:

l;s/.*//;h;b}}

Тогда я запустлю это снова:

_sed_function
#OUTPUT#
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$

ОК! Так что я был прав - это хорошее чувство. Теперь давайте перетасуем наш look, чтобы увидеть строки, которые он вытягивает, но удаляет. Мы удалим наш текущий lи добавим один к !{block}так, чтобы это было похоже на:

!{l;s/.*//;h;b}

_sed_function
#OUTPUT#
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$

Вот как это выглядит перед тем, как мы уничтожим это.

И последнее, что я хочу показать вам, это Hстарое пространство, в котором мы его строим. Есть пара ключевых концепций, которые я надеюсь продемонстрировать. Поэтому я lснова удаляю последний ook и изменяю первую строку, чтобы добавить заглядывание в Hстарое пространство в конце:

{ H ; x ; l ; x

_sed_function
#OUTPUT#
\n<tag1>$
\n<tag1>\n <tag2>bar</tag2>$
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$
\n<tag1>$
\n<tag1>\n <tag2>foo</tag2>$
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$

Hстарое пространство переживает линейные циклы - отсюда и название. Так что люди часто сбиваются с толку - хорошо, то, что я часто сбиваю с толку - это то, что его нужно удалить после того, как вы его используете. В этом случае я могу xизменить только один раз, поэтому пространство удержания становится пространством шаблона и наоборот, и это изменение также сохраняется в циклах строк.

Эффект заключается в том, что мне нужно удалить мое пространство удержания, которое раньше было моим пространством образца. Я делаю это, сначала очистив пространство текущего шаблона с помощью:

s/.*//

Который просто выбирает каждого персонажа и удаляет его. Я не могу использовать, dпотому что это завершит мой текущий цикл строки, а следующая команда не будет выполнена, что в значительной степени испортит мой сценарий.

h

Это работает аналогично, Hно перезаписывает пространство удержания, поэтому я просто скопировал пустое пространство шаблона поверх моего пространства удержания, фактически удалив его. Теперь я могу просто:

b

вне.

И вот как я пишу sedсценарии.

mikeserv
источник
Спасибо @slm! Ты действительно хороший парень, ты знаешь это?
mikeserv
Спасибо, отличная работа, очень быстрое восхождение на 3к, далее 5к 8-)
slm
Я не знаю, @slm. Я начинаю видеть, что я учусь здесь все меньше и меньше - возможно, я перерос свою полезность. Я должен подумать об этом. Я даже почти не заходил на сайт последние пару недель.
mikeserv
Хотя бы добраться до 10к. Все, что стоит разблокировать, находится на этом уровне. Продолжайте снимать, 5к сейчас придут довольно быстро.
SLM
1
Ну, @slm - ты все равно редкая порода. Я согласен с несколькими ответами, хотя. Вот почему меня это беспокоит, когда закрываются некоторые вопросы. Но это редко случается, на самом деле. Еще раз спасибо, slm.
mikeserv
2

Ответ @ jamespfinn будет отлично работать, если ваш файл такой же простой, как ваш пример. Если у вас более сложная ситуация, в которой <tag1>может быть больше двух строк, вам понадобится немного более сложный прием. Например:

$ cat foo.xml
<tag1>
 <tag2>bar</tag2>
 <tag3>baz</tag3>
</tag1>
<tag1>

 <tag2>foo</tag2>
</tag1>

<tag1>
 <tag2>bar</tag2>

 <tag2>foo</tag2>
 <tag3>baz</tag3>
</tag1>
$ perl -ne 'if(/<tag1>/){$a=1;} 
            if($a==1){push @l,$_}
            if(/<\/tag1>/){
              if(grep {/foo/} @l){print "@l";}
               $a=0; @l=()
            }' foo.xml
<tag1>

  <tag2>foo</tag2>
 </tag1>
<tag1>
  <tag2>bar</tag2>

  <tag2>foo</tag2>
  <tag3>baz</tag3>
 </tag1>

Сценарий Perl обработает каждую строку вашего входного файла и

  • if(/<tag1>/){$a=1;}: переменная $aустанавливается на, 1если <tag1>найден открывающий тег ( ).

  • if($a==1){push @l,$_}: для каждой строки, если $aесть 1, добавить эту строку в массив @l.

  • if(/<\/tag1>/) : если текущая строка соответствует закрывающему тегу:

    • if(grep {/foo/} @l){print "@l"}: если какая-либо из строк, сохраненных в массиве @l(это строки между <tag1>и </tag1>), совпадает со строкой foo, выведите содержимое @l.
    • $a=0; @l=(): очистить список ( @l=()) и установить $aзначение 0.
Тердон
источник
Это работает хорошо, за исключением случая, когда есть несколько тегов <tag1>, содержащих «foo». В этом случае он печатает каждую вещь от начала первого <tag1> до конца последнего </ tag1> ...
Den
@den Я проверил это на примере, показанном в моем ответе, который содержит 3 <tag1>с, fooи он отлично работает. Когда это терпит неудачу для вас?
Terdon
Это так неправильно
разбирать
1

Вот sedальтернатива:

sed -n '/<tag1/{:x N;/<\/tag1/!b x};/foo/p' your_file

объяснение

  • -n означает не печатать строки, если не указано
  • /<tag1/ сначала соответствует открывающему тегу
  • :x это метка, позволяющая перейти к этой точке позже
  • N добавляет следующую строку в пространство шаблона (активный буфер).
  • /<\/tag1/!b xозначает, что если текущее пространство шаблона не содержит закрывающего тега, переход к xметке, созданной ранее. Таким образом, мы продолжаем добавлять строки в пространство шаблона, пока не найдем наш закрывающий тег.
  • /foo/pозначает, что если текущее пространство образца совпадает foo, оно должно быть напечатано.
Джозеф Р.
источник
1

Я думаю, вы могли бы сделать это с помощью GNU awk, рассматривая конечный тег как разделитель записей, например, для известного конечного тега </tag1>:

gawk -vRS="\n</tag1>\n" '/foo/ {printf "%s%s", $0, RT}'

или в более общем случае (с регулярным выражением для конечного тега)

gawk -vRS="\n</[^>]*>\n" '/foo/ {printf "%s%s", $0, RT}'

Тестирование на @ terdon's foo.xml:

$ gawk -vRS="\n</[^>]*>\n" '/foo/ {printf "%s%s", $0, RT}' foo.xml
<tag1>

 <tag2>foo</tag2>
</tag1>

<tag1>
 <tag2>bar</tag2>

 <tag2>foo</tag2>
 <tag3>baz</tag3>
</tag1>
steeldriver
источник
0

Если ваш файл структурирован точно так, как вы показали выше, вы можете использовать флаги -A (строки после) и -B (строки до) для grep ... например:

$ cat yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>
$ grep -A1 -B1 bar yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>
$ grep -A1 -B1 foo yourFile.txt 
<tag1>
 <tag2>foo</tag2>
</tag1>

Если ваша версия grepподдерживает это, вы также можете использовать более простой -C(для контекста) параметр, который печатает окружающие N строк:

$ grep -C 1 bar yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>
jamespfinn
источник
Спасибо, но нет. Это только пример, и реальные вещи выглядят довольно непредсказуемо ;-)
День
1
Это не поиск тега с foo, это просто поиск foo и отображение строк контекста
Nathan Wallace
@NathanWallace: да, это именно то, о чем просил ОП, этот ответ отлично работает в случае, указанном в вопросе.
Тердон
@terdon это совсем не то, что задает вопрос. Цитата: «Я хотел бы прочитать <tag1>, если он содержит foo где-то внутри». Это решение похоже на «Я хотел бы прочитать« foo »и 1 строку контекста независимо от того, где появляется« foo »». Следуя вашей логике, столь же правильный ответ на этот вопрос будет tail -3 input_file.xml. Да, это работает для этого конкретного примера, но это не полезный ответ на вопрос.
Натан Уоллес
@NathanWallace моя точка зрения заключалась в том, что OP специально заявил, что это недопустимый формат XML, в этом случае вполне могло бы быть достаточно напечатать N строк вокруг строки, которую ищет OP. С доступной информацией этот ответ был достаточно приличным.
Terdon