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


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

Это не дает вывода в моей системе при запуске из файла примера 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сценарии.


Спасибо @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>

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>

Спасибо, но нет. Это только пример, и реальные вещи выглядят довольно непредсказуемо ;-)
— День

1
Это не поиск тега с foo, это просто поиск foo и отображение строк контекста
— Nathan Wallace

@NathanWallace: да, это именно то, о чем просил ОП, этот ответ отлично работает в случае, указанном в вопросе.
— Тердон

@terdon это совсем не то, что задает вопрос. Цитата: «Я хотел бы прочитать <tag1>, если он содержит foo где-то внутри». Это решение похоже на «Я хотел бы прочитать« foo »и 1 строку контекста независимо от того, где появляется« foo »». Следуя вашей логике, столь же правильный ответ на этот вопрос будет tail -3 input_file.xml. Да, это работает для этого конкретного примера, но это не полезный ответ на вопрос.
— Натан Уоллес

@NathanWallace моя точка зрения заключалась в том, что OP специально заявил, что это недопустимый формат XML, в этом случае вполне могло бы быть достаточно напечатать N строк вокруг строки, которую ищет OP. С доступной информацией этот ответ был достаточно приличным.
— Terdon
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.