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


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.