วิธีสร้าง perl one-liner line-ending ที่ไม่เชื่อเรื่องพระเจ้า

ฉันมีรอยขีดข่วนหัวของฉันเป็นเวลาหนึ่งชั่วโมงกับ perl oneliner ที่ล้มเหลวเนื่องจากไฟล์มีการสิ้นสุดบรรทัด CRLF มี regex พร้อมการจับคู่แบบกลุ่มที่ท้ายบรรทัด และ CR ถูกรวมไว้ในการแข่งขัน ทำให้เกิดสิ่งที่ไม่ดีด้วยการใช้การอ้างอิงด้านหลังเพื่อแทนที่

ฉันลงเอยด้วยการระบุ CRLF ด้วยตนเองใน regex แต่มีวิธีให้ Perl จัดการการสิ้นสุดบรรทัดโดยอัตโนมัติ ไม่ว่าจะเป็นอะไรก็ตาม

คำสั่งเดิมคือ

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

คำสั่ง "ถูกต้อง" คือ

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

ฉันรู้ว่าฉันสามารถแปลงการสิ้นสุดบรรทัดก่อนการประมวลผลได้ ฉันสนใจที่จะให้ Perl จัดการกรณีนี้อย่างสง่างามได้อย่างไร

ไฟล์ตัวอย่าง (บันทึกด้วยการลงท้ายบรรทัด CRLF!)

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

ผลผลิตที่คาดหวัง

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

เอาต์พุตพร้อมคำสั่งดั้งเดิม (แถบไปที่จุดเริ่มต้นของบรรทัดเนื่องจากจับคู่พร้อมกับการขึ้นบรรทัดใหม่):

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

person CharlesB    schedule 30.10.2013    source แหล่งที่มา
comment
จะเกิดอะไรขึ้นถ้าคุณใช้ \s* ในกรณีนี้ ฉันใช้สิ่งนี้ในภาษาอื่นเสมอ   -  person Darka    schedule 30.10.2013
comment
อ่านเอกสารเกี่ยวกับตัวแก้ไข และใช้ s ตัวแก้ไข /gs :)   -  person HamZa    schedule 30.10.2013
comment
@Darka: การใช้ \s* ใช้งานไม่ได้ แต่ยังคงจับ CR ใน .*   -  person CharlesB    schedule 30.10.2013
comment
@HamZa: การใช้ /gs modifier ไม่ทำงาน   -  person CharlesB    schedule 30.10.2013
comment
@CharlesB โอเค ฉันเห็น $ อย่างดุเดือด มันกำลังทำอะไรอยู่ที่นั่น? คุณช่วยยกตัวอย่างอินพุตและเอาต์พุตที่คาดหวังได้ไหม   -  person HamZa    schedule 30.10.2013
comment
@HamZa: $ อยู่ที่นี่เพื่อให้ตรงกับจุดสิ้นสุดของบรรทัด ฉันต้องการบันทึกทุกอย่างตั้งแต่ bar ถึงจุดสิ้นสุด แน่นอนว่าไม่จำเป็น แต่การลบออกจะไม่เปลี่ยนแปลงอะไรเลย   -  person CharlesB    schedule 30.10.2013
comment
@CharlesB แล้วคุณใช้ m modifier ล่ะ? สาธิต   -  person HamZa    schedule 30.10.2013


คำตอบ (5)


\R ลำดับการหลีก Perl v5.10+; ดู perldoc rebackslash หรือเอกสารประกอบออนไลน์ ซึ่งตรงกับ "การขึ้นบรรทัดใหม่ทั่วไป" ( ไม่เชื่อเรื่องแพลตฟอร์ม) สามารถ สามารถทำงานได้ที่นี่ (ตัวอย่างใช้ Bash เพื่อสร้างสตริงอินพุตหลายบรรทัด):

$ 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

โปรดทราบว่าข้อแตกต่างเพียงอย่างเดียวของ คำตอบของ Ether คือ การใช้โครงสร้างที่ ไม่โลภ (.*? แทนที่จะเป็นเพียง .*) ซึ่งสร้างความแตกต่างทั้งหมดที่นี่

อ่านต่อหากคุณต้องการทราบข้อมูลเพิ่มเติม


พื้นหลัง:

เป็นตัวอย่างของ หลุมพรางที่เกี่ยวข้องกับ \R ซึ่งเกิดจากการที่มันสามารถจับคู่อักขระ หนึ่งหรือสองตัว - อาจเป็น \r\n หรือโดยทั่วไปแล้ว \n: [1]

ด้วยโครงสร้าง (.*) ที่มีความละโมบ "my\r" - รวมถึง \r - จะถูกบันทึก เนื่องจากกลไก regex ดูเหมือนจะย้อนรอยเพียง หนึ่ง อักขระเพื่อค้นหา \R ซึ่งส่วนที่เหลือ \n ด้วยตัวมันเองก็พอใจเช่นกัน

ในทางตรงกันข้าม การใช้โครงสร้าง (.*?) ที่ไม่โลภจะทำให้ \R ตรงกับ \r\n ลำดับ ตามที่ตั้งใจไว้

[1] \R จับคู่มากกว่า \r\n และ \n: โดยจะจับคู่อักขระตัวเดียวใดๆ ที่จัดประเภทเป็น ช่องว่างแนวตั้ง ในแง่ Unicode ซึ่งรวมถึง \v (แนวตั้งด้วย แท็บ), \f (ฟีดแบบฟอร์ม), \r (โดยตัวมันเอง) และอักขระ Unicode ต่อไปนี้: 0x133 (NEXT LINE), 0x2028 (LINE SEPARATOR), 0x8232 (LINE SEPARATOR) และ 0x8233 (PARAGRAPH SEPARATOR)

person mklement0    schedule 30.09.2015

ก่อนอื่นให้เราจำไว้ว่า

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

สั้นสำหรับบางสิ่งที่ใกล้ชิด

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

Perl ทำให้โค้ดสามารถอ่าน/เขียนไฟล์ข้อความในเครื่องในลักษณะที่เป็นอิสระจากแพลตฟอร์ม

ในความคิดเห็น คุณถามวิธีอ่าน/เขียนทั้งไฟล์ข้อความในเครื่องและไฟล์ข้อความต่างประเทศในลักษณะที่เป็นอิสระจากแพลตฟอร์ม

ขั้นแรก คุณจะต้องปิดการใช้งานการจัดการตามปกติของ Perl

binmode STDIN;
binmode STDOUT;

จากนั้นคุณจะต้องจัดการกับการสิ้นสุดหลายบรรทัด

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

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

ดังนั้นแทนที่จะ

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

คุณจะมี

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
ใช้ได้กับทุกไฟล์ ขอบคุณ! แม้ว่าจะไม่ใช่สายการบินเดียวอีกต่อไป - person CharlesB; 30.10.2013
comment
และทำงานได้กับทุกระบบ (ยกเว้น MacOS รุ่นเก่าซึ่ง Perl ไม่รองรับอยู่แล้ว) สำหรับการไม่เป็นแบบซับเดียว นั่นคือค่าใช้จ่ายในการรองรับระบบและประเภทไฟล์ที่แตกต่างกัน 4 แบบ - person ikegami; 30.10.2013

ใน Perls ที่ใหม่กว่า คุณสามารถใช้ \R ใน regex ของคุณเพื่อตัดอักขระท้ายบรรทัดทั้งหมดออก (รวมทั้ง \n และ \r) ดู perldoc perlre

person Ether    schedule 30.10.2013
comment
ดูเหมือนเป็นวิธีแก้ปัญหาที่ดี แต่ก็ไม่ได้ผล ฉันลอง perl -pe 's/foo bar(.*)\R/foo $1 bar\n/gm' fix-cr.txt และ perl -pe 's/foo bar(\N*)\R/foo $1 bar\n/gm' fix-cr.txt แล้ว ไม่ได้ผลเลย (ได้ผลลัพธ์เหมือนกับ regex ปกติ) ฉันคิดถึงอะไร? - person CharlesB; 31.10.2013
comment
@CharlesB: มันใช้งานได้ถ้าคุณใช้โครงสร้างที่ไม่โลภ: แทนที่ (.*) ด้วย (.*?); ดูคำตอบของฉันสำหรับพื้นหลัง - person mklement0; 01.10.2015

คุณสามารถพูดได้:

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

การสิ้นสุดบรรทัดจะยังคงอยู่ กล่าวคือ จะเหมือนกับไฟล์อินพุต


คุณอาจต้องการอ้างอิงถึง perldoc perlport

person devnull    schedule 30.10.2013
comment
@CharlesB สับสนอะไรเกี่ยวกับเรื่องนั้น? ไม่ใช่ \015\012 CRLF ใช่หรือไม่ - person devnull; 30.10.2013
comment
@ชาร์ลส์B \015 == \r; \012 == \n. - person devnull; 30.10.2013
comment
แน่นอนว่าเป็นเรื่องที่เข้าใจได้ (ทำให้สับสนไม่เหมาะสมขออภัย) ฉันหมายถึงทำให้ regex อ่านได้น้อยลง ในขณะที่ฉันกำลังมองหาบางสิ่งบางอย่าง (ตัวเลือก ตัวแก้ไข ไม่รู้ว่าอะไร) ที่ทำให้ regex สะอาด - person CharlesB; 30.10.2013
comment
@CharlesB ขออภัย ฉันไม่ทราบถึง ตัวแก้ไข ที่จะช่วยให้คุณบรรลุเป้าหมาย เป้าหมาย - person devnull; 30.10.2013
comment
@CharlesB BTW คุณเคยเห็น perldoc perlport หรือไม่ - person devnull; 30.10.2013
comment
@devnull: คุณสามารถใช้ (\R) แทนที่ (\015?\012) - person Casimir et Hippolyte; 30.10.2013
comment
@CasimiretHippolyte ฉันเดาว่ามันต้องใช้ 5.10 - person devnull; 30.10.2013
comment
ในกรณีที่ไม่มี ›= 5.10.1 ฉันขอแนะนำ: perl -pe '$lf = qr/([^\015]*)(\015?\012)/; s/foo bar$lf/foo $1 bar$2/g' *.txt หรืออะไรที่คล้ายกัน คุณจะต้องการความแปลกประหลาด ห่อหุ้มมัน - person singingfish; 31.10.2013

มีวิธีรับ Perl จัดการการสิ้นสุดบรรทัดเฉพาะแพลตฟอร์มโดยอัตโนมัติหรือไม่

ใช่. มันเป็นค่าเริ่มต้นจริงๆ

ปัญหาคือคุณกำลังพยายามจัดการการสิ้นสุดบรรทัดของ Windows บนแพลตฟอร์มยูนิกซ์

สิ่งนี้จะทำได้อย่างแน่นอน:

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

ฉันขอแนะนำให้คุณทำต่อไปด้วยตนเองได้ไหม

หรือคุณสามารถแปลงไฟล์เป็นไฟล์ข้อความแล้วแปลงกลับได้

<file.orig dos2unix | perl -pe'...' | unix2dos >file.new
person ikegami    schedule 30.10.2013
comment
ขอบคุณ แต่ก็ยังขึ้นอยู่กับแพลตฟอร์ม หากฉันใช้คำสั่งนี้กับไฟล์ LF มันจะส่งออกไฟล์ CRLF (หรือแม้กระทั่งแยกวิเคราะห์ล้มเหลว) - person CharlesB; 30.10.2013
comment
@CharlesB นั่นไม่เป็นความจริง มันจัดการไฟล์ Windows ในลักษณะที่เป็นอิสระจากแพลตฟอร์มตามที่คุณถาม หากคุณต้องการจัดการไฟล์ในเครื่องในลักษณะที่ไม่ขึ้นอยู่กับแพลตฟอร์ม ให้ใช้โค้ดต้นฉบับของคุณ - person ikegami; 30.10.2013
comment
มันสมเหตุสมผลดี เป้าหมายของฉันคือการจัดการไฟล์ CRLF และ LF ในลักษณะที่เป็นอิสระจากแพลตฟอร์ม - person CharlesB; 30.10.2013
comment
ไม่ทำงานเช่นกัน นอกจากนี้โค้ดต้นฉบับยังระบุว่า syntax error at -e line 3, near ", :" syntax error at -e line 4, near ", :" - person CharlesB; 30.10.2013
comment
คำตอบที่ใช้เทคโนโลยีต่ำ: ลองทำให้ตอนจบเป็นทางเลือกด้วยเครื่องหมายคำถาม: perl -pe 's/foo bar(.*)(\r?\n?)/foo $1 bar$2/g' file.txt - person Magnus; 30.10.2013
comment
@Magnus นั่นจะไม่จัดการไฟล์ LF บน Windows - person ikegami; 30.10.2013
comment
':crlf' ทำให้เกิดข้อผิดพลาดทางไวยากรณ์ภายใน perl -pe'...' - person RobEarl; 30.10.2013
comment
อ่า ฉันคิดว่าคุณหมายถึงรหัสที่ฉันมีในความคิดเห็นก่อนหน้ารหัสที่คุณพูดถึง : ไม่ใช่รหัสในคำตอบของฉัน มันเป็นเพียงปัญหาในการหลบหนี ที่ตายตัว. - person ikegami; 30.10.2013