ต้องการทดแทนสิ่งแรกที่เกิดขึ้นกับ sed


26

ไฟล์ต้นฉบับ

claudio
antonio
claudio
michele

ฉันต้องการเปลี่ยนเฉพาะการเกิดขึ้นครั้งแรกของ "claudio" กับ "claudia" ดังนั้นผลลัพธ์ของไฟล์

claudia
antonio
claudio
michele

ฉันเหนื่อย

sed -e '1,/claudio/s/claudio/claudia/' nomi

แต่ดำเนินการทดแทนทั่วโลกทำไม?


ดูที่นี่linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/ …และinfo sed: ( 0,/REGEXP/: หมายเลขบรรทัด 0 สามารถใช้ในการระบุที่อยู่เช่น0,/REGEXP/นั้นsedจะพยายามจับคู่ REGEXP ในบรรทัดแรกเช่นกัน0,/REGEXP/คือ คล้ายกับ1,/REGEXP/ยกเว้นว่าถ้า ADDR2 ตรงกับบรรทัดแรกของอินพุต 0 / REGEXP / แบบฟอร์มจะพิจารณาให้จบช่วงในขณะที่ 1 / REGEXP / รูปแบบจะตรงกับจุดเริ่มต้นของช่วงและทำให้ช่วงขยาย มากถึงการเกิดขึ้นครั้งที่สองของนิพจน์ทั่วไป)
jimmij


awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomiควรทำอย่างไร
อดัมแคทซ์

คำตอบ:


23

หากคุณใช้ GNU sedลอง:

sed -e '0,/claudio/ s/claudio/claudia/' nomi

sedไม่เริ่มตรวจสอบ regex ที่สิ้นสุดช่วงจนกระทั่งหลังจากบรรทัดที่เริ่มช่วงนั้น

จากman sed(POSIX manpage, เน้นเหมือง):

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

การใช้ awk

ช่วงawkงานมากขึ้นตามที่คุณคาดหวัง:

$ awk 'NR==1,/claudio/{sub(/claudio/, "claudia")} 1' nomi
claudia
antonio
claudio
michele

คำอธิบาย:

  • NR==1,/claudio/

    เป็นช่วงที่เริ่มต้นด้วยเส้นที่ 1 claudioและจบลงด้วยการเกิดขึ้นครั้งแรกของ

  • sub(/claudio/, "claudia")

    ขณะที่เราอยู่ในช่วงคำสั่งทดแทนนี้จะถูกดำเนินการ

  • 1

    ชวเลขลับของ awk นี้สำหรับการพิมพ์บรรทัด


1
ที่ถือว่า GNU sedแม้ว่า
Stéphane Chazelas

@ StéphaneChazelasนอกจากนี้ยังใช้งานได้หากมีการตั้งค่า POSIXLY_CORRECT แต่ฉันเดาว่าไม่ได้มีความหมายเท่าที่ฉันต้องการ ตอบรับการปรับปรุง (ฉันขาดเครื่องทดสอบ BSD)
John1024

awk can, IMO นั้นง่ายกว่าด้วยตัวแปรสถานะบูลีน:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman

@glennjackman หรือawk !x{x=sub(/claudio/,"claudia")}1

ฉันไม่สามารถใช้ตัวคั่นอื่นในส่วนแรกได้สำเร็จ:0,/claudio/
Pat Myron

4

ต่อไปนี้เป็นความพยายามทางโปรแกรมเพิ่มเติม 2 อย่างด้วย sed: พวกเขาทั้งคู่อ่านไฟล์ทั้งหมดเป็นสตริงเดียวจากนั้นการค้นหาจะแทนที่ไฟล์แรกเท่านั้น

sed -n ':a;N;$bb;ba;:b;s/\(claudi\)o/\1a/;p' file
sed -n '1h;1!H;${g;s/\(claudi\)o/\1a/;p;}' file

ด้วยความเห็น:

sed -n '                # don't implicitly print input
  :a                    # label "a"
  N                     # append next line to pattern space
  $bb                   # at the last line, goto "b"
  ba                    # goto "a"
  :b                    # label "b"
  s/\(claudi\)o/\1a/    # replace
  p                     # and print
' file
sed -n '                # don't implicitly print input
  1h                    # put line 1 in the hold space
  1!H                   # for subsequent lines, append to hold space
  ${                    # on the last line
    g                     # put the hold space in pattern space
    s/\(claudi\)o/\1a/    # replace
    p                     # print
  }
' file

3

GNU เวอร์ชันใหม่sedรองรับ-zตัวเลือกนี้

โดยปกติ sed อ่านบรรทัดโดยการอ่านสตริงของอักขระจนถึงอักขระสิ้นสุดบรรทัด (บรรทัดใหม่หรือการขึ้นบรรทัดใหม่)
รุ่น GNU ของ sed เพิ่มคุณสมบัติในรุ่น 4.2.2 เพื่อใช้อักขระ "NULL" แทน สิ่งนี้มีประโยชน์หากคุณมีไฟล์ที่ใช้ NULL เป็นตัวแยกเรคคอร์ด ยูทิลิตี GNU บางตัวสามารถสร้างเอาต์พุตที่ใช้ NULL แทนบรรทัดใหม่เช่น "find. -print0" หรือ "grep -lZ"

คุณสามารถใช้ตัวเลือกนี้เมื่อคุณต้องการsedทำงานผ่านสายต่าง ๆ

echo 'claudio
antonio
claudio
michele' | sed -z 's/claudio/claudia/'

ผลตอบแทน

claudia
antonio
claudio
michele

1

คุณสามารถใช้awkกับแฟล็กเพื่อทราบว่าการแทนที่เสร็จสิ้นแล้ว ถ้าไม่ดำเนินการต่อ:

$ awk '!f && /claudio/ {$0="claudia"; f=1}1' file
claudia
antonio
claudio
michele

1

จริง ๆ แล้วมันง่ายจริง ๆ ถ้าคุณเพิ่งตั้งค่าล่าช้าเล็กน้อย - ไม่จำเป็นต้องไปถึงส่วนขยายที่ไม่น่าเชื่อถือ:

sed '$H;x;1,/claudio/s/claudio/claudia/;1d' <<\IN
claudio
antonio
claudio
michele
IN

ที่เพิ่ง defers บรรทัดแรกไปที่สองและที่สองที่สามและอื่น ๆ

มันพิมพ์:

claudia
antonio
claudio
michele

1

และอีกหนึ่งตัวเลือก

sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/claudio/claudia/;}" -- nomi

ข้อดีคือมันใช้อัญประกาศคู่เพื่อให้คุณสามารถใช้ตัวแปรภายในเช่น

export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi

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

1

สิ่งนี้สามารถทำได้โดยไม่มีพื้นที่พักและไม่มีการต่อบรรทัดทั้งหมดในพื้นที่รูปแบบ:

sed -n '/claudio/{s/o/a/;bx};p;b;:x;p;n;bx' nomi

คำอธิบาย: เราพยายามที่จะหา "เคลาดิโอ" และถ้าเราทำมันเรากระโดดลงไปในพิมพ์ขนาดเล็กโหลดวงระหว่างและ:x bxมิฉะนั้นเราจะพิมพ์และรีสตาร์ทสคริปต์ด้วยบรรทัดถัดไป

sed -n '      # do not print lines by default
  /claudio/ { # on lines that match "claudio" do ...
    s/o/a/    # replace "o" with "a"
    bx        # goto label x
  }           # end of do block
  p           # print the pattern space
  b           # go to the end of the script, continue with next line
  :x          # the label x for goto commands
  p           # print the pattern space
  n           # load the next line in the pattern space (clearing old contents)
  bx          # goto the label x
  ' nomi

1
sed -n '/claudia/{p;Q}'

sed -n '           # don't print input
    /claudia/      # regex search
    {              # when match is found do
    p;             # print line
    Q              # quit sed, don't print last buffered line
    {              # end do block

1
คุณใส่ใจอ่านคำถามหรือไม่
don_crissti

1

Sumary

ไวยากรณ์ GNU:

sed '/claudio/{s//claudia/;:p;n;bp}' file

หรือแม้กระทั่ง (เพื่อใช้เพียงครั้งเดียวคำที่จะถูกแทนที่:

sed '/\(claudi\)o/{s//\1a/;:p;n;bp}' file

หรือในไวยากรณ์ POSIX:

sed -e '/claudio/{s//claudia/;:p' -e 'n;bp' -e '}' file

ทำงานบน sed ใด ๆ ประมวลผลเพียงหลายบรรทัดเท่าที่จำเป็นเพื่อค้นหาบรรทัดแรกclaudioทำงานแม้ว่าclaudioจะอยู่ในบรรทัดแรกและสั้นลงเนื่องจากใช้เพียงหนึ่งสตริง regex

รายละเอียด

หากต้องการเปลี่ยนเพียงหนึ่งบรรทัดคุณต้องเลือกเพียงหนึ่งบรรทัด

ใช้ a 1,/claudio/(จากคำถามของคุณ) เลือก:

  • จากบรรทัดแรก (โดยไม่มีเงื่อนไข)
  • ไปต่อไปclaudioบรรทัดที่มีสตริง
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

$ sed -n '1,/claudio/{p}' file
claudio 1
antonio 2
claudio 3

ในการเลือกบรรทัดใด ๆที่มีclaudioให้ใช้:

$ sed -n `/claudio/{p}` file
claudio 1
claudio 3

และหากต้องการเลือกเฉพาะไฟล์แรก claudioให้ใช้:

sed -n '/claudio/{p;q}' file
claudio 1

จากนั้นคุณสามารถทำการทดแทนบนบรรทัดนั้นเท่านั้น:

sed '/claudio/{s/claudio/claudia/;q}' file
claudia 1

ซึ่งจะเปลี่ยนเฉพาะการเกิดขึ้นครั้งแรกของการจับคู่ regex บนบรรทัดแม้ว่าจะมีมากกว่าหนึ่งรายการในบรรทัดแรกที่ตรงกับ regex

แน่นอน/claudio/regex สามารถทำให้ง่ายขึ้นไปที่:

$ sed '/claudio/{s//claudia/;q}' file
claudia 1

และจากนั้นสิ่งเดียวที่ขาดหายไปคือการพิมพ์บรรทัดอื่นทั้งหมดที่ไม่ได้แก้ไข:

sed '/claudio/{s//claudia/;:p;n;bp}' file
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.