Grep çıktısı, yalnızca eşleşen belirli gruplar oluşturuyor mu?


290

Diyelim ki bir dosyam var:

# file: 'test.txt'
foobar bash 1
bash
foobar happy
foobar

Sadece "foobar" dan sonra hangi kelimelerin göründüğünü bilmek istiyorum, bu yüzden bu regex'i kullanabilirim:

"foobar \(\w\+\)"

Parantez, foobardan hemen sonra kelimeye özel bir ilgi duyduğumu gösteriyor. Ancak, bunu yaptığımda grep "foobar \(\w\+\)" test.txt, yalnızca "foobar'dan sonraki kelime" yerine tüm regex ile eşleşen tüm satırları alıyorum:

foobar bash 1
foobar happy

Bu komutun çıktısının şöyle görünmesini çok isterdim:

bash
happy

Grep'e yalnızca gruplandırmaya (veya belirli bir gruplandırmaya) uyan öğeleri düzenli bir ifadeyle çıkarmasını söylemenin bir yolu var mı?


4
Grep'e ihtiyaç duymayanlar için:perl -lne 'print $1 if /foobar (\w+)/' < test.txt
kasası

Yanıtlar:


325

GNU grep, -Pperl tarzı regex'ler için -oseçeneğe ve yalnızca desene uygun olanı yazdırma seçeneğine sahiptir. Bunlar , grep modelinin bir bölümünü amaçlarıyla eşleştiği tespit edilen şeyden çıkarmak için , etrafa dönük iddialar ( perlre man sayfasındaki Genişletilmiş Desenler altında açıklanmaktadır ) kullanılarak birleştirilebilir -o.

$ grep -oP 'foobar \K\w+' test.txt
bash
happy
$

\KKısa bir şekilde (ve daha verimli bir şekilde) olduğu (?<=pattern)Eğer çıktı istediğiniz metin önce sıfır genişliği olan ileriye dönük iddia olarak kullanan. (?=pattern)çıktı almak istediğiniz metinden sonra sıfır genişlikte ileriye dönük bir iddia olarak kullanılabilir.

Örneğin, foove ile arasındaki kelimeyi eşleştirmek baristerseniz, şunları kullanabilirsiniz:

$ grep -oP 'foo \K\w+(?= bar)' test.txt

veya (simetri için)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt

3
Eğer regex'iniz bir gruplamadan fazlaysa bunu nasıl yapabilirsiniz? (başlık ima edildiği gibi)
barracel

4
@ barracel: Yapabileceğine inanmıyorum. Zamansed(1)
camh

1
@ camh Ben sadece grep -oP 'foobar \K\w+' test.txtOP ile hiçbir şey çıktısını test etmedim test.txt. Grep sürümü 2.5.1. Neyin yanlış olabilir? O_O
SOUser

@ XichenLi: Söyleyemem. Ben sadece gr2'nin v2.5.1'ini yaptım (oldukça eski - 2006'dan itibaren) ve benim için çalıştı.
camh

@ SOUser: Aynı yaşadım - dosyaya hiçbir şey çıktı. Bu benim için çalıştığı şekilde çıktı göndermek için dosya adından önce '>' eklemek için düzenleme isteğini gönderdim.
rjchicago

39

Standart grep bunu yapamaz, ancak GNU grep'in son sürümleri bunu yapabilir . Sed, awk veya perl'e dönebilirsiniz. Örnek girişinizde istediğiniz şeyi yapan birkaç örnek; Köşe durumlarda biraz farklı davranırlar.

Değiştir foobar word other stufftarafından word, bir yedek yapılırsa sadece yazdırın.

sed -n -e 's/^foobar \([[:alnum:]]\+\).*/\1/p'

İlk sözcük ise foobar, ikinci sözcüğü yazdırın.

awk '$1 == "foobar" {print $2}'

Soyun foobarilk kelime ve çizgi aksi atlamak eğer; sonra ilk boşluktan sonra her şeyi soyun ve yazdırın.

perl -lne 's/^foobar\s+// or next; s/\s.*//; print'

Korku veren! Bunu sed ile yapabileceğimi düşündüm, ama daha önce kullanmadım ve tanıdıklarımı kullanabileceğimi umuyordum grep. Ancak bu komutların sözdizimi aslında vim tarzı arama ve değiştirme + regex'lerine aşina olduğum için çok tanıdık geliyor. Bir ton teşekkürler.
Cory Klein

1
Doğru değil Gilles. GNU grep çözümü için cevabımı görün.
Camh

1
@ camh: Ah, GNU grep'in şu an tam PCRE desteğine sahip olduğunu bilmiyordum. Cevabımı düzelttim, teşekkürler.
Gilles

1
Bu cevap özellikle gömülü Linux için faydalıdır, çünkü Busybox grepPCRE desteğine sahip değildir.
Craig McQueen

Açıkçası, sunulan aynı görevi yerine getirmenin birden fazla yolu var, ancak eğer OP grep kullanımı istiyorsa, neden başka birşeye cevap veriyorsun? Ayrıca, ilk paragrafınız yanlış: evet grep yapabilir.
fcm

32
    sed -n "s/^.*foobar\s*\(\S*\).*$/\1/p"

-n     suppress printing
s      substitute
^.*    anything before foobar
foobar initial search match
\s*    any white space character (space)
\(     start capture group
\S*    capture any non-white space character (word)
\)     end capture group
.*$    anything after the capture group
\1     substitute everything with the 1st capture group
p      print it

1
Sed örneği için +1, iş için grep'ten daha iyi bir araç gibi görünüyor. Bir yorum, ^ve açgözlü bir maç $olduğundan beri yabancı .*. Ancak, bunlar dahil olmak, regex'in amacını netleştirmeye yardımcı olabilir.
Tony,

18

Foobar'ın her zaman ilk kelime ya da satır olduğunu biliyorsanız, kesimi kullanabilirsiniz. Bunun gibi:

grep "foobar" test.file | cut -d" " -f2

-oGrep üzerinde anahtarı yaygın bunu yaparken, (moreso Gnu grep uzantıları yerine) uygulanmaktadır grep -o "foobar" test.file | cut -d" " -f2, geriye bakan iddialarını kullanmaktan daha taşınabilir bu çözümün, etkinliğini artıracaktır.
dubiousjim

İhtiyacınız olacağına inanıyorum grep -o "foobar .*"ya da grep -o "foobar \w+".
G-Man

9

PCRE desteklenmiyorsa, iki grep çağrısı ile aynı sonucu elde edebilirsiniz. Örneğin, foobar'dan sonra kelimeyi kapmak için şunu yapın :

<test.txt grep -o 'foobar  *[^ ]*' | grep -o '[^ ]*$'

Bu sonra keyfi bir kelimeye genişletilebilir filanca (okunabilmesi için Eres ile) böyle:

i=1
<test.txt egrep -o 'foobar +([^ ]+ +){'$i'}[^ ]+' | grep -o '[^ ]*$'

Çıktı:

1

Dizinin isıfır temelli olduğuna dikkat edin.


6

pcregrep-oHangi yakalama grubunu almak istediğinizi seçmenize izin veren daha akıllı bir seçeneğe sahiptir. Yani, örnek dosyanızı kullanarak

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy

4

Kullanılması grepberi, çapraz platform uyumlu değildir -P/ --perl-regexpüzerinde yalnızca GNUgrep değil BSDgrep .

İşte kullanarak çözüm ripgrep:

$ rg -o "foobar (\w+)" -r '$1' <test.txt
bash
happy

Başına man rg:

-r/ --replace REPLACEMENT_TEXTHer eşleşmeyi verilen metinle değiştir.

Yakalama grubu dizinleri (örn. $5) Ve adları (örn. $foo) Değiştirme dizesinde desteklenir.

İlgili: GH-462 .


2

Jgshawkey'nin cevabını çok faydalı buldum. grepbunun için iyi bir araç değil, ama sed, burada burda ilgili bir çizgiyi yakalamak için grep kullanan bir örneğimiz olmasına rağmen.

Sed'nin Regex sözdizimi, buna alışık değilseniz, kendine özgüdür.

İşte bir başka örnek: bu bir ID tamsayısı almak için xinput çıktısını ayrıştırır

⎜   ↳ SynPS/2 Synaptics TouchPad                id=19   [slave  pointer  (2)]

ve 19 istiyorum

export TouchPadID=$(xinput | grep 'TouchPad' | sed  -n "s/^.*id=\([[:digit:]]\+\).*$/\1/p")

Sınıf sözdizimini not edin:

[[:digit:]]

ve aşağıdakilerden kaçma ihtiyacı +

Sadece bir satırın eşleştiğini farz ediyorum.


Bu tam olarak yapmaya çalıştığım şeydi. Teşekkürler!
James, 0

grep'TouchPad'in' id'nin solunda olduğunu farz edersek , ekstra olmadan biraz daha basit versiyon :echo "SynPS/2 Synaptics TouchPad id=19 [slave pointer (2)]" | sed -nE "s/.*TouchPad.+id=([0-9]+).*/\1/p"
Amit Naidu
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.