Ganti string yang berisi baris baru dalam file besar

16

Adakah yang tahu alat berbasis-non-baris untuk "biner" mencari / mengganti string dengan cara yang agak hemat memori? Lihat pertanyaan ini juga.

Saya memiliki file teks + 2GB yang ingin saya proses serupa dengan apa yang tampaknya dilakukan:

sed -e 's/>\n/>/g'

Itu berarti, saya ingin menghapus semua baris baru yang terjadi setelah >, tetapi tidak di tempat lain, sehingga mengesampingkan tr -d.

Perintah ini (yang saya dapatkan dari jawaban pertanyaan serupa ) gagal dengan couldn't re-allocate memory:

sed --unbuffered ':a;N;$!ba;s/>\n/>/g'

Jadi, apakah ada metode lain tanpa menggunakan C? Saya benci perl, tetapi saya bersedia membuat pengecualian dalam kasus ini :-)

Saya tidak tahu pasti karakter apa pun yang tidak muncul dalam data, jadi mengganti sementara \ndengan karakter lain adalah sesuatu yang ingin saya hindari jika memungkinkan.

Ada ide bagus, siapa saja?

MattBianco
sumber
Sudahkah Anda mencoba opsi --unbuffered?
ctrl-alt-delor
Dengan atau tanpa --unbufferedkehabisan memori
MattBianco
Apa yang $!harus dilakukan
ctrl-alt-delor
Apa yang salah dengan perintah sed pertama. Yang kedua tampaknya membaca semuanya ke dalam ruang pola, saya tidak tahu bahwa $!itu benar. Ini saya berharap akan membutuhkan BANYAK memori.
ctrl-alt-delor
Masalahnya adalah bahwa sed membaca semuanya sebagai baris, itu sebabnya perintah pertama tidak menghapus baris baru, karena itu akan mengeluarkan teks baris-demi-baris lagi. Perintah kedua hanyalah solusi. Saya pikir sedbukan alat yang tepat dalam hal ini.
MattBianco

Jawaban:

14

Ini benar-benar sepele di Perl, Anda tidak harus membencinya!

perl -i.bak -pe 's/>\n/>/' file

Penjelasan

  • -i: edit file di tempat, dan buat cadangan dari aslinya yang disebut file.bak. Jika Anda tidak ingin cadangan, gunakan perl -i -pesaja.
  • -pe: baca file input baris demi baris dan cetak setiap baris setelah menerapkan skrip yang diberikan sebagai -e.
  • s/>\n/>/: substitusi, sama seperti sed.

Dan inilah awkpendekatannya:

awk  '{if(/>$/){printf "%s",$0}else{print}}' file2 
terdon
sumber
3
+1. golf awk:awk '{ORS=/>$/?"":"\n"}1'
glenn jackman
1
Mengapa saya tidak suka perl secara umum adalah alasan yang sama mengapa saya memilih jawaban ini (atau sebenarnya komentar Anda untuk jawaban Gnouc): keterbacaan. Menggunakan perl -pe dengan "pola sed" yang sederhana jauh lebih mudah dibaca daripada ekspresi sed yang kompleks.
MattBianco
3
@MattBianco cukup adil tapi, supaya Anda tahu, itu tidak ada hubungannya dengan Perl. Tampilan di belakang yang digunakan Gnouc adalah fitur dari beberapa bahasa ekspresi reguler (termasuk, tetapi tidak terbatas pada, PCRE), bukan kesalahan Perl sama sekali. Juga, setelah menampilkan monstrositas sed ini ':a;N;$!ba;s/>\n/>/g'dalam pertanyaan Anda, Anda melepaskan hak Anda untuk mengeluh tentang keterbacaan! : P
terdon
@glennjackman bagus! Saya bermain dengan foo ? bar : bazkonstruk tetapi tidak bisa membuatnya bekerja.
terdon
@terdon: Yeap, kesalahanku. Hapus.
cuonglm
7

Sebuah perlsolusi:

$ perl -pe 's/(?<=>)\n//'

Penjelasan

  • s/// digunakan untuk substitusi string.
  • (?<=>) adalah pola lookbehind.
  • \n cocok dengan baris baru.

Seluruh makna pola menghapus semua baris baru yang ada >sebelumnya.

cuonglm
sumber
2
mau berkomentar apa yang dilakukan oleh bagian-bagian program? Saya selalu ingin belajar.
MattBianco
2
Mengapa repot-repot dengan tampilan di belakang? Kenapa tidak adil s/>\n/>/?
terdon
1
atau s/>\K\n//akan juga berfungsi
glenn jackman
@terdon: Hanya hal pertama yang saya pikirkan, hapus alih-alih ganti
cuonglm
@glennjackman: poin bagus!
cuonglm
3

Bagaimana dengan ini:

sed ':loop
  />$/ { N
    s/\n//
    b loop
  }' file

Untuk GNU, Anda juga dapat mencoba menambahkan opsi -u( --unbuffered) sesuai pertanyaan. GNU sed juga senang dengan ini sebagai one-liner sederhana:

sed ':loop />$/ { N; s/\n//; b loop }' file
Graeme
sumber
Itu tidak menghapus yang terakhir \njika file berakhir >\n, tapi itu mungkin lebih baik.
Stéphane Chazelas
@ StéphaneChazelas, mengapa penutupan }harus dalam ekspresi yang terpisah? Apakah ini tidak akan berfungsi sebagai ekspresi multiline?
Graeme
1
Itu akan bekerja di sed POSIX dengan b loop\n}atau -e 'b loop' -e '}'tetapi tidak b loop;}dan tentu saja tidak b loop}karena }dan ;valid dalam nama label (meskipun tidak ada orang waras yang akan menggunakannya. Dan itu berarti sed GNU tidak sesuai POSIX) dan }perintah perlu dipisahkan dari bperintah.
Stéphane Chazelas
@ StéphaneChazelas, GNU sedsenang dengan semua hal di atas bahkan dengan --posix! Standar ini juga memiliki yang berikut untuk ekspresi penyangga - The list of sed functions shall be surrounded by braces and separated by <newline>s. Apakah ini tidak berarti bahwa titik koma hanya boleh digunakan di luar kawat gigi?
Graeme
@ mikeserv, loop diperlukan untuk menangani garis berturut-turut yang berakhiran >. Asli tidak pernah punya, ini ditunjukkan oleh Stéphane.
Graeme
1

Anda harus dapat menggunakan seddengan Nperintah, tetapi triknya adalah menghapus satu baris dari ruang pola setiap kali Anda menambahkan yang lain (sehingga ruang pola selalu hanya berisi 2 baris berturut-turut, alih-alih mencoba membaca secara keseluruhan file) - coba

sed ':a;$!N;s/>\n/>/;P;D;ba'

SUNTING : setelah membaca ulang Sed Sederet Liners Terkenal Peteris Krumins Dijelaskan Saya percaya sedsolusi yang lebih baik adalah

sed -e :a -e '/>$/N; s/\n//; ta'

yang hanya menambahkan baris berikut dalam kasus bahwa itu sudah membuat >kecocokan di akhir, dan harus kembali secara kondisional untuk menangani kasus pencocokan garis berturut-turut (itu adalah Krumin 39. Tambahkan baris ke yang berikutnya jika berakhir dengan backslash "\" tepatnya kecuali untuk substitusi >untuk \sebagai karakter bergabung, dan fakta bahwa karakter gabungan dipertahankan dalam output).

Steeldriver
sumber
2
Itu tidak berfungsi jika 2 baris berturut-turut berakhir >(itu juga spesifik untuk GNU)
Stéphane Chazelas
1

sedtidak menyediakan cara untuk memancarkan output tanpa baris baru final. Pendekatan Anda menggunakan secara Nfundamental berfungsi, tetapi menyimpan garis yang tidak lengkap dalam memori, dan dengan demikian dapat gagal jika garis menjadi terlalu panjang (implementasi sed tidak biasanya dirancang untuk menangani garis yang sangat panjang).

Anda bisa menggunakan awk saja.

awk '{if (/<$/) printf "%s", $0; else print}'

Pendekatan alternatif digunakan truntuk menukar karakter baris baru dengan karakter "membosankan", yang sering terjadi. Spasi mungkin berfungsi di sini - pilih karakter yang cenderung muncul di setiap baris atau setidaknya sebagian besar garis dalam data Anda.

tr ' \n' '\n ' | sed 's/> />/g' | tr '\n ' ' \n'
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Kedua metode sudah diperlihatkan di sini untuk memberikan efek yang lebih baik dalam jawaban lain. Dan pendekatannya dengan sedtidak bekerja tanpa buffer 2,5 gigabyte.
mikeserv
Apakah ada yang menyebut awk? Oh, saya melewatkannya, saya hanya memperhatikan perl dalam jawaban terdon untuk beberapa alasan. Tidak ada yang menyebutkan trpendekatan - mikeserv, Anda memposting pendekatan yang berbeda (valid, tetapi kurang umum) yang kebetulan juga digunakan tr.
Gilles 'SO- berhenti bersikap jahat'
sah, tetapi kurang generik bagi saya seperti Anda baru saja menyebutnya solusi yang berfungsi dan tepat sasaran. Saya pikir sulit untuk berpendapat bahwa hal seperti itu tidak berguna yang aneh karena memiliki 0 upvote. Perbedaan terbesar yang dapat saya lihat antara solusi saya sendiri dan penawaran umum Anda , adalah bahwa tambang saya secara khusus memecahkan masalah, sedangkan solusi Anda umumnya. Itu mungkin membuatnya berharga - dan saya bahkan dapat membalikkan suara saya - tetapi ada juga masalah sial dari 7 jam antara mereka dan tema berulang jawaban Anda meniru orang lain. Bisakah Anda menjelaskan ini?
mikeserv
1

bagaimana dengan menggunakan ed?

ed -s test.txt <<< $'/fruits/s/apple/banana/g\nw'

(via http://wiki.bash-hackers.org/howto/edit-ed )

andrej
sumber
diedit, tidak ada lagi ketergantungan pada situs web
andrej
0

Saya akhirnya menggunakan gsar seperti yang dijelaskan dalam jawaban ini seperti ini:

gsar -F '-s>:x0A' '-r>'
MattBianco
sumber
-1

Ada banyak cara untuk melakukan ini, dan sebagian besar di sini benar-benar bagus, tetapi saya pikir ini yang menjadi favorit saya:

tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'

Atau bahkan:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n'
mikeserv
sumber
Saya tidak bisa mendapatkan jawaban pertama Anda untuk bekerja sama sekali. Sementara saya mengagumi keanggunan yang kedua, saya percaya bahwa Anda harus menghapus *. Caranya sekarang, itu akan menghapus setiap baris kosong mengikuti garis yang diakhiri dengan a >. ... Hmm. Melihat kembali pertanyaan itu, saya melihat bahwa itu agak ambigu. Pertanyaannya mengatakan, "Saya ingin menghapus semua baris baru yang terjadi setelah >, ..." Saya menafsirkan itu berarti yang >\n\n\n\n\nfooharus diubah \n\n\n\nfoo, tetapi saya kira foomungkin hasil yang diinginkan.
Scott
@Scott - Saya menguji dengan variasi pada hal berikut: printf '>\n>\n\n>>\n>\n>>>\n>\nf\n\nff\n>\n' | tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'- yang menghasilkan >>>>>>>>>>f\n\nff\n\nbagi saya dengan jawaban pertama. Saya ingin tahu apa yang Anda lakukan untuk memecahkannya, karena saya ingin memperbaikinya. Adapun poin kedua - Saya tidak setuju bahwa itu ambigu. OP tidak meminta untuk menghapus semua > sebelumnya yang \newline, melainkan untuk menghapus semua \n ewlines berikut a >.
mikeserv
1
Ya, tetapi interpretasi yang valid adalah bahwa, di >\n\n\n\n\n, hanya baris baru pertama setelah a >; semua yang lain mengikuti baris baru lainnya. Perhatikan bahwa saran OP "ini adalah yang saya inginkan, kalau saja itu berhasil" sed -e 's/>\n/>/g', bukan sed -e 's/>\n*/>/g'.
Scott
1
@ Esc - saran tidak berhasil dan tidak pernah bisa. Saya tidak percaya bahwa saran kode dari seseorang yang tidak sepenuhnya memahami kode dapat dianggap sebagai titik interpretasi yang valid sebagai bahasa sederhana yang juga digunakan orang. Dan selain itu, output - jika benar-benar berfungsi - s/>\n/>/pada >\n\n\n\n\nmasih akan menjadi sesuatu yang s/>\n/>/akan diedit.
mikeserv