แทนที่สตริงที่มีอักขระขึ้นบรรทัดใหม่


10

ด้วยbashเปลือกในไฟล์ที่มีแถวเหมือนคนดังต่อไปนี้

first "line"
<second>line and so on

ฉันต้องการแทนที่ด้วยหนึ่งครั้งหรือมากกว่านั้น"line"\n<second>ด้วยother charactersและรับในแต่ละครั้ง:

first other characters line and so on

ดังนั้นผมจึงต้องเปลี่ยนสตริงทั้งที่มีตัวอักษรพิเศษเช่น"และ<และด้วยตัวอักษรขึ้นบรรทัดใหม่

หลังจากค้นหาคำตอบอื่น ๆ ฉันพบว่าsedสามารถยอมรับการขึ้นบรรทัดใหม่ทางด้านขวาของคำสั่ง (เช่นนั้นother charactersสตริง) แต่ไม่ใช่ทางด้านซ้าย

มีวิธี (ง่ายกว่านี้ ) ในการรับผลลัพธ์นี้ด้วยsedหรือgrepไม่?


คุณทำงานด้วย mac ไหม? \nงบ ewline ที่คุณทำคือเหตุผลที่ผมถาม ผู้คนไม่ค่อยถามว่าพวกเขาสามารถทำs//\n/ตามที่คุณสามารถทำได้กับ GNU sedหรือไม่แม้ว่าคนอื่น ๆ ส่วนใหญ่sedจะปฏิเสธการหลบหนีนั้นทางด้านขวามือ ยังคงการ\nหลบหนีจะทำงานทางด้านซ้ายใน POSIX ใด ๆsedและคุณสามารถแปลได้เหมือนกันy/c/\n/แม้ว่ามันจะมีผลเช่นเดียวกับs/c/\n/gและดังนั้นจึงไม่เป็นประโยชน์เสมอไป
mikeserv

คำตอบ:


3

สามsedคำสั่งต่าง ๆ:

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

พวกเขาทั้งสามสร้างตามs///คำสั่ง ubstitution พื้นฐาน:

s/"[^"]*"\n<[^>]*>/other characters /

พวกเขาทุกคนพยายามที่จะจัดการกับบรรทัดสุดท้ายเนื่องจากsedมีแนวโน้มที่จะแตกต่างกันไปในกรณีที่ขอบ นี่คือความหมายของ$!ที่อยู่ซึ่งตรงกับทุกบรรทัดที่!ไม่ใช่บรรทัด$สุดท้าย

พวกเขายังใช้Nคำสั่ง ext เพื่อผนวกบรรทัดอินพุตถัดไปเข้ากับพื้นที่รูปแบบหลังจาก\nอักขระ ewline ทุกคนที่ได้รับsedในขณะที่จะได้เรียนรู้ที่จะพึ่งพา\nตัวละคร ewline - เพราะวิธีเดียวที่จะได้รับหนึ่งคือการวางไว้ที่นั่นอย่างชัดเจน

ทั้งสามพยายามอ่านอินพุตให้น้อยที่สุดเท่าที่จะทำได้ก่อนที่จะลงมือsedทำทันทีที่ทำได้และไม่จำเป็นต้องอ่านในไฟล์อินพุตทั้งหมดก่อนที่จะดำเนินการ

แม้ว่าพวกเขาจะทำทุกอย่างNพวกเขาทั้งสามต่างวิธีในการเรียกซ้ำ

คำสั่งแรก

คำสั่งแรกใช้การN;P;Dวนซ้ำง่ายมาก คำสั่งทั้งสามนี้มีอยู่ในตัวของ POSIX ที่เข้ากันได้sedและเสริมเข้าด้วยกัน

  • N- ตามที่กล่าวไว้แล้วNต่อท้ายบรรทัดอินพุตเข้ากับ pattern-space หลังจากแทรก\nตัวคั่น ewline
  • P- เช่นp; มันPrints pattern-space - แต่เฉพาะ\nอักขระ ewline แรกที่เกิดขึ้น ดังนั้นให้ป้อนคำสั่ง / ต่อไปนี้:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Prints เพียงอย่างใดอย่างหนึ่ง อย่างไรก็ตามด้วย ...

  • D- เช่นd; มันDeletes รูปแบบช่องว่างและเริ่มต้นวงจรวงจรอื่น ซึ่งแตกต่างจาก d , Dลบเท่านั้นถึงครั้งแรกที่เกิดขึ้น\newline ในรูปแบบพื้นที่ หากมีพื้นที่ในรูปแบบเพิ่มเติมตาม\nตัวอักษร ewline ให้sedเริ่มวงจรบรรทัดถัดไปด้วยส่วนที่เหลือ ถ้าdในตัวอย่างก่อนหน้านี้ถูกแทนที่ด้วยDยกตัวอย่างเช่นsedจะPrint ทั้งหนึ่งและสอง

คำสั่งนี้เรียกใช้ซ้ำสำหรับบรรทัดที่ไม่ตรงกับs///คำสั่ง ubstitution เท่านั้น เนื่องจากs///ubstitution ลบ\newline ที่เพิ่มด้วยNดังนั้นจะไม่มีสิ่งใดเหลืออยู่เมื่อsed Dลบพื้นที่รูปแบบ

การทดสอบสามารถทำได้เพื่อนำไปใช้Pและ / หรือDคัดเลือก แต่มีคำสั่งอื่น ๆ ที่เหมาะสมกับกลยุทธ์นั้นดีกว่า เนื่องจากการเรียกซ้ำถูกนำมาใช้เพื่อจัดการบรรทัดที่ต่อเนื่องซึ่งตรงกับเพียงส่วนหนึ่งของกฎการแทนที่ลำดับที่ต่อเนื่องของบรรทัดที่ตรงกับปลายทั้งสองของs///ubtion ไม่ทำงานได้ดี:

รับข้อมูลนี้:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... มันพิมพ์ ...

first other characters "line"
<second>other characters line and so on

อย่างไรก็ตามมันจัดการได้

first "line"
second "line"
<second>line

... ไม่เป็นไร

คำสั่งที่สอง

คำสั่งนี้คล้ายกับคำสั่งที่สามมาก ทั้งสองใช้ฉลาก:bฟาร์มปศุสัตว์ / เอสต์(ตามที่แสดงในคำตอบของ Joeseph R. ที่นี่ด้วย )และนำกลับมาใช้ใหม่ตามเงื่อนไขที่กำหนดt

  • -e :n -e- sedสคริปต์แบบพกพาจะกำหนดขอบเขต:ฉลากด้วย\newline หรือ-eคำสั่งinline xecution ใหม่
    • :n- nกำหนดป้ายชื่อ นี้สามารถกลับไปในเวลาใด ๆ กับทั้งหรือbntn
  • tn- tคำสั่ง est ส่งกลับไปยังเลเบลที่ระบุ(หรือหากไม่ได้จัดเตรียมไว้ให้ออกจากสคริปต์สำหรับบรรทัดวงจรปัจจุบัน)หากการs///แทนที่ใด ๆตั้งแต่เลเบลถูกกำหนดหรือเนื่องจากถูกเรียกใช้ครั้งล่าสุดว่าtประสบความสำเร็จ

ในคำสั่งนี้การสอบถามซ้ำเกิดขึ้นสำหรับบรรทัดที่ตรงกัน หากsedแทนที่ลวดลายสำเร็จด้วยอักขระอื่นให้sedกลับไปที่:nป้ายกำกับแล้วลองอีกครั้ง หากs///ubstitut ไม่ได้ทำการsedพิมพ์อัตโนมัติ pattern-space และเริ่มรอบบรรทัดถัดไป

สิ่งนี้มีแนวโน้มที่จะจัดการกับลำดับที่ต่อเนื่องดีกว่า ที่สุดท้ายที่ล้มเหลวพิมพ์นี้:

first other characters other characters other characters line and so on

คำสั่งที่สาม

ดังที่ได้กล่าวมาแล้วตรรกะที่นี่คล้ายกันมากกับที่ผ่านมา แต่การทดสอบมีความชัดเจนมากขึ้น

  • /"$/bn- นี่คือsedการทดสอบของ เนื่องจากbคำสั่ง ranch เป็นฟังก์ชั่นของที่อยู่นี้sedจะbกลับไปที่ ranch :nหลังจาก\newline ถูกต่อท้ายและ pattern-space ยังคงลงท้ายด้วยเครื่องหมาย"คำพูดคู่

มีการทำเพียงเล็กน้อยระหว่างNและbเท่าที่จะทำได้ด้วยวิธีนี้sedสามารถรวบรวมอินพุตได้มากเท่าที่จำเป็นเพื่อให้แน่ใจว่าบรรทัดต่อไปนี้ไม่ตรงกับกฎของคุณ s///ubstitution แตกต่างกันที่นี่ในการที่จะมีพนักงานgธง lobal - และเพื่อที่จะทำทดแทนที่จำเป็นทั้งหมดในครั้งเดียว รับอินพุตที่เหมือนกันคำสั่งนี้เอาต์พุตเหมือนกันกับครั้งสุดท้าย


ขออภัยสำหรับคำถามเล็กน้อย แต่อะไรคือความหมายDATAและคุณจะรับข้อความได้อย่างไร
BowPark

@BowPark - ในตัวอย่างนี้<<\DATA\ntext input\nDATA\nอบใน แต่ที่เป็นข้อความเท่านั้นส่งให้sedโดยเปลือกในเอกสารที่นี่ มันจะทำงานได้ดีเหมือนหรือsed 'script' filename process that writes to stdout | sed 'script'มันช่วยได้ไหม
mikeserv

ใช่ขอบคุณ! ทำไมไม่มีDทุกบรรทัดที่แก้ไขแล้วจึงเป็นสองเท่า (คุณใช้มันเท่าที่จำเป็นบางทีฉันก็ไม่ค่อยรู้จักsed)
BowPark

1
@ BowPark - คุณจะได้รับสองเท่าเมื่อไม่ใช้DเพราะDมิฉะนั้นจะDลบออกจากผลลัพธ์ที่คุณเห็นเป็นสองเท่า ฉันเพิ่งแก้ไข - และฉันอาจขยายความในนั้นด้วยในไม่ช้า
mikeserv

1
@ BowPark - ตกลงฉันได้อัปเดตแล้วและมีตัวเลือกให้ ตอนนี้อ่านและทำความเข้าใจง่ายขึ้น ฉันยังพูดถึงDสิ่งนี้อย่างชัดเจน
mikeserv

7

ดีฉันสามารถคิดสองวิธีที่เรียบง่าย แต่ไม่เกี่ยวข้องกับgrep(ซึ่งไม่ได้ทำแทนอยู่แล้ว) sedหรือ

  1. Perl

    ในการแทนที่แต่ละครั้ง"line"\n<second>ด้วยother charactersให้ใช้:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    หรือหากต้องการจัดการเหตุการณ์ที่เกิดขึ้นหลายเหตุการณ์ติดต่อกัน"line"\n<second>และแทนที่ด้วยเหตุการณ์เดียวother charactersให้ใช้:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    ตัวอย่าง:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    -00ทำให้เกิด Perl เพื่ออ่านไฟล์ในโหมด "วรรค" ซึ่งหมายความว่า "สาย" จะถูกกำหนดโดย\n\nแทนที่จะ\nเป็นหลักแต่ละย่อหน้าจะถือว่าเป็นบรรทัด การทดแทนจึงตรงกับขึ้นบรรทัดใหม่

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    แนวคิดพื้นฐานเดียวกันนี้เราได้ตั้งตัวแยกเร็กคอร์ด ( RS) เป็น\n\nslurp ไฟล์ทั้งหมดจากนั้นตัวแยกเร็กคอร์ดเอาท์พุทเป็นอะไร (มิฉะนั้นจะมีการพิมพ์บรรทัดใหม่พิเศษ) จากนั้นใช้sub()ฟังก์ชันเพื่อทำการแทนที่


2
@mikeserv? อันไหน? อย่างที่สองควรจะเป็น OP กล่าวว่าพวกเขาต้องการ "เพื่อแทนที่หนึ่งเหตุการณ์หรือมากกว่านั้น" ดังนั้นการรับประทานย่อหน้าอาจเป็นสิ่งที่พวกเขาคาดหวัง
terdon

จุดที่ดีมาก ฉันเดาว่าฉันจะมุ่งเน้นมากขึ้นและได้รับในแต่ละครั้งแต่ฉันคิดว่ามันไม่ชัดเจนว่าควรจะมีการทดแทนหนึ่งครั้งต่อการเกิดขึ้นหนึ่งครั้งหรือการทดแทนหนึ่งครั้งต่อลำดับเหตุการณ์ที่เกิดขึ้น ... @ BowPark?
mikeserv

จำเป็นต้องมีการแทนที่หนึ่งครั้งต่อการเกิดขึ้น
BowPark

@ BowPark ตกลงแล้ววิธี perl แรกหรือ awk ทั้งสองควรทำงาน พวกเขาไม่ให้ผลลัพธ์ที่ต้องการหรือไม่
terdon

มันทำงานขอขอบคุณ แต่บรรทัดที่สามกับที่ควรจะเป็นawk print;}' fileฉันจำเป็นต้องหลีกเลี่ยง Perl และควรใช้sedมากกว่าอย่างไรก็ตามคุณแนะนำทางเลือกที่ดี
BowPark

6

อ่านไฟล์ทั้งหมดและทำการแทนที่แบบโกลบอล:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last

ใช่. มันใช้งานได้ แต่จะเกิดอะไรขึ้นถ้าฉันมีเหตุการณ์หลายครั้ง?
BowPark

ใช่แล้ว คงที่
เกล็นแจ็

1
ขอโทษที่ nitpick อีกครั้ง แต่${cmds}เป็น GNU เฉพาะ - อื่น ๆ ส่วนใหญ่sedที่จะต้องมีการ\newline หรือ-eการหยุดพักระหว่างและp }คุณสามารถหลีกเลี่ยงวงเล็บเหลี่ยมทั้งหมด - และแบบพกพา - และแม้กระทั่งหลีกเลี่ยงการใส่\nอักขระ ewline พิเศษในบรรทัดแรกเช่น:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv

ฉันทดสอบแล้วดูเหมือนว่าจะไม่พกพาได้ มันพิมพ์บรรทัดใหม่พิเศษที่จุดเริ่มต้นของการส่งออก แต่ผลลัพธ์ที่ถูกต้องใน GNU
BowPark

ในการลบบรรทัดใหม่ที่เป็นผู้นำ: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- อย่างไรก็ตามสิ่งนี้กำลังได้รับความเสียหาย
เกล็นแจ็

3

นี่คือตัวแปรของคำตอบของ Glennที่จะใช้ได้ถ้าคุณมีเหตุการณ์ที่เกิดขึ้นติดต่อกันหลายครั้ง (ใช้ได้กับ GNU sedเท่านั้น):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

:xเป็นเพียงป้ายแตกแขนง โดยพื้นฐานแล้วสิ่งนี้ทำคือตรวจสอบบรรทัดหลังการทดแทนและหากยังคงตรงกัน"line"จะแยกสาขากลับไปที่:xป้ายกำกับ (นั่นคือสิ่งที่bxทำ) และเพิ่มอีกบรรทัดหนึ่งลงในบัฟเฟอร์และเริ่มประมวลผล


@mikeserv โปรดระบุเกี่ยวกับสิ่งที่คุณหมายถึง มันใช้งานได้สำหรับฉัน
โจเซฟอาร์

@mikeserv ฉันขอโทษฉันไม่รู้จริงๆว่าคุณกำลังพูดถึงอะไร ฉันคัดลอกบรรทัดโค้ดด้านบนกลับไปยังเทอร์มินัลและทำงานได้อย่างถูกต้อง
โจเซฟอาร์

1
หดกลับ - เห็นได้ชัดว่าทำงานได้ใน GNU sedซึ่งใช้การจัดการป้ายกำกับที่ไม่ใช่ POSIX ของมันมากพอที่จะยอมรับช่องว่างเป็นตัวคั่นสำหรับการประกาศฉลาก คุณควรทราบว่าที่อื่น ๆsedจะล้มเหลวมี - Nและจะล้มเหลว GNU sedแบ่งหลักเกณฑ์ POSIX เพื่อพิมพ์พื้นที่ว่างแบบก่อนที่จะเลิกบนNบรรทัดสุดท้าย แต่ POSIX ทำให้ชัดเจนว่าหากNคำสั่งอ่านบนบรรทัดสุดท้ายไม่ควรพิมพ์สิ่งใด
mikeserv

หากคุณแก้ไขโพสต์เพื่อระบุ GNU ฉันจะย้อนกลับการโหวตของฉันและลบความคิดเห็นเหล่านี้ นอกจากนี้มันอาจคุ้มค่าที่จะเรียนรู้เกี่ยวกับvคำสั่งของ GNU ที่แตกต่างกันsedแต่ไม่มีตัวเลือกใน GNU รุ่น 4 ขึ้นไป
mikeserv

1
ในกรณีที่ผมจะนำเสนออีกหนึ่ง - นี้สามารถทำได้ portably sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'ที่ชอบ:
mikeserv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.