Bisakah grep output hanya pengelompokan tertentu yang cocok?


293

Katakanlah saya punya file:

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

Saya hanya ingin tahu kata-kata apa yang muncul setelah "foobar", jadi saya bisa menggunakan regex ini:

"foobar \(\w\+\)"

Tanda kurung menunjukkan bahwa saya memiliki minat khusus pada kata setelah foobar. Tetapi ketika saya melakukan grep "foobar \(\w\+\)" test.txt, saya mendapatkan seluruh baris yang cocok dengan seluruh regex, daripada hanya "kata setelah foobar":

foobar bash 1
foobar happy

Saya lebih suka output dari perintah itu terlihat seperti ini:

bash
happy

Apakah ada cara untuk memberi tahu grep agar hanya menampilkan item yang cocok dengan pengelompokan (atau pengelompokan tertentu) dalam ekspresi reguler?


4
bagi mereka yang tidak perlu grep:perl -lne 'print $1 if /foobar (\w+)/' < test.txt
lemari besi

Jawaban:


327

GNU grep memiliki -Popsi untuk reg-style perl, dan -oopsi untuk hanya mencetak apa yang cocok dengan polanya. Ini dapat digabungkan dengan menggunakan pernyataan melihat-lihat (dijelaskan dalam Pola Diperluas dalam halaman perlre ) untuk menghapus bagian dari pola grep dari apa yang ditentukan telah cocok untuk keperluan -o.

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

Ini \Kadalah bentuk pendek (dan bentuk yang lebih efisien) (?<=pattern)yang Anda gunakan sebagai pernyataan melihat ke belakang lebar nol sebelum teks yang ingin Anda hasilkan. (?=pattern)dapat digunakan sebagai pernyataan melihat ke depan dengan lebar nol setelah teks yang ingin Anda hasilkan.

Misalnya, jika Anda ingin mencocokkan kata antara foodan bar, Anda dapat menggunakan:

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

atau (untuk simetri)

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

3
Bagaimana Anda melakukannya jika regex Anda memiliki lebih dari satu grup? (seperti judulnya tersirat?)
barracel

4
@ Barracel: Saya tidak percaya Anda bisa. Saatnyased(1)
camh

1
@camh Saya baru saja menguji bahwa grep -oP 'foobar \K\w+' test.txttidak menghasilkan apa-apa dengan OP test.txt. Versi grep adalah 2.5.1. Apa yang salah? O_O
SOUser

@XichenLi: Saya tidak bisa mengatakannya. Saya baru saja membangun v2.5.1 dari grep (sudah cukup tua - sejak 2006) dan itu berhasil untuk saya.
camh

@ SOUser: Saya mengalami hal yang sama - tidak menampilkan apa pun ke file. Saya mengirimkan permintaan edit untuk menyertakan '>' sebelum nama file untuk mengirim output karena ini berhasil bagi saya.
rjchicago

39

Grep standar tidak dapat melakukan ini, tetapi versi terbaru dari GNU grep bisa . Anda dapat beralih ke sed, awk atau perl. Berikut adalah beberapa contoh yang melakukan apa yang Anda inginkan pada input sampel Anda; mereka berperilaku sedikit berbeda dalam kasus sudut.

Ganti foobar word other stuffdengan word, cetak hanya jika penggantian dilakukan.

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

Jika kata pertama adalah foobar, cetak kata kedua.

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

Lepas foobarjika itu kata pertama, dan lewati saja; kemudian strip semua setelah spasi putih dan cetak.

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

Luar biasa! Saya pikir saya mungkin bisa melakukan ini dengan sed, tapi saya belum pernah menggunakannya sebelumnya dan berharap saya bisa menggunakan familiarku grep. Tapi sintaks untuk perintah-perintah ini sebenarnya terlihat sangat akrab sekarang karena saya sudah terbiasa dengan pencarian gaya vim & ganti + regex. Terima kasih banyak.
Cory Klein

1
Tidak benar, Gilles. Lihat jawaban saya untuk solusi grep GNU.
camh

1
@camh: Ah, saya tidak tahu GNU grep sekarang memiliki dukungan PCRE penuh. Saya sudah memperbaiki jawaban saya, terima kasih.
Gilles

1
Jawaban ini sangat berguna untuk Linux tertanam karena Busybox greptidak memiliki dukungan PCRE.
Craig McQueen

Jelas ada beberapa cara untuk menyelesaikan tugas yang sama, namun, jika OP meminta penggunaan grep, mengapa Anda menjawab sesuatu yang lain? Juga, paragraf pertama Anda salah: ya grep bisa melakukannya.
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
+1 untuk contoh sed, sepertinya alat yang lebih baik untuk pekerjaan daripada grep. Satu komentar, ^dan $karena itu .*adalah pertandingan serakah. Namun, termasuk mereka dapat membantu memperjelas maksud regex.
Tony

18

Nah, jika Anda tahu bahwa foobar selalu merupakan kata atau baris pertama, maka Anda dapat menggunakan cut. Seperti itu:

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

The -oswitch on grep secara luas diterapkan (lebih dari ekstensi grep Gnu), demikian grep -o "foobar" test.file | cut -d" " -f2akan meningkatkan efektivitas dari solusi ini, yang lebih portabel daripada menggunakan pernyataan lookbehind.
dubiousjim

Saya percaya bahwa Anda akan membutuhkan grep -o "foobar .*"atau grep -o "foobar \w+".
G-Man

9

Jika PCRE tidak didukung, Anda dapat mencapai hasil yang sama dengan dua pemanggilan grep. Misalnya untuk mengambil kata setelah foobar lakukan ini:

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

Ini dapat diperluas ke kata arbitrer setelah foobar seperti ini (dengan ERE agar mudah dibaca):

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

Keluaran:

1

Perhatikan indeks iberbasis nol.


6

pcregrepmemiliki -oopsi yang lebih cerdas yang memungkinkan Anda memilih grup menangkap mana yang Anda inginkan. Jadi, menggunakan file contoh Anda,

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

4

Menggunakan greptidak kompatibel lintas platform, karena -P/ --perl-regexphanya tersedia di GNUgrep , bukan BSDgrep .

Inilah solusinya menggunakan ripgrep:

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

Sesuai man rg:

-r/ --replace REPLACEMENT_TEXTGanti setiap kecocokan dengan teks yang diberikan.

Indeks grup pengambilan (misalnya, $5) dan nama (misalnya, $foo) didukung dalam string pengganti.

Terkait: GH-462 .


2

Saya menemukan jawaban @jgshawkey sangat membantu. grepbukan alat yang baik untuk ini, tetapi sed adalah, meskipun di sini kita memiliki contoh yang menggunakan grep untuk mengambil garis yang relevan.

Sintaks regex dari sed adalah istimewa jika Anda tidak terbiasa.

Berikut adalah contoh lain: yang ini mem-parsing output xinput untuk mendapatkan ID integer

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

dan saya ingin 19

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

Perhatikan sintaks kelas:

[[:digit:]]

dan kebutuhan untuk melarikan diri dari yang berikut +

Saya menganggap hanya satu baris yang cocok.


Ini persis apa yang saya coba lakukan. Terima kasih!
James

Versi yang sedikit lebih sederhana tanpa tambahan grep, dengan anggapan 'TouchPad' ada di sebelah kiri 'id':echo "SynPS/2 Synaptics TouchPad id=19 [slave pointer (2)]" | sed -nE "s/.*TouchPad.+id=([0-9]+).*/\1/p"
Amit Naidu
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.