Bir regex ile bir dosyadan birden fazla satır nasıl alınır?


10

Bir regex ile bir dosyadan birden fazla satır nasıl alınır?

Sık sık birden fazla satır almak / bir regex ile birden çok satır değiştirmek istiyorum. Örnek bir durum:

Bir XML / SGML dosyasının bir kısmını okumaya çalışıyorum (mutlaka iyi biçimlendirilmiş veya tahmin edilebilir bir sözdiziminde değil, bu nedenle bir regex uygun bir ayrıştırıcıdan daha güvenli olacaktır.Ayrıca ben de tamamen beyaz yapmak istiyorum sadece bazı anahtar kelimelerin bilindiği yapılandırılmamış dosyalar.) bir kabuk betiğinde (Solaris ve Linux üzerinde çalışıyor).

Örnek XML:

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

Bundan <tag1>, fooiçinde bir yerde olup olmadığını okumak istiyorum.

Gibi bir regex (<tag1>.*?foo.*?</tag1>)sağ kısmını ancak araçları gibi vermeli grepve sedtek hatları ile benim için tek işi. Nasıl alabilirim

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

bu örnekte?



@evilsoup Bu doğru, ama sorum özellikle XML / SGML dosyalarıyla ilgili değil, herhangi bir metin dosyasıyla ilgili.
Den

Yanıtlar:


7

Eğer GNU grep yüklü varsa satırlı içinde ileterek arama yapabilir -P(perl-regex) bayrağı ve aktive PCRE_DOTALLolan(?s)

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

Yukarıdakiler platformunuzda çalışmazsa, -zbayrağı ek olarak geçmeyi deneyin , bu grep'i NUL'a satır ayırıcı olarak davranmaya zorlar ve tüm dosyanın tek bir satır gibi görünmesine neden olur.

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

Bu, OP'nin örnek dosyasında çalıştırıldığında sistemime hiçbir çıktı vermez.
terdon

Benim için çalışıyor. +1. İçin teşekkürler (?s)ucu
Nathan Wallace

@terdon, GNU grep'in hangi sürümünü kullanıyorsunuz?
iruvar

@ Debian'da 1_CR (GNU grep) 2.14. OP örneğini olduğu gibi kopyaladım (sadece son satırsonu ekleyerek) ve üzerinde koştum grepama sonuç alamadım.
terdon

1
@slm, ben pcre 6.6'da, GNU grep 2.5.1 RHEL'de. Platformlarınız grep -ozPyerine denemek ister misiniz 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

Yukarıdakileri yaparsanız, gösterdiğiniz veriler göz önüne alındığında, oradaki son temizleme satırından önce sed, şuna benzer bir desen alanı ile çalışmalısınız :

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

Desen alanınızı istediğiniz zaman look ile yazdırabilirsiniz . Daha sonra \nkarakterlere hitap edebilirsiniz .

sed l <file

Her satırın çağrıldığı sedaşamada işlediğini gösterecektir l.

Sadece bunu test ettik ve daha bir ihtiyaç Yani \backslashsonra ,commailk satırda, ama aksi çalışıyor gibidir. Burada bir _sed_functioncevap koydum, bu yüzden bu cevap boyunca gösteri amaçları için kolayca çağırabilirim: (yorumlarla birlikte çalışır, ancak kısaca aşk için kaldırılır)

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

Şimdi geçiş yaparız pbir için lbiz bizim senaryoyu geliştirmek ve non-op demo kaldırmak olarak nasıl dans ettiğini görebilirsiniz s?bizim son satırında bu yüzden sed 3<<\SCRIPTsadece görünüyor gibi:

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

Sonra tekrar çalıştırırım:

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

Tamam! Bu yüzden haklıydım - bu iyi bir duygu. Şimdi, liçine çektiği ancak sildiği çizgileri görmek için fırçamızı karıştıralım . Akımımızı kaldıracak lve bir tane ekleyeceğiz, !{block}böylece şöyle görünüyor:

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

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

Silmeden hemen önce böyle görünüyor.

Size göstermek istediğim son şey H, onu oluştururken eski alan. Gösterebileceğimi umduğum birkaç anahtar kavram var. Bu yüzden son lmeşeyi tekrar kaldırıyorum Hve sonunda eski boşluğa bir göz eklemek için ilk satırı değiştiriyorum :

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

Heski alan çizgi döngülerinde hayatta kalır - dolayısıyla adı. Ne Tamam - yani insanlar genellikle üzerinde çelme neler Ben sıklıkta çelme - Kullandıktan sonra onu silerek ihtiyacı olmasıdır. Bu durumda sadece bir xkez değişirim, böylece tutma alanı desen alanı haline gelir ve tam tersi ve bu değişiklik de çizgi döngülerinde hayatta kalır.

Etkisi, eskiden desen alanım olan tutma alanımı silmem gerektiğidir. Ben ilk önce geçerli desen alanı temizleyerek yaparak:

s/.*//

Hangi her karakteri seçer ve onu kaldırır. Kullanamıyorum dçünkü bu benim mevcut satır döngüsü sona erecek ve bir sonraki komut tamamlanmayacaktı, bu da benim senaryoyu çöpe atacaktır.

h

Bu benzer şekilde çalışır, Hancak tutma alanının üzerine yazar , bu yüzden boş desen alanımı tutma alanımın üstüne kopyaladım, etkili bir şekilde sildim. Şimdi sadece:

b

dışarı.

Ben de böyle sedsenaryo yazıyorum .


Teşekkürler @slm! Sen gerçekten iyi birisin, biliyor musun?
mikeserv

Teşekkürler, güzel iş, 3k çok hızlı yükseliş, sonraki 5k 8-)
slm

Bilmiyorum, slm. Burada daha az öğrenmeye başladığımı görmeye başlıyorum - belki de yararlılığından daha fazla büyüdüm. Bunu düşünmeliyim. hatta son birkaç hafta bile siteye geldim.
mikeserv

En az 10 bin olsun. Kilidi açmaya değer her şey bu seviyede. Uzaklaşmaya devam edin, 5k şimdi oldukça hızlı bir şekilde gelecektir.
slm

1
Eh, @slm - sen zaten nadir bir cinssin. Yine de çoklu cevapları kabul ediyorum. Bu yüzden bazı qs kapatıldığında beni rahatsız ediyor. Ama bu nadiren olur, aslında. Tekrar teşekkürler, slm.
mikeserv

2

@ jamespfinn dosyasının yanıtı, örneğin örneği kadar basitse mükemmel çalışır. Eğer <tag1>2 çizgiden fazla olabilecek daha karmaşık bir durumunuz varsa, biraz daha karmaşık bir numaraya ihtiyacınız olacaktır. Örneğin:

$ 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 betiği girdi dosyanızın her satırını işleyecek ve

  • if(/<tag1>/){$a=1;}: açılış etiketi ( ) bulunursa değişken $aolarak ayarlanır .1<tag1>

  • if($a==1){push @l,$_}Her bir hat için, eğer $abir 1dizinin bu hat ekleyin @l.

  • if(/<\/tag1>/) : geçerli satır kapanış etiketiyle eşleşiyorsa:

    • if(grep {/foo/} @l){print "@l"}: diziye kaydedilen satırlardan herhangi biri @l(bunlar <tag1>ve arasındaki satırlarsa </tag1>) dizeyle eşleşiyorsa foo, içeriğini yazdırın @l.
    • $a=0; @l=(): listeyi boşaltın ( @l=()) ve $a0 olarak ayarlayın .

Bu "foo" içeren birden fazla <tag1> olması durumu dışında iyi sonuç verir. Bu durumda, ilk <tag1> başından son </tag1> sonuna kadar her şeyi yazdırır ...
Den

@den 3 <tag1>ile içeren cevabımda gösterilen örnek ile test ettim foove iyi çalışıyor. Senin için ne zaman başarısız oluyor?
terdon

regex kullanarak xml ayrıştırma çok yanlış hissediyor :)
Braiam 27:14

1

İşte bir sedalternatif:

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

açıklama

  • -n anlamına gelmediği sürece satırları yazdırmayın demektir.
  • /<tag1/ ilk olarak açılış etiketi ile eşleşir
  • :x daha sonra bu noktaya atlamayı sağlayan bir etikettir
  • N desen uzayına sonraki satırı ekler (etkin arabellek).
  • /<\/tag1/!b xgeçerli desen alanı kapanış etiketi içermiyorsa, xdaha önce oluşturulan etikete dallanır . Böylece kapanış etiketimizi bulana kadar desen alanına çizgiler eklemeye devam ediyoruz.
  • /foo/pgeçerli desen alanı eşleşiyorsa foo, yazdırılması gerektiği anlamına gelir .

1

Bence GNU awk ile yapabilirsin, son etiketi bilinen bir son etiketi için bir kayıt ayırıcı olarak ele alarak </tag1>:

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

veya daha genel olarak (bitiş etiketi için normal ifadeyle)

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

@ Terdon's üzerinde test etme 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

Dosyanız tam olarak yukarıda gösterildiği gibi yapılandırılmışsa, grep için -A (sonraki satırlar) ve -B (önceki satırlar) bayraklarını kullanabilirsiniz ... örneğin:

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

Sürümünüz grepdestekliyorsa -C, çevredeki N satırlarını yazdıran daha basit (bağlam için) seçeneğini de kullanabilirsiniz :

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

Teşekkürler ama hayır. Bu sadece bir örnek ve gerçek şeyler oldukça tahmin edilemez görünüyor ;-)
Den

1
İçinde foo olan bir etiket bulmak değil, sadece foo bulmak ve bağlam çizgileri görüntülemek
Nathan Wallace

@NathanWallace evet, bu OP'nin tam olarak istediği şeydi, bu cevap soruda verilen durumda mükemmel bir şekilde çalışıyor.
terdon

@terdon sorunun sorduğu soru bu değil. Alıntı: "İçinde bir yerde foo içeriyorsa <tag1> 'ı okumak istiyorum." Bu çözüm, 'foo'nun nerede göründüğüne bakılmaksızın' foo 've 1 bağlam satırını okumak istiyorum. Mantıklarınızın ardından, bu soruya aynı derecede geçerli bir cevap olacaktır tail -3 input_file.xml. Evet, bu belirli örnek için işe yarıyor, ancak soruya yardımcı bir cevap değil.
Nathan Wallace

@NathanWallace, OP'nin bunun geçerli bir XML formatı olmadığını özellikle belirtmişti, bu durumda OP'nin aradığı dizenin etrafındaki N satırlarını yazdırmak için yeterli olabilirdi. Mevcut bilgilerle bu cevap yeterince iyi idi.
terdon
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.