Bagaimana cara mendapatkan semua garis antara kemunculan pola pertama dan terakhir?

8

Bagaimana saya bisa memotong file (aliran input dengan baik) sehingga saya hanya mendapatkan garis mulai dari kemunculan pola pertama hingga kemunculan pola footerakhir bar?

Sebagai contoh, pertimbangkan input berikut:

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

Saya mengharapkan hasil ini:

foo
this 
foo
bar
something
something else
foo
bar
rahmu
sumber
3
Single pass stream atau file? Ini jauh lebih mudah dilakukan ketika akses acak diizinkan. Dengan sebuah file Anda hanya akan menemukan yang pertama foodan terakhir bardan mencetak semuanya di antaranya, jika ada. Dengan stream Anda harus membaca sampai yang pertama foo, dan buffer semua baris berikutnya dalam memori sampai EOF, flushing buffer setiap kali a barterlihat. Ini bisa berarti buffering seluruh aliran dalam memori.
jw013

Jawaban:

6
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'

Pencocokan pola sed /first/,/second/membaca baris satu per satu. Ketika beberapa baris cocok dengan /first/itu akan mengingatnya dan berharap untuk pertandingan pertama untuk /second/pola. Pada saat yang sama berlaku semua aktivitas yang ditentukan untuk pola itu. Setelah itu proses mulai lagi dan lagi hingga akhir file.

Bukan itu yang kita butuhkan. Kita perlu melihat pencocokan /second/pola yang terakhir. Karena itu kami membangun konstruksi yang terlihat hanya untuk entri pertama /foo/. Ketika ditemukan siklus adimulai. Kami menambahkan baris baru ke buffer pencocokan dengan Ndan memeriksa apakah cocok dengan pola /bar/. Jika ya, kita cukup mencetaknya dan menghapus buffer pertandingan dan janyway jump ke awal siklus ba.

Kita juga perlu menghapus simbol baris baru setelah buffer dibersihkan /^\n/s/^\n//. Saya yakin ada solusi yang jauh lebih baik, sayangnya itu tidak muncul di benak saya.

Semoga semuanya jelas.

buru-buru
sumber
1
Berhasil! Akan sangat keren jika Anda bisa memandu kami melalui konstruksi perintah seperti itu. Aku akan merasa bodoh hanya menyalin / menempelkannya dari beberapa situs web online;)
rahmu
1
Maaf saya tidak memposting penjelasan dengan jawabannya. Sekarang ada di pos.
buru
Dalam beberapa sedversi misalnya BSD sed (yang ditemukan pada Mac), tag harus diikuti oleh baris baru atau akhir string, jadi diperlukan penyesuaian berikut: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' Ini juga berfungsi pada GNU sed, jadi saya pikir modifikasi ini (multiple -eargs mengakhiri argumen setelah setiap nama cabang) adalah kebiasaan portabel yang baik untuk digunakan saat menggunakan cabang dalam sed.
Wildcard
4

Saya akan melakukannya dengan Perl satu-liner kecil.

cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest
EOF

hasil panen

foo
this 
foo
bar
something
something else
foo
bar
pengguna1146332
sumber
3
Jika ini adalah kode-golf, Anda bisa menggunakan Ebukan edan -00777bukannya $/bit (lihat perlrun (1)). Yang akan mempersingkat menjadi:, perl -0777 -nE 'say /(foo.*bar)/s'masih bisa dibaca.
Thor
1
Saya tidak tahu tentang bendera-bendera ini! Saya yakin terutama -0[octal]akan menemukan jalannya dalam alur kerja saya! Terima kasih untuk itu
user1146332
3

Berikut ini adalah solusi sed GNU dua lulus yang tidak memerlukan banyak memori:

< infile                                     \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p'                            \
| tr '\n' ' '                                \
| sed 's/ /,/; s/ /p/'                       \
| sed -n -f - infile

Penjelasan

  • sedDoa pertama melewati infile dan menemukan kejadian pertama foodan semua kejadian berikutnya bar.
  • Alamat-alamat ini kemudian dibentuk menjadi sedskrip baru dengan dua doa seddan satu tr. Output yang ketiga sedadalah [start_address],[end_address]p, tanpa tanda kurung.
  • Doa terakhir sedmelewati infilelagi, mencetak alamat yang ditemukan dan semuanya di antaranya.
Thor
sumber
2

Jika file input cocok dengan nyaman dalam memori, tetap sederhana .

Jika file input sangat besar, Anda dapat menggunakannya csplituntuk memecahnya menjadi beberapa bagian pada bagian pertama foodan setiap bagian selanjutnya, barkemudian mengumpulkan bagian-bagian tersebut. Potongan-potongan itu disebut piece-000000000,, piece-000000001dll. Pilih awalan (di sini, piece-) yang tidak akan berbenturan dengan file lain yang ada.

csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file

(Pada sistem non-Linux, Anda harus menggunakan sejumlah besar di dalam kurung kurawal, misalnya {999999999}, dan meneruskan -kopsi. Angka itu adalah jumlah barpotongan.)

Anda dapat mengumpulkan semua bagian dengan cat piece-*, tetapi ini akan memberi Anda segalanya setelah yang pertama foo. Jadi hapus bagian terakhir itu terlebih dahulu. Karena nama file yang dihasilkan oleh csplittidak mengandung karakter khusus, Anda dapat memperbaikinya tanpa mengambil tindakan pencegahan mengutip khusus, misalnya dengan

rm $(echo piece-* | sed 's/.* //')

atau setara

rm $(ls piece-* | tail -n 1)

Sekarang Anda dapat bergabung dengan semua bagian dan menghapus file-file sementara:

cat piece-* >output
rm piece-*

Jika Anda ingin menghapus potongan-potongan itu karena digabungkan untuk menghemat ruang disk, lakukan dalam satu lingkaran:

mv piece-000000000 output
for x in piece-?????????; do
  cat "$x" >>output; rm "$x"
done
Gilles 'SANGAT berhenti menjadi jahat'
sumber
1

Inilah cara lain dengan sed:

sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile

Ini menambahkan setiap baris dalam /foo/,$jangkauan (garis !- garis yang tidak dalam kisaran ini ddihapus) ke Hruang lama. Baris yang tidak cocok barkemudian dihapus. Pada garis yang cocok, ruang pola dikosongkan, e xdiubah dengan ruang tahan dan garis kosong terkemuka di ruang pola dihapus.

Dengan input besar dan beberapa kemunculan barhal ini seharusnya (jauh) lebih cepat daripada menarik setiap garis ke dalam ruang pola dan kemudian, setiap kali, memeriksa ruang pola bar.
Dijelaskan:

sed '/foo/,$!d                     # delete line if not in this range
H                                  # append to hold space
/bar/!d                            # if it doesn't match bar, delete 
s/.*//                             # otherwise empty pattern space and
x                                  # exchange hold buffer w. pattern space then
s/\n//                             # remove the leading newline
' infile

Tentu, jika ini adalah file (dan sesuai dengan memori) Anda bisa menjalankan:

 ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN

karena ed bisa mencari maju dan mundur.
Anda bahkan bisa membaca output perintah ke buffer teks jika shell Anda mendukung substitusi proses:

printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)

atau jika tidak, dengan gnu ed:

printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'
don_crissti
sumber
0

Menggunakan awk dalam shell apa pun pada sistem UNIX dan tanpa membaca seluruh file atau input stream ke memori sekaligus:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar
Ed Morton
sumber
0

Grep dapat melakukannya juga (well, GNU grep):

<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'

<infile grep -ozP '        #  call grep to print only the matching section (`-o`)
                           #  use NUL for delimiter (`-z`) (read the whole file).
                           #  And using pcre regex.
(?s)foo.*bar               #  Allow the dot (`.`) to also match newlines.
' | tr '\0' '\n'           #  Restore the NULs to newlines.

Untuk masukan dari badan pertanyaan:

$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this 
foo
bar
something
something else
foo
bar
Ishak
sumber