Jak uzyskać wiele wierszy z pliku przez wyrażenie regularne?


10

Jak uzyskać wiele wierszy z pliku przez wyrażenie regularne?

Często chciałbym uzyskać wiele linii / zmodyfikować wiele linii za pomocą wyrażenia regularnego. Przykładowy przypadek:

Próbuję odczytać część pliku XML / SGML (niekoniecznie są one dobrze sformułowane lub mają przewidywalną składnię, więc wyrażenie regularne byłoby bezpieczniejsze niż odpowiedni analizator składni. Ponadto chciałbym móc to zrobić również całkowicie pliki nieustrukturyzowane, w których znane są tylko niektóre słowa kluczowe.) w skrypcie powłoki (działającym w systemach Solaris i Linux).

Przykładowy XML:

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

Z tego chciałbym przeczytać, <tag1>czy zawiera foogdzieś w sobie.

Wyrażenie regularne (<tag1>.*?foo.*?</tag1>)powinno dać właściwą część, ale narzędzia takie jak grepi seddziałają tylko dla mnie z pojedynczymi liniami. Jak mogę zdobyć

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

w tym przykładzie?



@evilsoup To prawda, ale moje pytanie nie dotyczy konkretnie plików XML / SGML, tylko dowolnych plików tekstowych.
— Den

Odpowiedzi:


7

Jeśli masz zainstalowany system GNU grep, możesz wyszukiwać wieloliniowo, przekazując -Pflagę (perl-regex) i aktywując za PCRE_DOTALLpomocą(?s)

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

Jeśli powyższe nie działa na twojej platformie, spróbuj przekazać -zflagę dodatkowo, to zmusza grep do traktowania NUL jako separatora linii, powodując, że cały plik wygląda jak pojedyncza linia.

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

Nie daje to żadnego wyjścia w moim systemie, gdy jest uruchomiony na przykładowym pliku OP.
— terdon

Pracuje dla mnie. +1. Dzięki za (?s)wskazówkę
— Nathan Wallace

@terdon, jaką wersję GNU grep używasz?
— iruvar

@ 1_CR (GNU grep) 2.14w Debianie. Skopiowałem przykład OP w takiej postaci, w jakiej jest (dodając tylko ostatnią nową linię) i uruchomiłem grepna nim, ale nie otrzymałem żadnych wyników.
— terdon

1
@slm, jestem na PCre 6.6, GNU grep 2.5.1 na RHEL. Czy masz coś przeciwko próbowaniu grep -ozPzamiast grep -oPna swoich platformach?
— 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

Jeśli zrobisz powyższe, biorąc pod uwagę wyświetlane dane, przed ostatnim wierszem czyszczenia, powinieneś pracować z sedprzestrzenią wzorów, która wygląda następująco:

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

Możesz wydrukować przestrzeń wzoru w dowolnym momencie za pomocą lOOK. Następnie możesz adresować \nznaki.

sed l <file

Pokaże, że każda linia sedprzetwarza ją na etapie, w którym ljest wywoływana.

Właśnie to przetestowałem i potrzebowałem jeszcze jednego \backslashpo ,commapierwszej linii, ale poza tym działa tak, jak jest. Tutaj umieściłem go, _sed_functionaby móc łatwo nazwać go w celach demonstracyjnych w całej tej odpowiedzi: (działa z dołączonymi komentarzami, ale zostały tu usunięte ze względu na zwięzłość)

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

Teraz zmienimy na pna l, abyśmy mogli zobaczyć, nad czym pracujemy, podczas opracowywania naszego skryptu i usuwania demonstracji non-op, s?dzięki czemu ostatni wiersz naszego sed 3<<\SCRIPTwygląda następująco:

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

Potem uruchomię to jeszcze raz:

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

Ok! Więc miałem rację - to dobre uczucie. Teraz potrząśnij naszym lokiem, aby zobaczyć linie, które wciąga, ale usuwa. Usuniemy nasz obecny li dodamy jeden do, !{block}aby wyglądał następująco:

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

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

Tak to wygląda tuż przed usunięciem.

Ostatnią rzeczą, którą chcę wam pokazać, jest Hstara przestrzeń, w której ją budujemy. Mam kilka kluczowych pojęć, które, mam nadzieję, mogę wykazać. Więc lponownie usuwam ostatni ook i zmieniam pierwszy wiersz, aby dodać zerknięcie do Hstarej spacji na końcu:

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

Hstara przestrzeń przetrwa cykle liniowe - stąd nazwa. Więc co ludzie często potknąć się na - Ok, co ja często potknąć się na - jest to, że wymaga kasowania po użyciu. W tym przypadku xzmieniam się tylko raz, więc przestrzeń wstrzymania staje się przestrzenią wzorów i odwrotnie, a ta zmiana przetrwa również cykle linii.

W efekcie muszę usunąć przestrzeń wstrzymania, która była kiedyś przestrzenią wzorów. Robię to, najpierw czyszcząc bieżącą przestrzeń wzorców za pomocą:

s/.*//

Który po prostu wybiera każdą postać i usuwa ją. Nie mogę użyć, dponieważ to skończyłoby mój bieżący cykl linii i następne polecenie nie zostałoby ukończone, co praktycznie zniszczyłoby mój skrypt.

h

Działa to w podobny sposób, Hale zastępuje przestrzeń wstrzymania, więc właśnie skopiowałem swoją pustą przestrzeń wzoru na górze mojej przestrzeni wstrzymania, skutecznie ją usuwając. Teraz mogę po prostu:

b

na zewnątrz.

I tak piszę sedskrypty.


Dzięki @slm! Jesteś naprawdę w porządku facetem, wiesz o tym?
— mikeserv

Dzięki, dobra robota, bardzo szybki wzrost do 3 tys., Następnie 5 tys. 8-)
— slm

Nie wiem, @slm. Zaczynam widzieć, że uczę się tutaj coraz mniej - może przerosłem już jego przydatność. Muszę o tym pomyśleć. ledwo nawet pojawiłem się na stronie w ciągu ostatnich kilku tygodni.
— mikeserv

Co najmniej dostać się do 10k. Wszystko, co warto odblokować, znajduje się na tym poziomie. Trzymaj się z dala, 5k przyjedzie teraz dość szybko.
— slm

1
Cóż, @slm - i tak jesteś rzadką rasą. Zgadzam się co do wielu odpowiedzi. Dlatego to mnie wkurza, gdy niektóre qs się zamykają. Ale tak naprawdę rzadko się to zdarza. Jeszcze raz dziękuję, slm.
— mikeserv

2

Odpowiedź @ jamespfinn będzie działać idealnie, jeśli Twój plik jest tak prosty jak twój przykład. Jeśli masz bardziej złożoną sytuację, która <tag1>może obejmować więcej niż 2 linie, potrzebujesz nieco bardziej złożonej sztuczki. Na przykład:

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

Skrypt perla przetworzy każdą linię pliku wejściowego i

  • if(/<tag1>/){$a=1;}: zmienna $ajest ustawiona na, 1jeśli <tag1>znaleziono otwierający tag ( ).

  • if($a==1){push @l,$_}: jeśli $ajest 1, dodaj tę linię do tablicy @l.

  • if(/<\/tag1>/) : jeśli bieżący wiersz pasuje do tagu zamykającego:

    • if(grep {/foo/} @l){print "@l"}: jeśli którakolwiek z linii zapisanych w tablicy @l(są to linie pomiędzy <tag1>i </tag1>) pasuje do ciągu foo, wypisz zawartość @l.
    • $a=0; @l=(): opróżnij listę ( @l=()) i ustaw z $apowrotem na 0.

Działa to dobrze, z wyjątkiem przypadku, gdy istnieje więcej niż jeden <tag1> zawierający „foo”. W takim przypadku wydruk jest wszystko, co od początku pierwsza <tag1> do końca ostatniego </ tag1> ...
— Den

@den Przetestowałem to na przykładzie pokazanym w mojej odpowiedzi, który zawiera 3 <tag1>z fooi działa dobrze. Kiedy ci się to nie udaje?
— terdon

tak źle
— parsuje

1

Oto sedalternatywa:

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

Wyjaśnienie

  • -n oznacza, że ​​nie drukuj wierszy, chyba że otrzymałeś takie polecenie.
  • /<tag1/ pierwszy pasuje do tagu otwierającego
  • :x to etykieta umożliwiająca późniejsze przejście do tego punktu
  • N dodaje następny wiersz do obszaru wzorów (aktywny bufor).
  • /<\/tag1/!b xoznacza, że ​​jeśli bieżąca przestrzeń wzorcowa nie zawiera znacznika zamykającego, przejdź do xutworzonej wcześniej etykiety. W ten sposób dodajemy linie do przestrzeni wzorów, dopóki nie znajdziemy naszego tagu zamykającego.
  • /foo/poznacza, że ​​jeśli bieżąca przestrzeń wzoru pasuje foo, należy ją wydrukować.

1

Myślę, że można to zrobić za pomocą GNU awk, traktując znacznik końcowy jako separator rekordów, np. Dla znanego znacznika końcowego </tag1>:

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

lub bardziej ogólnie (z wyrażeniem regularnym dla tagu końcowego)

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

Testowanie na @ 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

Jeśli plik ma strukturę dokładnie taką, jak pokazano powyżej, możesz użyć flag -A (wiersze po) i -B (wiersze przed) dla grep ... na przykład:

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

Jeśli twoja wersja grepgo obsługuje, możesz także użyć prostszej -C(dla kontekstu) opcji, która drukuje otaczające N wiersze:

$ grep -C 1 bar yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>

Dziękuje ale nie. To tylko przykład, a prawdziwe rzeczy wyglądają dość nieprzewidywalnie ;-)
— Den

1
To nie jest znalezienie tagu z foo, to tylko znalezienie foo i wyświetlenie linii kontekstu
— Nathan Wallace

@NathanWallace tak, dokładnie o to prosił PO, ta odpowiedź działa idealnie dobrze w przypadku podanym w pytaniu.
— terdon

@terdon wcale nie o to pyta. Cytat: „Chciałbym przeczytać <tag1>, jeśli zawiera on gdzieś foo.” To rozwiązanie jest jak „Chciałbym przeczytać„ foo ”i 1 linię kontekstu niezależnie od tego, gdzie pojawia się„ foo ”. Zgodnie z twoją logiką, równie ważna byłaby odpowiedź na to pytanie tail -3 input_file.xml. Tak, działa w tym konkretnym przykładzie, ale nie jest to pomocna odpowiedź na pytanie.
— Nathan Wallace

@NathanWallace miałem na myśli to, że OP wyraźnie stwierdził, że nie jest to prawidłowy format XML, w takim przypadku może być wystarczające wydrukowanie N linii wokół szukanego ciągu przez OP. Przy dostępnych informacjach odpowiedź była wystarczająco przyzwoita.
— terdon
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.