it-swarm-id.com

Bisakah grep output hanya pengelompokan tertentu yang cocok?

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 Firman setelah foobar. Tetapi ketika saya melakukan grep "foobar \(\w\+\)" test.txt, saya mendapatkan seluruh baris yang cocok dengan seluruh regex, bukan hanya "Kata setelah foobar":

foobar bash 1
foobar happy

Saya lebih suka bahwa output dari perintah itu terlihat seperti ini:

bash
happy

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

338
Cory Klein

GNU grep memiliki -P opsi untuk regex gaya Perl, dan -o pilihan untuk mencetak hanya yang cocok dengan polanya. Ini dapat digabungkan dengan menggunakan pernyataan melihat-lihat (dijelaskan di bawah Pola yang 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
$

\K adalah bentuk pendek (dan bentuk yang lebih efisien) dari (?<=pattern) yang Anda gunakan sebagai pernyataan melihat ke belakang nol lebar sebelum teks yang ingin Anda hasilkan. (?=pattern) dapat digunakan sebagai pernyataan lihat-depan lebar nol setelah teks yang ingin Anda hasilkan.

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

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

atau (untuk simetri)

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

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

Ganti foobar Word other stuff oleh 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}'

Lepaskan foobar jika itu adalah kata pertama, dan lewati saja; kemudian strip semua setelah spasi putih dan cetak.

Perl -lne 's/^foobar\s+// or next; s/\s.*//; print'
    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
46
jgshawkey

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

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

pcregrep memiliki yang lebih cerdas -o opsi yang memungkinkan Anda memilih grup mana yang ingin Anda hasilkan. Jadi, menggunakan file contoh Anda,

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

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

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

Ini dapat diperluas ke Word yang sewenang-wenang setelah foobar seperti ini (dengan ERE agar mudah dibaca):

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

Keluaran:

1

Perhatikan indeks i berbasis nol.

9
Thor

Menggunakan grep tidak kompatibel lintas platform, karena -P/--Perl-regexp hanya tersedia di GNU grep , bukan BSD grep .

Ini solusinya menggunakan ripgrep :

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

Sesuai man rg:

-r/--replace REPLACEMENT_TEXT Ganti setiap kecocokan dengan teks yang diberikan.

Menangkap indeks grup (mis., $5) dan nama (mis., $foo) didukung dalam string pengganti.

Terkait: GH-462 .

7
kenorb

Saya menemukan jawaban @jgshawkey sangat membantu. grep bukan alat yang bagus untuk ini, tetapi sebenarnya, meskipun di sini kami memiliki contoh yang menggunakan grep untuk mengambil baris yang relevan.

Sintaks regex 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 + berikut ini

Saya berasumsi hanya satu baris yang cocok.

2
Tim Richardson