sed: อ่านไฟล์ทั้งหมดในพื้นที่รูปแบบโดยไม่ล้มเหลวในอินพุตบรรทัดเดียว


9

การอ่านไฟล์ทั้งหมดในพื้นที่รูปแบบมีประโยชน์สำหรับการแทนที่บรรทัดใหม่ & c และมีหลายกรณีที่ให้คำแนะนำต่อไปนี้:

sed ':a;N;$!ba; [commands...]'

อย่างไรก็ตามมันจะล้มเหลวหากอินพุตมีเพียงหนึ่งบรรทัด

ตัวอย่างเช่นด้วยอินพุตสองบรรทัดทุกบรรทัดจะต้องอยู่ภายใต้คำสั่งการแทนที่:

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

แต่ด้วยอินพุตบรรทัดเดียวจะไม่มีการทดแทน:

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

เราจะเขียนsedคำสั่งเพื่ออ่านอินพุตทั้งหมดในครั้งเดียวและไม่มีปัญหานี้ได้อย่างไร?


ฉันแก้ไขคำถามของคุณเพื่อให้มีคำถามจริง คุณอาจรอคำตอบอื่น ๆ หากคุณต้องการ แต่ในที่สุดก็ทำเครื่องหมายคำตอบที่ดีที่สุดว่าเป็นที่ยอมรับ (ดูปุ่มไพพ์ที่ด้านซ้ายของคำตอบด้านล่างปุ่มลูกศรขึ้น - ลง)
John1024

@ John1024 ขอบคุณดีที่มีตัวอย่าง การค้นหาสิ่งนี้มีแนวโน้มที่จะเตือนฉันว่า "ทุกอย่างผิดปกติ" แต่ฉันดีใจที่พวกเราบางคนไม่ยอมแพ้ :}
dicktyr

2
มีตัวเลือกที่สาม! ใช้sed -zตัวเลือกของ GNU หากไฟล์ของคุณไม่มีโมฆะมันจะอ่านจนกว่าจะสิ้นสุดไฟล์! ค้นพบจากสิ่งนี้: stackoverflow.com/a/30049447/582917
CMCDragonkai

คำตอบ:


13

มีเหตุผลหลายประการที่การอ่านไฟล์ทั้งหมดในพื้นที่รูปแบบอาจผิดพลาดได้ ปัญหาตรรกะในคำถามโดยรอบบรรทัดสุดท้ายเป็นปัญหาทั่วไป มันเกี่ยวข้องกับsedวงจรของสาย - เมื่อไม่มีอีกแล้วและsedพบ EOF มันผ่าน - มันหยุดการประมวลผล ดังนั้นถ้าคุณอยู่ในบรรทัดสุดท้ายและคุณแนะนำsedให้รับมันก็จะหยุดอยู่ตรงนั้นและไม่ทำอะไรอีก

ที่กล่าวว่าหากคุณต้องการอ่านไฟล์ทั้งหมดลงในพื้นที่รูปแบบจริงๆแล้วมันก็น่าจะคุ้มค่าที่จะพิจารณาเครื่องมืออื่นอยู่ดี ความจริงก็คือsedเป็นตัวแก้ไขกระแสที่มีความหมายเหมือนกัน - มันถูกออกแบบมาเพื่อทำงานเป็นสาย - หรือบล็อกข้อมูลแบบลอจิคัล - ในเวลาเดียวกัน

มีเครื่องมือที่คล้ายกันหลายอย่างที่ติดตั้งดีกว่าเพื่อจัดการบล็อกไฟล์แบบเต็ม edและexตัวอย่างเช่นสามารถทำสิ่งที่sedสามารถทำได้และมีไวยากรณ์ที่คล้ายกัน - และอื่น ๆ นอกเหนือจาก - แต่แทนที่จะทำงานเฉพาะในอินพุตสตรีมในขณะที่เปลี่ยนเป็นเอาต์พุตเช่นเดียวกับsedพวกเขายังรักษาไฟล์สำรองชั่วคราวในระบบไฟล์ . งานของพวกเขามีบัฟเฟอร์ไปยังดิสก์ตามความจำเป็นและพวกเขาจะไม่ลาออกอย่างกะทันหันในตอนท้ายของไฟล์(และมีแนวโน้มที่จะระเบิดมากน้อยมักจะภายใต้ความเครียดบัฟเฟอร์) ยิ่งไปกว่านั้นพวกเขายังมีฟังก์ชั่นที่มีประโยชน์มากมายซึ่งsedไม่เรียงลำดับที่ไม่สมเหตุสมผลในบริบทของสตรีมเช่นเครื่องหมายบรรทัดเลิกทำบัฟเฟอร์ที่ตั้งชื่อเข้าร่วมและอื่น ๆ

sedจุดแข็งหลักของมันคือความสามารถในการประมวลผลข้อมูลทันทีที่อ่านได้อย่างรวดเร็วมีประสิทธิภาพและในสตรีม เมื่อคุณ slurp ไฟล์ที่คุณทิ้งและคุณมักจะพบปัญหากรณีขอบเช่นปัญหาบรรทัดสุดท้ายที่คุณพูดถึงและบัฟเฟอร์ overruns และประสิทธิภาพสุดขีด - ในขณะที่ข้อมูลจะแยกวิเคราะห์ยาวขึ้นเวลาประมวลผลของเครื่องมือ regexp เมื่อระบุการจับคู่ เพิ่มขึ้นชี้แจง

เกี่ยวกับจุดสุดท้ายนั้นโดยวิธี: ในขณะที่ฉันเข้าใจs/a/A/gกรณีตัวอย่างน่าจะเป็นเพียงตัวอย่างไร้เดียงสาและอาจไม่ใช่สคริปต์จริงที่คุณต้องการรวบรวมในอินพุตคุณอาจพบว่ามันคุ้มค่าในการทำความคุ้นเคยกับy///. หากคุณมักจะพบว่าตัวเองกำลังgทดแทนตัวละครตัวหนึ่งไปอีกตัวหนึ่งแบบ lobally นั่นyอาจจะมีประโยชน์มากสำหรับคุณ มันคือการเปลี่ยนแปลงเมื่อเทียบกับการทดแทนและเร็วกว่าเพราะไม่ได้หมายความว่า regexp จุดหลังนี้สามารถทำให้มีประโยชน์เมื่อพยายามรักษาและทำซ้ำ//ที่อยู่เปล่า ๆเพราะมันไม่ได้ส่งผลกระทบต่อพวกเขา แต่จะได้รับผลกระทบจากพวกเขา ไม่ว่าในกรณีใดy/a/A/ก็เป็นวิธีที่ง่ายกว่าในการทำสิ่งเดียวกันให้สำเร็จและสามารถสลับได้เช่นกัน:y/aA/Aa/ ซึ่งจะแลกเปลี่ยนบน / ตัวพิมพ์เล็กทั้งหมดบนบรรทัดซึ่งกันและกัน

คุณควรทราบด้วยว่าพฤติกรรมที่คุณอธิบายไม่ใช่สิ่งที่ควรจะเกิดขึ้น

จาก GNU info sedในส่วนข้อบกพร่องที่รายงานโดยทั่วไป :

  • N คำสั่งในบรรทัดสุดท้าย

    • sedทางออกส่วนใหญ่โดยไม่พิมพ์อะไรเมื่อNออกคำสั่งในบรรทัดสุดท้ายของไฟล์ GNU sedพิมพ์พื้นที่รูปแบบก่อนออกจากนอกเสียจากว่า-nได้ระบุสวิตช์คำสั่งแล้ว ตัวเลือกนี้เกิดจากการออกแบบ

    • ตัวอย่างเช่นพฤติกรรมของsed N foo barจะขึ้นอยู่กับว่า foo มีจำนวนบรรทัดคู่หรือคี่ หรือเมื่อเขียนสคริปต์เพื่ออ่านไม่กี่บรรทัดต่อไปดังต่อไปนี้การแข่งขันรูปแบบการใช้งานแบบดั้งเดิมของการsedจะบังคับให้คุณเขียนสิ่งที่ต้องการแทนเพียง/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }/foo/{ N;N;N;N;N;N;N;N;N; }

    • ไม่ว่าในกรณีใดวิธีแก้ปัญหาที่ง่ายที่สุดคือใช้$d;Nในสคริปต์ที่ต้องอาศัยลักษณะการทำงานดั้งเดิมหรือตั้งค่าPOSIXLY_CORRECTตัวแปรเป็นค่าที่ไม่ว่างเปล่า

POSIXLY_CORRECTตัวแปรสภาพแวดล้อมที่ถูกกล่าวถึงเพราะระบุ POSIX ว่าถ้าsedการเผชิญหน้า EOF เมื่อที่พยายามNมันควรจะลาออกโดยไม่ต้องออก แต่รุ่น GNU จงใจแบ่งมาตรฐานในกรณีนี้ โปรดทราบว่าแม้ในขณะที่พฤติกรรมนั้นเป็นธรรมเหนือข้อสันนิษฐานก็คือกรณีข้อผิดพลาดเป็นหนึ่งในการแก้ไขกระแส - ไม่ slurping ไฟล์ทั้งหมดในหน่วยความจำ

มาตรฐานกำหนดNพฤติกรรมดังนี้:

  • N

    • ผนวกอินพุตบรรทัดถัดไปโดยลด\newline ที่ถูกยกเลิกลงในพื้นที่รูปแบบโดยใช้\newline ในตัวเพื่อแยกวัสดุที่ต่อท้ายออกจากวัสดุดั้งเดิม โปรดทราบว่าการเปลี่ยนแปลงหมายเลขบรรทัดปัจจุบัน

    • หากไม่มีบรรทัดอินพุตถัดไปNคำสั่งกริยาจะแยกไปที่ส่วนท้ายของสคริปต์และออกโดยไม่เริ่มรอบใหม่หรือคัดลอกพื้นที่รูปแบบไปยังเอาต์พุตมาตรฐาน

เมื่อทราบว่ามีบาง GNU-ISMS อื่น ๆ ที่แสดงให้เห็นในคำถาม - โดยเฉพาะอย่างยิ่งการใช้งานของ:ฉลากbไร่และวงเล็บฟังก์ชั่นบริบท{ }เป็นกฎของหัวแม่มือsedคำสั่งใด ๆที่ยอมรับพารามิเตอร์โดยพลการจะเข้าใจการกำหนดขอบเขตที่\newline ในสคริปต์ ดังนั้นคำสั่ง ...

:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...

... ทุกคนมีแนวโน้มที่จะปฏิบัติไม่ถูกต้องขึ้นอยู่กับการsedใช้งานที่อ่านได้ พวกเขาควรจะเขียน:

...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}

เดียวกันถือเป็นจริงสำหรับr, w, t, a, iและ(และอาจจะขึ้นไม่กี่คนที่ฉันลืมในขณะนี้)c ในเกือบทุกกรณีพวกเขาอาจจะเขียน:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

... โดยที่-eคำสั่ง xecution ใหม่ย่อมาจาก\newline delimiter ดังนั้นที่infoข้อความGNU แนะนำการใช้งานแบบดั้งเดิมsedจะบังคับให้คุณทำ :

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

... มันควรจะเป็น ...

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}

... แน่นอนว่าไม่เป็นความจริงเช่นกัน การเขียนบทในวิธีนั้นเป็นเรื่องที่ค่อนข้างงี่เง่า มีวิธีที่ง่ายกว่ามากในการทำเช่นเดียวกันเช่น:

printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
         //!g;x;$!d;:nd' -e 'l;$a\' \
     -e 'this is the last line' 

... ที่พิมพ์:

foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line

... เพราะtคำสั่ง est - เหมือนกับsedคำสั่งส่วนใหญ่- ขึ้นอยู่กับวงจรของวงจรเพื่อรีเฟรชรีจิสเตอร์รีเทิร์นและที่นี่วงจรของไลน์จะได้รับอนุญาตให้ทำงานส่วนใหญ่ได้ นั่นคือการแลกเปลี่ยนอีกอย่างที่คุณทำเมื่อคุณข้ามไฟล์ - รอบของบรรทัดจะไม่รีเฟรชอีกครั้งและการทดสอบจำนวนมากจะทำงานผิดปกติ

คำสั่งข้างต้นไม่เสี่ยงต่อการป้อนข้อมูลที่มากเกินไปเพราะมันจะทำการทดสอบง่ายๆเพื่อตรวจสอบสิ่งที่มันอ่านเมื่อมันอ่าน ด้วยHบรรทัดเก่าทั้งหมดจะต่อท้ายพื้นที่พักสาย แต่ถ้าบรรทัดตรงกับ/foo/นั้นจะแทนที่hพื้นที่ว่างเก่า บัฟเฟอร์มีการxเปลี่ยนแปลงe ถัดไปและs///พยายามใช้การแทนที่แบบมีเงื่อนไขหากเนื้อหาของบัฟเฟอร์ตรงกับ//รูปแบบสุดท้ายที่ระบุ ในคำอื่น ๆ//s/\n/&/3pพยายามที่จะเข้ามาแทนที่การขึ้นบรรทัดใหม่ที่สามในพื้นที่ถือด้วยตัวเองและพิมพ์ผลถ้า/foo/พื้นที่ถือในปัจจุบันตรงกับ หากการทำเช่นนี้tประสบความสำเร็จสคริปต์ก็จะแยกไปที่เลเบลnot delete ซึ่งทำหน้าที่เป็นlook และตัดคำสคริปต์ออก

ในกรณีที่ทั้งคู่/foo/และบรรทัดที่สามไม่สามารถจับคู่ร่วมกันในพื้นที่พักแม้ว่า//!gจะจะเขียนทับบัฟเฟอร์ถ้า/foo/ไม่ตรงหรือถ้ามันถูกจับคู่ก็จะเขียนทับบัฟเฟอร์ถ้า\newline ไม่ตรงกัน(ดังนั้นแทนที่/foo/ด้วย ตัวเอง) การทดสอบที่ละเอียดเล็กน้อยนี้ช่วยป้องกันบัฟเฟอร์ไม่ให้เติมเต็มโดยไม่จำเป็นสำหรับการเหยียดยาวเป็นเวลานาน/foo/และช่วยให้มั่นใจได้ว่ากระบวนการไม่ติดขัดเนื่องจากอินพุตไม่ซ้อนกัน ต่อไปนี้ในกรณีที่ไม่มี/foo/หรือ//s/\n/&/3pล้มเหลวบัฟเฟอร์จะถูกสลับอีกครั้งและทุกบรรทัด แต่สุดท้ายจะถูกลบ

บรรทัดสุดท้าย$!d- บรรทัดสุดท้าย- เป็นการสาธิตอย่างง่ายว่าsedสคริปต์จากบนลงล่างสามารถจัดการหลายกรณีได้อย่างง่ายดาย เมื่อวิธีการทั่วไปของคุณคือการตัดกรณีที่ไม่ต้องการที่เริ่มต้นด้วยวิธีที่ทั่วไปที่สุดและทำงานไปยังกรณีที่เฉพาะเจาะจงที่สุดแล้วขอบสามารถจัดการได้ง่ายขึ้นเพราะพวกเขาได้รับอนุญาตให้ผ่านไปยังจุดสิ้นสุดของสคริปต์ด้วยข้อมูลอื่น ๆ ที่คุณต้องการ ทุกอย่างจะห่อคุณด้วยข้อมูลที่คุณต้องการเท่านั้น แม้ว่าการดึงเคสขอบดังกล่าวออกจากลูปปิดอาจทำได้ยากกว่า

และนี่คือสิ่งสุดท้ายที่ฉันต้องพูดว่า: หากคุณต้องดึงไฟล์ทั้งหมดออกมาจริงๆคุณสามารถยืนทำงานให้น้อยลงได้โดยอาศัยวงจรเส้นเพื่อทำเพื่อคุณ โดยทั่วไปแล้วคุณจะใช้Next และnext สำหรับlookahead - เพราะพวกมันก้าวหน้าไปก่อนวัฏจักรของเส้น แทนที่จะใช้ลูปปิดแบบวนซ้ำซ้อนในวง - เนื่องจากsedวงรอบเป็นเพียงลูปการอ่านอย่างง่ายต่อไป - ถ้าจุดประสงค์ของคุณเพียงเพื่อรวบรวมอินพุตอย่างไม่เจาะจงก็อาจทำได้ง่ายกว่า:

sed 'H;1h;$!d;x;...'

... ซึ่งจะรวบรวมไฟล์ทั้งหมดหรือไปลอง


ข้อความด้านข้างเกี่ยวกับNและพฤติกรรมบรรทัดสุดท้าย ...

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


1
การใส่สิ่งที่ไม่มีเงื่อนไขHก่อนน่ารัก
jthill

@mikeserv ขอบคุณสำหรับข้อมูลของคุณ ฉันสามารถเห็นประโยชน์ที่เป็นไปได้ในการรักษาวงจรของวงจร แต่มันทำงานได้น้อยลงอย่างไร
dicktyr

@dicktyr เป็นอย่างดีไวยากรณ์ใช้ทางลัดบางอย่าง:a;$!{N;ba}ตามที่กล่าวไว้ข้างต้น - มันง่ายกว่าที่จะใช้แบบฟอร์มมาตรฐานในระยะยาวเมื่อคุณพยายามเรียกใช้ regexps ในระบบที่ไม่คุ้นเคย แต่นั่นไม่ใช่สิ่งที่ฉันหมายถึง: คุณใช้ลูปปิด - คุณไม่สามารถเข้าหากลางคันได้อย่างง่ายดายเมื่อคุณต้องการอย่างที่คุณอาจทำได้โดยการแตกแขนงออก - ตัดแต่งข้อมูลที่ไม่ต้องการ - และปล่อยให้วงจรเกิดขึ้น มันเหมือนสิ่งจากบนลงล่าง - ทุกสิ่งsedไม่ได้เป็นผลโดยตรงจากสิ่งที่เพิ่งทำไป บางทีคุณอาจเห็นมันแตกต่าง - แต่ถ้าคุณลองคุณอาจพบว่าสคริปต์ง่ายขึ้น
mikeserv

11

มันล้มเหลวเนื่องจากNคำสั่งมาก่อนรูปแบบตรงกัน$!(ไม่ใช่บรรทัดสุดท้าย) และหยุดทำงานก่อนที่จะทำงานใด ๆ :

ยังไม่มีข้อความ

เพิ่มบรรทัดใหม่ให้กับพื้นที่รูปแบบจากนั้นผนวกบรรทัดถัดไปของอินพุตเข้ากับพื้นที่รูปแบบ หากไม่มีอินพุตเพิ่มแล้วออกจากโหมดไม่ประมวลผลคำสั่งอีกต่อไป

สิ่งนี้สามารถแก้ไขได้อย่างง่ายดายในการทำงานกับอินพุตบรรทัดเดียวเช่นกัน (และแน่นอนว่ามีความชัดเจนมากขึ้นในทุกกรณี) เพียงแค่จัดกลุ่มคำสั่งNและbหลังรูปแบบ:

sed ':a;$!{N;ba}; [commands...]'

มันทำงานได้ดังต่อไปนี้:

  1. :a สร้างป้ายกำกับที่ชื่อว่า 'a'
  2. $! ถ้าไม่ใช่บรรทัดสุดท้าย
  3. Nผนวกบรรทัดถัดไปเข้ากับพื้นที่รูปแบบ (หรือปิดหากไม่มีบรรทัดถัดไป) และbaสาขา (ไปที่) ป้ายกำกับ 'a'

น่าเสียดายที่มันไม่สามารถพกพาได้ (เนื่องจากใช้นามสกุล GNU) แต่ทางเลือกต่อไปนี้ (แนะนำโดย @mikeserv) เป็นแบบพกพา:

sed 'H;1h;$!d;x; [commands...]'

ฉันโพสต์สิ่งนี้ไว้ที่นี่เพราะฉันไม่พบข้อมูลที่อื่นและฉันต้องการให้มันพร้อมใช้งานเพื่อให้ผู้อื่นอาจหลีกเลี่ยงปัญหาที่เกิด:a;N;$!ba;ขึ้นอย่างกว้างขวาง
dicktyr

ขอบคุณสำหรับการโพสต์! โปรดจำไว้ว่าการยอมรับคำตอบของคุณเองก็ใช้ได้เช่นกัน คุณเพียงแค่ต้องรอสักครู่ก่อนที่ระบบจะให้คุณทำ
terdon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.