Bagaimana saya bisa "memahami" pola di beberapa baris?


24

Sepertinya saya menyalahgunakan grep/ egrep.

Saya mencoba mencari string dalam beberapa baris dan tidak dapat menemukan kecocokan sementara saya tahu bahwa apa yang saya cari harus cocok. Awalnya saya berpikir bahwa regex saya salah tetapi pada akhirnya saya membaca bahwa alat ini beroperasi per baris (juga regex saya sangat sepele sehingga tidak mungkin menjadi masalah).

Jadi alat mana yang akan digunakan untuk mencari pola di beberapa baris?



1
@CiroSantilli - Saya tidak berpikir bahwa Q ini dan yang Anda tautkan adalah duplikat. Q lainnya bertanya bagaimana Anda akan melakukan pencocokan pola multi-garis (yaitu alat apa yang harus / dapat saya gunakan untuk melakukan ini) sementara yang satu ini bertanya bagaimana melakukan ini dengan grep. Mereka terkait erat tetapi bukan dups, IMO.
slm

@sim kasus-kasus itu sulit untuk diputuskan: Saya bisa mengerti maksud Anda. Saya pikir kasus khusus ini lebih baik sebagai duplikat karena pengguna mengatakan "grep"menyarankan kata kerja "to grep", dan jawaban teratas, termasuk diterima, jangan gunakan grep.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Jawaban:


24

Berikut ini sedsalah satu yang akan memberi Anda grepperilaku seperti melintasi beberapa baris:

sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file

Bagaimana itu bekerja

  • -n menekan perilaku standar pencetakan setiap baris
  • /foo/{}menginstruksikannya untuk mencocokkan foodan melakukan apa yang ada di dalam squigglies ke garis yang cocok. Ganti foodengan bagian awal dari pola.
  • :start adalah label percabangan untuk membantu kami tetap terhubung sampai kami menemukan akhir dari regex kami.
  • /bar/!{}akan mengeksekusi apa yang ada di squigglies ke baris yang tidak cocok bar. Ganti bardengan bagian akhir dari pola.
  • Nmenambahkan baris berikutnya ke buffer aktif ( sedmenyebutnya ruang pola)
  • b startakan bercabang tanpa syarat ke startlabel yang kita buat sebelumnya agar tetap menambahkan baris berikutnya selama ruang pola tidak mengandung bar.
  • /your_regex/pmencetak ruang pola jika cocok your_regex. Anda harus mengganti your_regexdengan seluruh ekspresi yang ingin Anda cocokkan di beberapa baris.

1
+1 Menambahkan ini ke toolikt! Terima kasih.
wmorrison365

Catatan: Pada MacOS ini memberikansed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James

1
Mendapatkan sed: unterminated {kesalahan
tanggal

@Tidak Ditembak dalam gelap di sini, tetapi apakah regex Anda mengandung karakter "{"? Jika demikian, Anda harus melarikan diri dari backslash.
Joseph R.

1
@Nomaed Tampaknya ada hubungannya dengan perbedaan antara sedimplementasi. Saya mencoba mengikuti rekomendasi dalam jawaban itu untuk membuat skrip di atas memenuhi standar tetapi mengatakan kepada saya bahwa "mulai" adalah label yang tidak ditentukan. Jadi saya tidak yakin apakah ini dapat dilakukan dengan cara yang sesuai standar. Jika Anda mengelolanya, silakan edit jawaban saya.
Joseph R.

19

Saya biasanya menggunakan alat yang disebut pcregrepyang dapat diinstal di sebagian besar rasa linux menggunakan yumatau apt.

Untuk misalnya.

Misalkan jika Anda memiliki file yang dinamai testfiledengan konten

abc blah
blah blah
def blah
blah blah

Anda dapat menjalankan perintah berikut:

$ pcregrep -M  'abc.*(\n|.)*def' testfile

untuk melakukan pencocokan pola di beberapa baris.

Selain itu, Anda dapat melakukan hal yang sama dengannya sed.

$ sed -e '/abc/,/def/!d' testfile

5

Berikut ini pendekatan yang lebih sederhana menggunakan Perl:

perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file

atau (karena JosephR mengambil sedrute , saya akan tanpa malu mencuri sarannya )

perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file

Penjelasan

$f=join("",<>);: ini membaca seluruh file dan menyimpan kontennya (baris baru dan semua) ke dalam variabel $f. Kami kemudian mencoba untuk mencocokkan foo\nbar.*\n, dan mencetaknya jika cocok (variabel khusus $&memegang kecocokan terakhir yang ditemukan). The ///mdiperlukan untuk membuat ekspresi pertandingan reguler di seluruh baris.

The -0menetapkan pemisah record masukan. Mengatur ini untuk 00mengaktifkan 'mode paragraf' di mana Perl akan menggunakan baris baru berurutan ( \n\n) sebagai pemisah rekaman. Dalam kasus di mana tidak ada baris baru berturut-turut, seluruh file dibaca (disedot) sekaligus.

Peringatan:

Jangan tidak melakukan ini untuk file besar, itu akan memuat seluruh file ke dalam memori dan yang mungkin menjadi masalah.


2

Salah satu cara untuk melakukan ini adalah dengan Perl. misal inilah isi file bernama foo:

foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6

Sekarang, inilah beberapa Perl yang akan cocok dengan setiap baris yang dimulai dengan foo diikuti oleh baris yang dimulai dengan bar:

cat foo | perl -e 'while(<>){$all .= $_}
  while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
  print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'

Perl, rusak:

  • while(<>){$all .= $_} Ini memuat seluruh input standar ke variabel $all
  • while($all =~Sementara variabel allmemiliki ekspresi reguler ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/mRegex: foo di awal baris, diikuti oleh sejumlah karakter non-baris baru, diikuti oleh baris baru, segera diikuti oleh "bar", dan sisa baris dengan bar di dalamnya. /mpada akhir regex berarti "cocok dengan banyak garis"
  • print $1 Cetak bagian dari regex yang ada dalam tanda kurung (dalam hal ini, seluruh ekspresi reguler)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Hapus kecocokan pertama untuk regex, sehingga kami dapat mencocokkan beberapa kasus regex dalam file yang dimaksud

Dan hasilnya:

foo line 1
bar line 2
foo
bar line 6

3
Hanya mampir untuk mengatakan Perl Anda dapat disingkat menjadi lebih idiomatik:perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Joseph R.

2

Alternatif grep sift mendukung pencocokan multiline (disclaimer: Saya penulis).

Misalkan testfilemengandung:

<buku>
  <title> Lorem Ipsum </title>
  [Deskripsi] Lorem ipsum dolor duduk amet, consectetur
  Adipiscing elit, dan lakukan sementara waktu juga
  labore dan dolore magna aliqua </description>
</book>


sift -m '<description>.*?</description>' (tampilkan garis yang berisi deskripsi)

Hasil:

testfile: <description> Lorem ipsum dolor sit amet, consectetur
testfile: adipiscing elite, dan lakukan temp temporididid
testfile: labore dan dolore magna aliqua </description>


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (ekstrak dan format ulang deskripsi)

Hasil:

deskripsi = "Lorem ipsum dolor sit amet, consectetur
  Adipiscing elit, dan lakukan sementara waktu juga
  labore dan dolore magna aliqua "

1
Alat yang sangat bagus. Selamat! Cobalah untuk memasukkannya dalam distribusi seperti Ubuntu.
Lourenco

2

Cukup grep normal yang mendukung Perl-regexpparameter Pakan melakukan pekerjaan ini.

$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz  '(?s)abc.*?def'
abc blah
blah blah
def

(?s) disebut pengubah DOTALL yang membuat titik di regex Anda agar tidak hanya cocok dengan karakter tetapi juga garis terputus.


Ketika saya mencoba solusi ini, output tidak berakhir di 'def' tetapi pergi ke akhir file 'bla'
buckley

mungkin grep Anda tidak mendukung -Popsi
Avinash Raj

1

Saya memecahkan ini untuk saya menggunakan opsi grep dan -A dengan grep lain.

grep first_line_word -A 1 testfile | grep second_line_word

Opsi -A 1 mencetak 1 baris setelah baris yang ditemukan. Tentu saja itu tergantung pada kombinasi file dan kata Anda. Tetapi bagi saya itu adalah solusi tercepat dan dapat diandalkan.


alias grepp = 'grep --color = otomatis -B10 -A20 -i' lalu cat somefile | grepp blah | grepp foo | grepp bar ... ya itu -A dan -B sangat berguna ... Anda punya jawaban terbaik
Scott Stensland

1

Misalkan kita memiliki file test.txt yang berisi:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

Kode berikut dapat digunakan:

sed -n '/foo/,/bar/p' test.txt

Untuk output berikut:

foo
here
is the
text
to keep between the 2 patterns
bar

1

Jika kita ingin mendapatkan teks di antara 2 pola tidak termasuk diri mereka sendiri.

Misalkan kita memiliki file test.txt yang berisi:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

Kode berikut dapat digunakan:

 sed -n '/foo/{
 n
 b gotoloop
 :loop
 N
 :gotoloop
 /bar/!{
 h
 b loop
 }
 /bar/{
 g
 p
 }
 }' test.txt

Untuk output berikut:

here
is the
text
to keep between the 2 patterns

Bagaimana cara kerjanya, mari kita buat langkah demi langkah

  1. /foo/{ dipicu ketika baris berisi "foo"
  2. n ganti spasi pola dengan baris berikutnya, yaitu kata "di sini"
  3. b gotoloop cabang ke label "gotoloop"
  4. :gotoloop mendefinisikan label "gotoloop"
  5. /bar/!{ jika polanya tidak mengandung "bar"
  6. h ganti ruang tunggu dengan pola, jadi "di sini" disimpan di ruang penyimpanan
  7. b loop cabang ke label "loop"
  8. :loop mendefinisikan label "loop"
  9. N menambahkan pola ke ruang palka.
    Sekarang tahan ruang berisi:
    "di sini"
    "adalah"
  10. :gotoloop Kita sekarang pada langkah 4, dan loop sampai satu baris berisi "bar"
  11. /bar/ loop selesai, "bar" telah ditemukan, itu adalah ruang pola
  12. g ruang pola diganti dengan ruang pegang yang berisi semua garis antara "foo" dan "bar" yang telah disimpan selama loop utama
  13. p salin ruang pola ke output standar

Selesai!


Bagus, +1. Saya biasanya menghindari menggunakan perintah ini dengan tr'ing baris baru ke SOH dan melakukan perintah sed normal kemudian ganti baris baru.
A.Danischewski
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.