Як отримати декілька рядків з файлу за допомогою регулярного вираження?


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-regex) і активуючи за PCRE_DOTALLдопомогою(?s)

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

Якщо вищезгадане не працює на вашій платформі, спробуйте передати -zпрапор на додаток, це змушує греп трактувати NUL як роздільник рядків, внаслідок чого весь файл виглядає як один рядок.

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

Це не дає результатів у моїй системі під час запуску на прикладі файлу ОП.
тердон

Працює для мене. +1. Дякую за (?s)пораду
Натан Уоллес

@terdon, яку версію GNU grep ти працюєш?
iruvar

@ 1_CR (GNU grep) 2.14на Debian. Я скопіював приклад ОП так, як є (додавши лише остаточний новий рядок) і запустив grepна нього, але результатів не отримав.
terdon

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>$

Ви можете роздрукувати простір шаблону, коли завгодно, з lдубом. Потім можна звертатися до \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>$

Добре! Тож я мав рацію - це гарне почуття. Тепер давайте перемістимо наш lдуб навколо, щоб побачити лінії, які він тягне, але видаляє. Ми видалимо наш поточний lі додамо його, !{block}щоб він виглядав так:

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

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

Ось як це виглядає безпосередньо перед тим, як ми їх витираємо.

Останнє, що я хочу вам показати, - це Hстарий простір під час його створення. Є кілька ключових понять, які я сподіваюся продемонструвати. Тому я lзнову видаляю останній ок і змінюю перший рядок, щоб додати заглянути до 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. Я починаю бачити, що я тут все менше і менше навчаюся - можливо, я переріс його корисність. Я мушу подумати над цим. ive ледве навіть прийшов на сайт останні пару тижнів.
mikeserv

Принаймні дістатися до 10k. Все, що варто розблокувати, знаходиться на такому рівні. Тримайте чіпінг далеко, 5k прийде досить швидко.
slm

1
Ну, @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>

Дякую, але ні. Це всього лише приклад , і реальний матеріал виглядає досить непередбачувано ;-)
Den

1
Це не пошук тегів з foo в ньому, це просто пошук foo та відображення рядків контексту
Nathan Wallace

@NathanWallace так, саме про це і просив ОП, ця відповідь чудово справляється у випадку, поданому у запитанні.
terdon

@terdon - це зовсім не те, що задає питання. Цитата: "Я хотів би прочитати <tag1>, якщо він містить десь усередині нього". Це рішення схоже на "я хотів би прочитати" foo "і 1 рядок контексту, незалежно від місця" foo "." Виходячи з вашої логіки, однаково справедливою відповіді на це питання було б tail -3 input_file.xml. Так, це працює для цього конкретного прикладу, але це не є корисною відповіддю на питання.
Натан Уоллес

@NathanWallace мій погляд на те, що ОП спеціально заявила, що це не допустимий формат XML, у цьому випадку це могло б бути достатнім для друку N рядків навколо рядка, який шукає ОП. За наявної інформації ця відповідь була досить гідною.
тердон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.