Bagaimana menemukan pola di beberapa garis menggunakan grep?

208

Saya ingin mencari file yang memiliki "abc" DAN "efg" dalam urutan itu, dan kedua string berada pada baris yang berbeda dalam file itu. Misalnya: file dengan konten:

blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..

Harus dicocokkan.

Saobi
sumber
4
kemungkinan duplikat dari Bagaimana saya bisa mencari pola multiline dalam file?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

225

Grep tidak cukup untuk operasi ini.

pcregrep yang ditemukan di sebagian besar sistem Linux modern dapat digunakan sebagai

pcregrep -M  'abc.*(\n|.)*efg' test.txt

di mana -M, --multiline izinkan pola untuk mencocokkan lebih dari satu baris

Ada pcre2grep yang lebih baru juga. Keduanya disediakan oleh proyek PCRE .

pcre2grep tersedia untuk Mac OS X melalui Mac Ports sebagai bagian dari port pcre2:

% sudo port install pcre2 

dan melalui Homebrew sebagai:

% brew install pcre

atau untuk pcre2

% brew install pcre2

pcre2grep juga tersedia di Linux (Ubuntu 18.04+)

$ sudo apt install pcre2-utils # PCRE2
$ sudo apt install pcregrep    # Older PCRE
Pembawa cincin
sumber
11
@StevenLu -M, --multiline- Mengizinkan pola cocok dengan lebih dari satu baris.
pembawa cincin
7
Perhatikan bahwa. * (\ N |.) * Setara dengan (\ n |.) * Dan yang terakhir lebih pendek. Terlebih lagi pada sistem saya, "pcre_exec () error -8" terjadi ketika saya menjalankan versi yang lebih panjang. Jadi cobalah 'abc (\ n |.) * Efg' sebagai gantinya!
daveagp
6
Anda perlu membuat ekspresi non-serakah dalam contoh kasus:'abc.*(\n|.)*?efg'
pembawa cincin
4
dan Anda dapat menghilangkan yang pertama .*-> 'abc(\n|.)*?efg'untuk membuat regex lebih pendek (dan menjadi bertele-tele)
Michi
6
pcregrepmemang membuat segalanya lebih mudah, tetapi grepakan bekerja juga. Misalnya, lihat stackoverflow.com/a/7167115/123695
Michael Mior
113

Saya tidak yakin apakah itu mungkin dengan grep, tetapi sed membuatnya sangat mudah:

sed -e '/abc/,/efg/!d' [file-with-content]
LJ.
sumber
4
Ini tidak menemukan file, itu mengembalikan bagian yang cocok dari satu file
shiggity
11
@ Lj. tolong bisakah Anda menjelaskan perintah ini? Saya kenal sed, tetapi jika belum pernah melihat ekspresi seperti itu sebelumnya.
Anthony
1
@Anthony, Ini didokumentasikan di halaman manual sed, di bawah alamat. Penting untuk menyadari bahwa / abc / & / efg / adalah alamat.
Squidly
49
Saya menduga jawaban ini akan sangat membantu jika memiliki sedikit penjelasan, dan dalam hal ini, saya akan memilihnya sekali lagi. Saya tahu sedikit sed, tetapi tidak cukup untuk menggunakan jawaban ini untuk menghasilkan kode keluar yang berarti setelah setengah jam mengutak-atik. Kiat: 'RTFM' jarang mendapat suara di StackOverflow, seperti yang diperlihatkan oleh komentar Anda sebelumnya.
Michael Scheper
25
Penjelasan cepat dengan contoh: sed '1,5d': hapus baris antara 1 dan 5. sed '1,5! D': hapus baris yang tidak di antara 1 dan 5 (yaitu pertahankan garis di antara) lalu alih-alih angka, Anda dapat cari baris dengan / pattern /. Lihat juga yang lebih sederhana di bawah ini: sed -n '/ abc /, / efg / p' p adalah untuk cetak dan flag -n tidak menampilkan semua baris
phil_w
86

Berikut adalah solusi yang terinspirasi oleh jawaban ini :

  • jika 'abc' dan 'efg' dapat berada di baris yang sama:

    grep -zl 'abc.*efg' <your list of files>
  • jika 'abc' dan 'efg' harus berada di baris yang berbeda:

    grep -Pzl '(?s)abc.*\n.*efg' <your list of files>

Params:

  • -zPerlakukan input sebagai satu set garis, masing-masing diakhiri dengan nol byte, bukan baris baru. yaitu grep memperlakukan input sebagai satu garis besar.

  • -l nama cetak dari setiap file input dari mana output biasanya akan dicetak.

  • (?s)aktifkan PCRE_DOTALL, yang berarti '.' menemukan karakter atau baris baru.

atti
sumber
@syntaxerror Tidak, saya pikir ini hanya huruf kecil l. AFAIK tidak ada -1opsi nomor .
Sparhawk
Sepertinya Anda benar, mungkin saya telah membuat kesalahan ketik saat pengujian. Dalam kasus apa pun maaf karena meletakkan jejak palsu.
syntaxerror
6
Ini luar biasa. Saya hanya punya satu pertanyaan tentang ini. Jika -zopsi menentukan grep untuk memperlakukan baris baru zero byte characterslalu mengapa kita perlu (?s)di regex? Jika sudah menjadi karakter non-baris baru, bukankah .harus dapat mencocokkannya secara langsung?
Durga Swaroop
1
-z (alias --null-data) dan (? s) adalah persis apa yang Anda butuhkan untuk mencocokkan multi-line dengan grep standar. Orang-orang di MacOS, silakan tinggalkan komentar tentang ketersediaan opsi -z atau --null-data di sistem Anda!
Zeke Fast
4
-z pasti tidak tersedia di MacOS
Dylan Nicholson
33

sed harus cukup sebagai poster LJ yang disebutkan di atas,

alih-alih! d Anda cukup menggunakan p untuk mencetak:

sed -n '/abc/,/efg/p' file
Kara
sumber
16

Saya sangat mengandalkan pcregrep, tetapi dengan grep yang lebih baru Anda tidak perlu menginstal pcregrep untuk banyak fitur-fiturnya. Gunakan saja grep -P.

Dalam contoh pertanyaan OP, saya pikir opsi berikut ini berfungsi dengan baik, dengan yang terbaik kedua mencocokkan dengan bagaimana saya memahami pertanyaan:

grep -Pzo "abc(.|\n)*efg" /tmp/tes*
grep -Pzl "abc(.|\n)*efg" /tmp/tes*

Saya menyalin teks sebagai / tmp / test1 dan menghapus 'g' dan disimpan sebagai / tmp / test2. Berikut adalah output yang menunjukkan bahwa yang pertama menunjukkan string yang cocok dan yang kedua hanya menunjukkan nama file (khas -o adalah untuk menunjukkan kecocokan dan -l khas adalah hanya menampilkan nama file). Perhatikan bahwa 'z' diperlukan untuk multiline dan '(. | \ N)' berarti untuk mencocokkan 'apa pun selain baris baru' atau 'baris baru' - yaitu apa pun:

user@host:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes*
/tmp/test1:abc blah
blah blah..
blah blah..
blah blah..
blah efg
user@host:~$ grep -Pzl "abc(.|\n)*efg" /tmp/tes*
/tmp/test1

Untuk menentukan apakah versi Anda cukup baru, jalankan man grepdan lihat apakah sesuatu yang mirip dengan ini muncul di dekat bagian atas:

   -P, --perl-regexp
          Interpret  PATTERN  as a Perl regular expression (PCRE, see
          below).  This is highly experimental and grep -P may warn of
          unimplemented features.

Itu dari GNU grep 2.10.

Sage
sumber
14

Ini dapat dilakukan dengan mudah dengan terlebih dahulu menggunakan truntuk mengganti baris baru dengan beberapa karakter lain:

tr '\n' '\a' | grep -o 'abc.*def' | tr '\a' '\n'

Di sini, saya menggunakan karakter alarm, \a(ASCII 7) sebagai pengganti baris baru. Ini hampir tidak pernah ditemukan dalam teks Anda, dan grepdapat mencocokkannya dengan ., atau mencocokkannya dengan \a.

Gavin S. Yancey
sumber
1
Ini adalah pendekatan saya, tetapi saya menggunakan \0dan karenanya diperlukan grep -adan cocok pada \x00... Anda telah membantu saya menyederhanakan! echo $log | tr '\n' '\0' | grep -aoE "Error: .*?\x00Installing .*? has failed\!" | tr '\0' '\n'sekarangecho $log | tr '\n' '\a' | grep -oE "Error: .*?\aInstalling .*? has failed\!" | tr '\a' '\n'
Charlie Gorichanaz
1
Gunakan grep -o.
kyb
7

awk one-liner:

awk '/abc/,/efg/' [file-with-content]
Swynndla
sumber
4
Ini dengan senang hati akan mencetak dari abchingga akhir file jika pola akhir tidak ada dalam file, atau pola akhir terakhir tidak ada. Anda dapat memperbaikinya tetapi itu akan menyulitkan skrip secara signifikan.
tripleee
Bagaimana cara mengecualikan /efg/dari output?
kyb
6

Anda dapat melakukannya dengan sangat mudah jika Anda dapat menggunakan Perl.

perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt

Anda bisa melakukannya dengan ekspresi reguler tunggal juga, tetapi itu melibatkan mengambil seluruh isi file menjadi string tunggal, yang mungkin berakhir dengan mengambil terlalu banyak memori dengan file besar. Untuk kelengkapan, berikut adalah metode itu:

perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt
sundar - Pasang kembali Monica
sumber
Ditemukan jawaban kedua berguna untuk mengekstrak seluruh blok multi-baris dengan kecocokan pada beberapa baris - harus menggunakan pencocokan non-serakah ( .*?) untuk mendapatkan kecocokan minimal.
RichVel
5

Saya tidak tahu bagaimana saya akan melakukannya dengan grep, tetapi saya akan melakukan sesuatu seperti ini dengan awk:

awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo

Anda perlu berhati-hati dalam melakukannya. Apakah Anda ingin regex cocok dengan substring atau seluruh kata? tambahkan tag yang sesuai. Juga, sementara ini benar-benar sesuai dengan bagaimana Anda menyatakan contoh, itu tidak berfungsi ketika abc muncul kedua kalinya setelah efg. Jika Anda ingin mengatasinya, tambahkan if jika sesuai dalam / abc / case, dll.

frankc
sumber
3

Sayangnya, kamu tidak bisa. Dari grepdokumen:

grep mencari nama input FILE (atau input standar jika tidak ada file yang dinamai, atau jika tanda hubung minus tunggal (-) diberikan sebagai nama file) untuk baris yang berisi kecocokan dengan POLA yang diberikan.

Kaleb Pederson
sumber
bagaimana dengangrep -Pz
Navaro
3

Jika Anda ingin menggunakan konteks, ini bisa dicapai dengan mengetik

grep -A 500 abc test.txt | grep -B 500 efg

Ini akan menampilkan semuanya antara "abc" dan "efg", selama mereka berada dalam 500 baris satu sama lain.

Agouge
sumber
3

Jika Anda membutuhkan kedua kata tersebut saling berdekatan, misalnya tidak lebih dari 3 baris, Anda dapat melakukan ini:

find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Contoh yang sama tetapi hanya memfilter file * .txt:

find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Dan juga Anda dapat mengganti grepperintah dengan egrepperintah jika Anda ingin juga menemukan dengan ekspresi reguler.

Mariano Ruiz
sumber
3

Saya merilis alternatif grep beberapa hari yang lalu yang mendukung hal ini secara langsung, baik melalui pencocokan multiline atau menggunakan kondisi - semoga bermanfaat bagi sebagian orang yang mencari di sini. Beginilah perintah untuk contoh akan terlihat:

Multiline:

sift -lm 'abc.*efg' testfile

Kondisi:

sift -l 'abc' testfile --followed-by 'efg'

Anda juga dapat menentukan bahwa 'efg' harus mengikuti 'abc' dalam sejumlah baris:

sift -l 'abc' testfile --followed-within 5:'efg'

Anda dapat menemukan informasi lebih lanjut di sift-tool.org .

svent
sumber
Saya tidak berpikir contoh pertama sift -lm 'abc.*efg' testfileberhasil, karena pertandingan serakah dan melahap semua baris sampai yang terakhir efgdalam file.
Dr. Alex RE
2

Sementara opsi sed adalah yang paling sederhana dan termudah, one-liner LJ sayangnya bukan yang paling portabel. Mereka yang terjebak dengan versi C Shell harus melarikan diri dari poni mereka:

sed -e '/abc/,/efg/\!d' [file]

Sayangnya ini tidak berfungsi di bash et al.

bug
sumber
1
#!/bin/bash
shopt -s nullglob
for file in *
do
 r=$(awk '/abc/{f=1}/efg/{g=1;exit}END{print g&&f ?1:0}' file)
 if [ "$r" -eq 1 ];then
   echo "Found pattern in $file"
 else
   echo "not found"
 fi
done
ghostdog74
sumber
1

Anda dapat menggunakan grep memetikan Anda tidak tertarik dalam urutan pola.

grep -l "pattern1" filepattern*.* | xargs grep "pattern2"

contoh

grep -l "vector" *.cpp | xargs grep "map"

grep -lakan menemukan semua file yang cocok dengan pola pertama, dan xargs akan memahami pola kedua. Semoga ini membantu.

Balu Mohan
sumber
1
Itu akan mengabaikan urutan "pattern1" dan "pattern2" muncul di file, meskipun - OP secara khusus menentukan bahwa hanya file di mana "pattern2" muncul SETELAH "pattern1" harus dicocokkan.
Emil Lundberg
1

Dengan pencari perak :

ag 'abc.*(\n|.)*efg'

mirip dengan jawaban pembawa cincin, tetapi dengan ag sebagai gantinya. Keuntungan kecepatan pencari perak mungkin bisa bersinar di sini.

Shwaydogg
sumber
1
Ini sepertinya tidak berhasil. (echo abctest; echo efg)|ag 'abc.*(\n|.)*efg'tidak cocok
phiresky
1

Saya menggunakan ini untuk mengekstrak urutan fasta dari file multi fasta menggunakan opsi -P untuk grep:

grep -Pzo ">tig00000034[^>]+"  file.fasta > desired_sequence.fasta
  • P untuk pencarian berdasarkan perl
  • z untuk membuat akhir baris dalam 0 byte daripada char baris baru
  • o untuk hanya menangkap apa yang cocok sejak grep mengembalikan seluruh baris (yang dalam hal ini sejak Anda lakukan -z adalah seluruh file).

Inti dari regexp adalah [^>]yang diterjemahkan menjadi "tidak lebih besar dari simbol"

Jon Boyle
sumber
0

Sebagai alternatif dari jawaban Balu Mohan, dimungkinkan untuk menegakkan urutan pola hanya menggunakan grep, headdan tail:

for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep "pattern2" &>/dev/null && echo $f; done

Yang ini tidak terlalu cantik. Diformat lebih mudah:

for f in FILEGLOB; do
    tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null \
    | grep -q "pattern2" \
    && echo $f
done

Ini akan mencetak nama semua file di mana "pattern2"muncul setelah "pattern1", atau di mana keduanya muncul pada baris yang sama :

$ echo "abc
def" > a.txt
$ echo "def
abc" > b.txt
$ echo "abcdef" > c.txt; echo "defabc" > d.txt
$ for f in *.txt; do tail $f -n +$(grep -n "abc" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep -q "def" && echo $f; done
a.txt
c.txt
d.txt

Penjelasan

  • tail -n +i - cetak semua baris setelah i , inklusif
  • grep -n - baris yang cocok yang diawali dengan nomor baris mereka
  • head -n1 - hanya mencetak baris pertama
  • cut -d : -f 1 - cetak kolom potongan pertama menggunakan : sebagai pembatas
  • 2>/dev/null- tailoutput kesalahan diam yang terjadi jika$() ekspresi kembali kosong
  • grep -q- diam grepdan kembali segera jika kecocokan ditemukan, karena kami hanya tertarik pada kode keluar
Emil Lundberg
sumber
Adakah yang bisa menjelaskannya &>? Saya juga menggunakannya, tetapi saya tidak pernah melihatnya didokumentasikan di mana pun. BTW, mengapa kita harus membungkam grep seperti itu, sebenarnya? grep -qtidak akan melakukan trik juga?
syntaxerror
1
&>memberitahu bash untuk mengarahkan ulang output standar dan kesalahan standar, lihat REDIRECTION di manual bash. Anda sangat benar karena kita bisa melakukannya dengan baik grep -q ...daripada grep ... &>/dev/null, tangkapan yang bagus!
Emil Lundberg
Berpikir begitu. Akan menghilangkan rasa sakit banyak mengetik ekstra canggung. Terima kasih atas penjelasannya - jadi saya harus melewatkan sedikit di manual. (Mencari sesuatu yang terkait di dalamnya beberapa waktu lalu.) --- --- Anda bahkan mungkin mempertimbangkan untuk mengubahnya dalam jawaban Anda. :)
syntaxerror
0

Ini juga harus bekerja ?!

perl -lpne 'print $ARGV if /abc.*?efg/s' file_list

$ARGVberisi nama file saat ini ketika membaca dari file_list /spencarian pengubah di baris baru.

PS12
sumber
0

Filepattern *.shpenting untuk mencegah direktori diperiksa. Tentu saja beberapa tes bisa mencegahnya juga.

for f in *.sh
do
  a=$( grep -n -m1 abc $f )
  test -n "${a}" && z=$( grep -n efg $f | tail -n 1) || continue 
  (( ((${z/:*/}-${a/:*/})) > 0 )) && echo $f
done

Itu

grep -n -m1 abc $f 

mencari maksimum 1 yang cocok dan mengembalikan (-n) linenumber. Jika kecocokan ditemukan (tes -n ...) cari kecocokan terakhir dari efg (temukan semua dan ambil yang terakhir dengan ekor -n 1).

z=$( grep -n efg $f | tail -n 1)

lain melanjutkan.

Karena hasilnya adalah sesuatu seperti 18:foofile.sh String alf="abc";kita perlu memotong dari ":" hingga akhir baris.

((${z/:*/}-${a/:*/}))

Harus mengembalikan hasil positif jika pertandingan terakhir dari ekspresi 2 melewati pertandingan pertama yang pertama.

Lalu kami melaporkan nama file echo $f.

Pengguna tidak diketahui
sumber
0

Mengapa bukan sesuatu yang sederhana seperti:

egrep -o 'abc|efg' $file | grep -A1 abc | grep efg | wc -l

mengembalikan 0 atau bilangan bulat positif.

egrep -o (Hanya menampilkan kecocokan, trik: beberapa kecocokan pada baris yang sama menghasilkan output multi-baris seolah-olah mereka berada pada baris yang berbeda)

  • grep -A1 abc (cetak abc dan garis setelahnya)

  • grep efg | wc -l (Hitungan 0-n dari garis efg ditemukan setelah abc pada baris yang sama atau mengikuti, hasilnya dapat digunakan dalam 'jika ")

  • grep dapat diubah menjadi egrep dll. jika diperlukan pencocokan pola

Kevin
sumber
0

Jika Anda memiliki beberapa perkiraan tentang jarak antara 2 string 'abc' dan 'efg' yang Anda cari, Anda dapat menggunakan:

grep -r . -e 'abc' -A num1 -B num2 | grep 'efg'

Dengan begitu, grep pertama akan mengembalikan baris dengan baris 'abc' ditambah # num1 setelahnya, dan # num2 baris setelahnya, dan grep kedua akan menyaring semua yang ada untuk mendapatkan 'efg'. Maka Anda akan tahu di mana file mereka muncul bersama.

Benjamin Berend
sumber
0

Dengan ugrep dirilis beberapa bulan lalu:

ugrep 'abc(\n|.)+?efg'

Alat ini sangat dioptimalkan untuk kecepatan. Ini juga kompatibel dengan GNU / BSD / PCRE-grep.

Perhatikan bahwa kita harus menggunakan pengulangan yang malas +?, kecuali jika Anda ingin mencocokkan semua baris efgbersama sampai yang terakhir efgdalam file.

Alex RE
sumber
-3

Ini seharusnya bekerja:

cat FILE | egrep 'abc|efg'

Jika ada lebih dari satu kecocokan, Anda dapat memfilter menggunakan grep -v

Guru
sumber
2
Sementara cuplikan kode ini diterima, dan dapat memberikan bantuan, akan sangat ditingkatkan jika menyertakan penjelasan tentang bagaimana dan mengapa ini menyelesaikan masalah. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, bukan hanya orang yang bertanya sekarang! Harap edit jawaban Anda untuk menambahkan penjelasan, dan berikan indikasi batasan dan asumsi apa yang berlaku.
Toby Speight
1
Itu sebenarnya tidak mencari di beberapa baris , seperti yang dinyatakan dalam pertanyaan.
n.st