Cara membuat akhiran baris perl satu baris agnostik

Saya telah menggaruk-garuk kepala selama satu jam karena perl oneliner gagal karena file tersebut memiliki akhiran baris CRLF. Ia memiliki regex dengan pertandingan grup di akhir baris, dan CR diikutsertakan dalam pertandingan, membuat hal-hal buruk dengan menggunakan referensi belakang untuk penggantian.

Saya akhirnya menentukan CRLF secara manual di regex, tetapi apakah ada cara agar Perl menangani akhir baris secara otomatis apa pun itu?

Perintah aslinya adalah

perl -pe  's/foo bar(.*)$/foo $1 bar/g' file.txt

Perintah "Benar" adalah

perl -pe  's/foo bar(.*)\r\n/foo $1 bar\r\n/g' file.txt

Saya tahu saya juga dapat mengonversi akhiran baris sebelum diproses, saya tertarik dengan cara agar Perl menangani kasus ini dengan baik.

Contoh file (simpan dengan akhiran baris CRLF!)

[19:06:57.033] foo barmy
[19:06:57.033] foo baryour

Keluaran yang diharapkan

[19:06:57.033] foo my bar
[19:06:57.033] foo your bar

Output dengan perintah asli (bilah berada di awal baris karena cocok dengan gerbong kembali):

bar:06:57.033] foo my
bar:06:57.033] foo your

person CharlesB    schedule 30.10.2013    source sumber
comment
bagaimana jika Anda menggunakan \s* dalam kasus ini. Saya selalu menggunakan ini dalam bahasa lain.   -  person Darka    schedule 30.10.2013
comment
Baca dokumentasi tentang pengubah dan gunakan s pengubah /gs :)   -  person HamZa    schedule 30.10.2013
comment
@Darka: menggunakan \s* tidak berhasil, masih menangkap CR di .*   -  person CharlesB    schedule 30.10.2013
comment
@HamZa: menggunakan pengubah /gs tidak berfungsi   -  person CharlesB    schedule 30.10.2013
comment
@CharlesB oke, saya melihat $ liar, apa yang dilakukannya di sana? Bisakah Anda juga memberikan contoh masukan dan keluaran yang diharapkan?   -  person HamZa    schedule 30.10.2013
comment
@HamZa: $ ada di sini untuk mencocokkan akhir baris, saya ingin menangkap semuanya dari bar hingga akhir. Tentu itu tidak diperlukan, tetapi menghapusnya tidak mengubah apa pun   -  person CharlesB    schedule 30.10.2013
comment
@CharlesB bagaimana jika Anda menggunakan pengubah m? demo   -  person HamZa    schedule 30.10.2013


Jawaban (5)


\R urutan escape Perl v5.10+; lihat perldoc rebackslash atau dokumentasi online, yang cocok dengan "baris baru umum" ( platform-agnostically) dapat dibuat berfungsi di sini (contoh menggunakan Bash untuk membuat string input multi-baris):

$ printf 'foo barmy\r\nfoo baryour\r\n' | perl -pe 's/foo bar(.*?)\R/foo $1 bar\n/gm'
foo my bar
foo your bar

Perhatikan bahwa satu-satunya perbedaan jawaban Ether adalah penggunaan konstruksi tidak serakah (.*? bukan hanya .*), yang membuat perbedaan besar di sini.

Baca terus, jika Anda ingin tahu lebih banyak.


Latar belakang:

Ini adalah contoh jebakan yang terkait dengan \R, yang berasal dari fakta bahwa karakter tersebut dapat cocok dengan satu atau dua karakter - baik \r\n atau, biasanya, \n: [1]

Dengan konstruk (.*) yang serakah, "my\r" - termasuk \r - ditangkap, karena mesin regex tampaknya hanya melakukan backtrack sebanyak satu karakter untuk mencari \R, yang tersisa \n dengan sendirinya juga memuaskan.

Sebaliknya, penggunaan konstruk (.*?) yang tidak serakah menyebabkan \R cocok dengan urutan \r\n, sebagaimana dimaksud.

[1] \R cocok LEBIH dari sekadar \r\n dan \n: cocok dengan karakter apa pun yang diklasifikasikan sebagai spasi vertikal dalam istilah Unicode, yang juga mencakup \v (vertikal tab), \f (umpan formulir), \r (sendiri), dan karakter Unicode berikut: 0x133 (NEXT LINE), 0x2028 (LINE SEPARATOR), 0x8232 (LINE SEPARATOR) dan 0x8233 (PARAGRAPH SEPARATOR)

person mklement0    schedule 30.09.2015

Pertama-tama, mari kita ingat hal itu

perl -ple's/foo bar(.*)\z/foo $1 bar/g' file.txt

adalah kependekan dari sesuatu yang dekat dengan

perl -e'
   while (<>) {
      chomp;
      s/foo bar(.*)\z/foo $1 bar/g;
      print $_, $/;
   }
' file.txt

Perl membuatnya sehingga kode dapat membaca/menulis file teks lokal secara independen pada platform.

Dalam komentar, Anda bertanya bagaimana cara membaca/menulis file teks lokal dan file teks asing secara independen pada platform.

Pertama, Anda harus menonaktifkan penanganan normal Perl.

binmode STDIN;
binmode STDOUT;

Maka Anda harus menangani beberapa akhiran baris.

sub mychomp { (@_ ? $_[0] : $_) =~ s/(\s*)\z//; $1 }

while (<STDIN>) {
   my $le = mychomp($_);
   s/foo bar(.*)\z/foo $1 bar/g;
   print($_, $le);
}

Jadi, bukannya

perl -ple's/foo bar(.*)\z/foo $1 bar/g' file.txt

kamu akan melakukannya

perl -e'
   sub mychomp { (@_ ? $_[0] : $_) =~ s/(\s*)\z//; $1 }

   binmode STDIN;
   binmode STDOUT;
   while (<STDIN>) {
      my $le = mychomp($_);
      s/foo bar(.*)\z/foo $1 bar/g;
      print($_, $le);
   }
' <file
person ikegami    schedule 30.10.2013
comment
Berfungsi pada setiap file, terima kasih! tapi bukan satu kalimat lagi - person CharlesB; 30.10.2013
comment
Dan ini berfungsi di semua sistem (kecuali MacOS kuno yang tidak didukung oleh Perl). Agar tidak menjadi satu baris, itulah biaya untuk mendukung 4 kombinasi sistem dan jenis file yang berbeda. - person ikegami; 30.10.2013

Di perls yang lebih baru, Anda dapat menggunakan \R di regex Anda untuk menghapus semua karakter akhir baris (termasuk \n dan \r). Lihat perldoc perlre.

person Ether    schedule 30.10.2013
comment
Tampaknya solusi yang bagus, namun tidak berhasil. Saya mencoba perl -pe 's/foo bar(.*)\R/foo $1 bar\n/gm' fix-cr.txt dan perl -pe 's/foo bar(\N*)\R/foo $1 bar\n/gm' fix-cr.txt, tidak ada satupun yang berfungsi (mendapatkan hasil yang sama dengan regex normal). Apa yang saya lewatkan? - person CharlesB; 31.10.2013
comment
@CharlesB: Ini berfungsi jika Anda menggunakan konstruksi yang tidak serakah: ganti (.*) dengan (.*?); lihat jawaban saya untuk latar belakang. - person mklement0; 01.10.2015

Anda dapat mengatakan:

perl -pe 's/foo bar([^\015]*)(\015?\012)/foo $1 bar$2/g' *.txt

Akhiran baris akan dipertahankan, yaitu sama dengan file masukan.


Anda mungkin juga ingin merujuk ke perldoc perlport.

person devnull    schedule 30.10.2013
comment
@CharlesB Apa yang dikaburkan tentang itu? Bukankah \015\012 CRLF? - person devnull; 30.10.2013
comment
@CharlesB \015 == \r; \012 == \n. - person devnull; 30.10.2013
comment
Tentu, itu bisa dimengerti (maaf, mengaburkan tidak tepat). Maksud saya membuat regex kurang mudah dibaca, sementara saya mencari sesuatu (opsi, pengubah, tidak tahu apa) yang membuat regex tetap bersih - person CharlesB; 30.10.2013
comment
@CharlesB Maaf, saya tidak mengetahui adanya pengubah yang dapat membantu Anda mencapai tujuan Anda. - person devnull; 30.10.2013
comment
@CharlesB BTW, pernahkah Anda melihat perldoc perlport? - person devnull; 30.10.2013
comment
@devnull: Anda dapat menggunakan (\R) sebagai pengganti (\015?\012) - person Casimir et Hippolyte; 30.10.2013
comment
@CasimiretHippolyte Saya kira itu membutuhkan 5.10 - person devnull; 30.10.2013
comment
Dengan tidak adanya ›= 5.10.1 bolehkah saya menyarankan: perl -pe '$lf = qr/([^\015]*)(\015?\012)/; s/foo bar$lf/foo $1 bar$2/g' *.txt atau yang serupa. Anda akan membutuhkan keanehan. Rangkum itu. - person singingfish; 31.10.2013

apakah ada cara agar Perl menangani akhir baris khusus platform secara otomatis?

Ya. Ini sebenarnya defaultnya.

Masalahnya adalah Anda mencoba menangani akhiran baris Windows pada platform unix.

Ini pasti akan berhasil:

perl -pe'
    BEGIN {
       binmode STDIN,  ":crlf";
       binmode STDOUT, ":crlf";
    }
    s/foo bar(.*)$/foo $1 bar/g;
' <file.txt

Bolehkah saya menyarankan Anda tetap melakukannya secara manual?

Alternatifnya, Anda dapat mengonversi file menjadi file teks dan mengonversinya kembali.

<file.orig dos2unix | perl -pe'...' | unix2dos >file.new
person ikegami    schedule 30.10.2013
comment
Terima kasih, tapi ini masih bergantung pada platform. Jika saya menggunakan perintah ini pada file LF, itu akan menghasilkan file CRLF (atau bahkan gagal diurai) - person CharlesB; 30.10.2013
comment
@CharlesB, Itu tidak benar. Ini menangani file Windows secara platform independen seperti yang Anda minta. Jika Anda ingin menangani file lokal secara independen pada platform, gunakan kode asli Anda. - person ikegami; 30.10.2013
comment
Sangat masuk akal, tujuan saya adalah menangani file CRLF dan LF secara platform independen - person CharlesB; 30.10.2013
comment
juga tidak berhasil. Juga, kode asli mengatakan syntax error at -e line 3, near ", :" syntax error at -e line 4, near ", :" - person CharlesB; 30.10.2013
comment
Jawaban berteknologi rendah: Bagaimana kalau membuat akhiran opsional dengan tanda tanya: perl -pe 's/foo bar(.*)(\r?\n?)/foo $1 bar$2/g' file.txt - person Magnus; 30.10.2013
comment
@Magnus, Itu tidak akan menangani file LF di Windows - person ikegami; 30.10.2013
comment
':crlf' menyebabkan kesalahan sintaksis dalam perl -pe'...' - person RobEarl; 30.10.2013
comment
ah, saya pikir yang Anda maksud adalah kode yang saya miliki di komentar tepat sebelum yang Anda sebutkan :, bukan kode di jawaban saya. Itu hanya masalah melarikan diri. Tetap. - person ikegami; 30.10.2013