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 foo
gdzieś w sobie.
Wyrażenie regularne (<tag1>.*?foo.*?</tag1>)
powinno dać właściwą część, ale narzędzia takie jak grep
i sed
działają tylko dla mnie z pojedynczymi liniami. Jak mogę zdobyć
<tag1>
<tag2>foo</tag2>
</tag1>
w tym przykładzie?
Odpowiedzi:
Jeśli masz zainstalowany system GNU grep, możesz wyszukiwać wieloliniowo, przekazując
-P
flagę (perl-regex) i aktywując zaPCRE_DOTALL
pomocą(?s)
Jeśli powyższe nie działa na twojej platformie, spróbuj przekazać
-z
flagę dodatkowo, to zmusza grep do traktowania NUL jako separatora linii, powodując, że cały plik wygląda jak pojedyncza linia.źródło
(?s)
wskazówkę(GNU grep) 2.14
w Debianie. Skopiowałem przykład OP w takiej postaci, w jakiej jest (dodając tylko ostatnią nową linię) i uruchomiłemgrep
na nim, ale nie otrzymałem żadnych wyników.grep -ozP
zamiastgrep -oP
na swoich platformach?Jeśli zrobisz powyższe, biorąc pod uwagę wyświetlane dane, przed ostatnim wierszem czyszczenia, powinieneś pracować z
sed
przestrzenią wzorów, która wygląda następująco:Możesz wydrukować przestrzeń wzoru w dowolnym momencie za pomocą
l
OOK. Następnie możesz adresować\n
znaki.Pokaże, że każda linia
sed
przetwarza ją na etapie, w któryml
jest wywoływana.Właśnie to przetestowałem i potrzebowałem jeszcze jednego
\backslash
po,comma
pierwszej linii, ale poza tym działa tak, jak jest. Tutaj umieściłem go,_sed_function
aby 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ść)Teraz zmienimy na
p
nal
, abyśmy mogli zobaczyć, nad czym pracujemy, podczas opracowywania naszego skryptu i usuwania demonstracji non-op,s?
dzięki czemu ostatni wiersz naszegosed 3<<\SCRIPT
wygląda następująco:Potem uruchomię to jeszcze raz:
Ok! Więc miałem rację - to dobre uczucie. Teraz potrząśnij naszym
l
okiem, aby zobaczyć linie, które wciąga, ale usuwa. Usuniemy nasz obecnyl
i dodamy jeden do,!{block}
aby wyglądał następująco:Tak to wygląda tuż przed usunięciem.
Ostatnią rzeczą, którą chcę wam pokazać, jest
H
stara przestrzeń, w której ją budujemy. Mam kilka kluczowych pojęć, które, mam nadzieję, mogę wykazać. Więcl
ponownie usuwam ostatni ook i zmieniam pierwszy wiersz, aby dodać zerknięcie doH
starej spacji na końcu:H
stara 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 przypadkux
zmieniam 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ą:
Który po prostu wybiera każdą postać i usuwa ją. Nie mogę użyć,
d
ponieważ 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.Działa to w podobny sposób,
H
ale 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:na zewnątrz.
I tak piszę
sed
skrypty.źródło
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:Skrypt perla przetworzy każdą linię pliku wejściowego i
if(/<tag1>/){$a=1;}
: zmienna$a
jest ustawiona na,1
jeśli<tag1>
znaleziono otwierający tag ( ).if($a==1){push @l,$_}
: jeśli$a
jest1
, 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ągufoo
, wypisz zawartość@l
.$a=0; @l=()
: opróżnij listę (@l=()
) i ustaw z$a
powrotem na 0.źródło
<tag1>
zfoo
i działa dobrze. Kiedy ci się to nie udaje?Oto
sed
alternatywa: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 punktuN
dodaje następny wiersz do obszaru wzorów (aktywny bufor)./<\/tag1/!b x
oznacza, że jeśli bieżąca przestrzeń wzorcowa nie zawiera znacznika zamykającego, przejdź dox
utworzonej wcześniej etykiety. W ten sposób dodajemy linie do przestrzeni wzorów, dopóki nie znajdziemy naszego tagu zamykającego./foo/p
oznacza, że jeśli bieżąca przestrzeń wzoru pasujefoo
, należy ją wydrukować.źródło
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>
:lub bardziej ogólnie (z wyrażeniem regularnym dla tagu końcowego)
Testowanie na @ terdon's
foo.xml
:źródło
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:
Jeśli twoja wersja
grep
go obsługuje, możesz także użyć prostszej-C
(dla kontekstu) opcji, która drukuje otaczające N wiersze:źródło
tail -3 input_file.xml
. Tak, działa w tym konkretnym przykładzie, ale nie jest to pomocna odpowiedź na pytanie.