จะใช้ sed เพื่อแทนที่เฉพาะสิ่งที่เกิดขึ้นครั้งแรกในไฟล์ได้อย่างไร?


217

ฉันต้องการอัปเดตไฟล์ต้นฉบับ C ++ จำนวนมากพร้อมกับคำสั่งพิเศษเพิ่มเติมก่อนที่จะมี #includes อยู่ สำหรับงานประเภทนี้ปกติฉันจะใช้สคริปต์ bash ขนาดเล็กที่มี sed เพื่อเขียนไฟล์อีกครั้ง

ฉันจะได้รับsedการแทนที่เพียงแค่การเกิดขึ้นครั้งแรกของสตริงในไฟล์แทนที่การเกิดขึ้นทุกครั้งได้อย่างไร

ถ้าฉันใช้

sed s/#include/#include "newfile.h"\n#include/

มันมาแทนที่ #includes ทั้งหมด

ข้อเสนอแนะทางเลือกเพื่อให้บรรลุในสิ่งเดียวกันก็ยินดีต้อนรับ

คำตอบ:


135
 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

หรือถ้าคุณต้องการ: บันทึกของบรรณาธิการ: ทำงานร่วมกับGNU sedเท่านั้น

sed '0,/foo/s//bar/' file 

แหล่ง


86
ฉันคิดว่าฉันชอบโซลูชัน 'หรือถ้าคุณชอบ' นอกจากนี้ยังเป็นการดีที่จะอธิบายคำตอบ - และทำให้คำตอบตอบคำถามนั้นโดยตรงจากนั้นพูดคุยโดยทั่วไปแทนที่จะพูดคุยกันโดยทั่วไปเท่านั้น แต่คำตอบที่ดี
Jonathan Leffler

7
FYI สำหรับผู้ใช้ Mac คุณต้องแทนที่ 0 ด้วย 1 ดังนั้น: sed '1, / RE / s // to_that /' ไฟล์
mhost

1
@mhost ไม่มันจะแทนที่ถ้าพบ pattern ในบรรทัด # 1 และไม่ใช่ถ้า pattern อยู่ที่บรรทัดอื่น แต่ยังคงเป็น pattern แรกที่พบ ปล. ควรพูดถึงว่ามัน0,ใช้ได้กับgnu sed
Jotne

11
มีคนช่วยอธิบายวิธีการแก้ปัญหา 'หรือถ้าคุณ Preeer'? ฉันไม่รู้ว่าจะใส่รูปแบบ "จาก"
Jean-Luc Nacif Coelho

3
@ Jean-LucNacifCoelho: การใช้s//- คือregex ที่ว่างเปล่า - หมายความว่า regex ที่นำไปใช้ล่าสุดถูกนำมาใช้ซ้ำโดยปริยาย ในกรณีREนี้ ทางลัดที่สะดวกนี้หมายความว่าคุณไม่ต้องทำซ้ำ regex-end ยุติการsโทรของคุณ
mklement0

289

sedสคริปต์ที่จะแทนที่เกิดขึ้นครั้งแรกของ "แอปเปิ้ล" โดย "กล้วย"

ตัวอย่าง

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

นี่คือสคริปต์อย่างง่าย: หมายเหตุบรรณาธิการ: ใช้งานได้กับGNU sedเท่านั้น

sed '0,/Apple/{s/Apple/Banana/}' input_filename

พารามิเตอร์สองตัวแรก0และ/Apple/เป็นตัวระบุช่วง s/Apple/Banana/คือสิ่งที่จะดำเนินการภายในช่วงนั้น ดังนั้นในกรณีนี้ "อยู่ในช่วงเริ่มต้น ( 0) จนถึงอินสแตนซ์แรกของApple, แทนที่Appleด้วยBananaเฉพาะอันแรกเท่านั้นที่Appleจะถูกแทนที่

ประวัติความเป็นมา: ในแบบดั้งเดิมsedระบุช่วงคือยัง "เริ่มต้นที่นี่" และ "จบที่นี่" (รวม) อย่างไรก็ตาม "เริ่มต้น" ที่ต่ำที่สุดคือบรรทัดแรก (บรรทัดที่ 1) และหาก "จุดสิ้นสุดที่นี่" เป็น regex จะพยายามจับคู่บนบรรทัดถัดไปหลังจาก "เริ่มต้น" ดังนั้นจุดสิ้นสุดที่เร็วที่สุดที่เป็นไปได้คือบรรทัด 2. ดังนั้นเมื่อรวมช่วงขอบเขตช่วงที่เล็กที่สุดที่เป็นไปได้คือ "2 บรรทัด" และช่วงเริ่มต้นที่เล็กที่สุดคือทั้งบรรทัดที่ 1 และ 2 (เช่นหากมีการเกิดขึ้นที่บรรทัดที่ 1 การเกิดขึ้นที่บรรทัดที่ 2 จะไม่เปลี่ยนด้วย ) GNUsed เพิ่มส่วนขยายของตัวเองเพื่อให้ระบุการเริ่มต้นเป็น "หลอก" line 0เพื่อให้จุดสิ้นสุดของช่วงสามารถline 1ทำให้ช่วงของ "เพียงบรรทัดแรก"

หรือเวอร์ชันที่เรียบง่าย (RE ที่ว่างเปล่า//หมายถึงการใช้งานซ้ำตามที่ระบุไว้ก่อนหน้าดังนั้นนี่จึงเทียบเท่า):

sed '0,/Apple/{s//Banana/}' input_filename

และวงเล็บปีกกาเป็นทางเลือกสำหรับsคำสั่งดังนั้นสิ่งนี้ก็เท่ากับ:

sed '0,/Apple/s//Banana/' input_filename

ทั้งหมดนี้ทำงานกับ GNU sedเท่านั้น

นอกจากนี้คุณยังสามารถติดตั้ง GNU sed บน OS X โดยใช้ brew install gnu-sedHomebrew


167
แปลเป็นภาษามนุษย์: เริ่มต้นที่บรรทัด 0 ต่อไปจนกว่าคุณจะจับคู่ 'Apple' ดำเนินการทดแทนในวงเล็บปีกกา cfr: grymoire.com/Unix/Sed.html#uh-29
mariotomo

8
ใน OS X ฉันได้รับsed: 1: "…": bad flag in substitute command: '}'
ELLIOTTCABLE

5
@ELLIOTTCABLE บน OS X sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'ใช้ จากคำตอบของ @ MikhailVS (ในปัจจุบัน) วิธีลงด้านล่าง
djb

8
ทำงานโดยไม่ใส่วงเล็บด้วย:sed '0,/foo/s/foo/bar/'
Innokenty

5
ฉันได้รับsed: -e expression #1, char 3: unexpected '' พร้อมสิ่งนี้
Jonathan

56
sed '0,/pattern/s/pattern/replacement/' filename

สิ่งนี้ใช้ได้สำหรับฉัน

ตัวอย่าง

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

หมายเหตุจากบรรณาธิการ: ทั้งสองทำงานร่วมกับGNU sedเท่านั้น


2
@Landys ยังคงแทนที่อินสแตนซ์ในบรรทัดอื่นด้วย ไม่ใช่แค่ตัวอย่างแรก
sarat

1
@sarat ใช่คุณพูดถูก sed '1,/pattern/s/pattern/replacement/' filenameใช้งานได้เฉพาะเมื่อ "รูปแบบจะไม่เกิดขึ้นในบรรทัดแรก" บน Mac ฉันจะลบความคิดเห็นก่อนหน้าเนื่องจากไม่ถูกต้อง รายละเอียดสามารถพบได้ที่นี่ ( linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… ) คำตอบของ Andy ใช้งานได้กับ GNU เท่านั้น แต่ไม่ใช่ใน Mac
Landys

45

ภาพรวมในหลาย ๆ ที่เป็นประโยชน์คำตอบที่มีอยู่ , ครบครันด้วยคำอธิบาย :

ตัวอย่างที่นี่ใช้กรณีการใช้งานที่ง่ายขึ้น: แทนที่คำว่า 'foo' ด้วย 'bar' ในบรรทัดแรกที่ตรงกันเท่านั้น
เนื่องจากการใช้สตริง ANSI C-ยกมา ( $'...')เพื่อให้เส้นที่นำเข้าตัวอย่างbash, kshหรือzshจะถือว่าเป็นเปลือก


GNU sedเท่านั้น:

เบน Hoffstein ของ anwswerแสดงให้เราเห็นว่า GNU ให้ขยายไปยังสเปคสำหรับ POSIXsedที่ช่วยให้ต่อไปนี้รูปแบบที่ 2 ที่อยู่ : 0,/re/( reหมายถึงการแสดงออกปกติโดยพลการที่นี่)

0,/re/อนุญาตให้ regex จับคู่กับบรรทัดแรกได้เช่นกัน กล่าวอีกนัยหนึ่ง: ที่อยู่ดังกล่าวจะสร้างช่วงจากบรรทัดที่ 1 ถึงและรวมถึงบรรทัดที่ตรงกับre- ไม่ว่าจะreเกิดขึ้นในบรรทัดที่ 1 หรือในบรรทัดที่ตามมาใด ๆ

  • ตัดกับส่วนนี้ด้วยรูปแบบที่สอดคล้องกับ POSIX 1,/re/ซึ่งสร้างช่วงที่จับคู่จากบรรทัดที่ 1 จนถึงและรวมถึงบรรทัดที่จับคู่reในบรรทัดถัดไป กล่าวอีกนัยหนึ่ง: สิ่งนี้จะไม่ตรวจจับการเกิดขึ้นครั้งแรกของการreแข่งขันหากมันเกิดขึ้นในบรรทัดที่1และยังป้องกันการใช้ชวเลขและ//เพื่อนำ regex ที่ใช้ล่าสุดมาใช้ใหม่ (ดูจุดถัดไป) 1

หากคุณรวม0,/re/อยู่กับs/.../.../(ทดแทน) โทรที่ใช้เหมือนกันแสดงออกปกติคำสั่งของคุณจะมีประสิทธิภาพเพียงดำเนินการเปลี่ยนตัวในครั้งแรกreที่สายการแข่งขัน
sedให้ความสะดวกสบายทางลัดสำหรับการนำการแสดงออกปกตินำมาใช้มากที่สุดเมื่อเร็ว ๆ นี้ : มีที่ว่างเปล่า//คู่คั่น,

$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

คุณลักษณะ POSIX เท่านั้นเท่านั้นsedเช่น BSD (macOS)sed (จะทำงานร่วมกับGNU sed ):

เนื่องจาก0,/re/ไม่สามารถใช้รูปแบบและ1,/re/จะไม่ตรวจสอบreถ้ามันเกิดขึ้นที่จะเกิดขึ้นในบรรทัดแรกมาก (ดูด้านบน) การจัดการพิเศษสำหรับสายที่ 1 จะต้อง

คำตอบของ MikhailVSกล่าวถึงเทคนิคใส่ตัวอย่างที่เป็นรูปธรรมที่นี่:

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

บันทึก:

  • //ทางลัดว่างเปล่า regex มีการใช้งานสองครั้งที่นี่: หนึ่งครั้งสำหรับจุดสิ้นสุดของช่วงและหนึ่งครั้งในการsโทร ในทั้งสองกรณี regex fooจะถูกนำมาใช้ซ้ำโดยปริยายทำให้เราไม่ต้องทำซ้ำซึ่งทำให้ทั้งรหัสสั้นและบำรุงรักษาได้มากกว่า

  • POSIX sedต้องการขึ้นบรรทัดใหม่ที่เกิดขึ้นจริงหลังจากที่ฟังก์ชั่นบางอย่างเช่นหลังจากที่ชื่อของป้ายหรือแม้กระทั่งการละเลยของตนเป็นกรณีที่มีtที่นี่ การแบ่งสคริปต์อย่างมีกลยุทธ์ออกเป็นหลาย-eตัวเลือกเป็นทางเลือกในการใช้การขึ้นบรรทัดใหม่จริง: วาง-eสคริปต์แต่ละอันที่บรรทัดใหม่จะต้องไป

1 s/foo/bar/แทนที่fooในบรรทัดที่ 1 เท่านั้นหากพบว่ามี หากเป็นเช่นนั้นให้tแยกไปที่ส่วนท้ายของสคริปต์ (ข้ามคำสั่งที่เหลืออยู่บนบรรทัด) ( tฟังก์ชั่นแยกสาขาไปยังป้ายกำกับเฉพาะเมื่อการsโทรล่าสุดทำการทดแทนจริงในกรณีที่ไม่มีป้ายกำกับเช่นกรณีที่นี่จุดสิ้นสุดของสคริปต์จะถูกแยกเป็น)

ที่เกิดขึ้นเมื่อช่วงที่อยู่1,//ซึ่งปกติจะพบว่าเกิดขึ้นครั้งแรกที่เริ่มต้นจากสาย 2จะไม่ตรงและช่วงจะไม่ได้2รับการดำเนินการเพราะอยู่จะถูกประเมินเมื่อบรรทัดปัจจุบันที่มีอยู่แล้ว

ในทางกลับกันหากไม่มีการจับคู่ในบรรทัดที่ 1 1,// จะถูกป้อนและจะค้นหาการจับคู่แรกที่แท้จริง

ผลกระทบสุทธิเป็นเช่นเดียวกับ GNU sed's 0,/re/: เพียงเกิดขึ้นครั้งแรกจะถูกแทนที่ไม่ว่าจะเกิดขึ้นในบรรทัดที่ 1 หรืออื่น ๆ


วิธีที่ไม่ใช่ช่วง

คำตอบ Potong ของแสดงให้เห็นถึงห่วงเทคนิคที่หลีกเลี่ยงความจำเป็นสำหรับช่วงที่ ; เนื่องจากเขาใช้ไวยากรณ์GNU ต่อ sedไปนี้เป็นสิ่งที่เทียบเท่ากับPOSIX :

เทคนิควนรอบ 1: ในนัดแรกทำการทดแทนจากนั้นเข้าสู่วงที่พิมพ์เส้นที่เหลือตามที่เป็นอยู่ :

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

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

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

1 1.61803ให้ตัวอย่างของสิ่งที่เกิดขึ้นกับ1,/re/โดยมีและไม่มีสิ่งต่อไปนี้s//:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'ผลตอบแทน$'1bar\n2bar'; กล่าวคือทั้งสองบรรทัดได้รับการอัปเดตเนื่องจากหมายเลขบรรทัด1ตรงกับบรรทัดที่ 1 และ regex /foo/- จุดสิ้นสุดของช่วง - จากนั้นจะค้นหาเฉพาะการเริ่มต้นในบรรทัดถัดไป ดังนั้นทั้งสองบรรทัดจะถูกเลือกในกรณีนี้และทำการs/foo/bar/ทดแทนในทั้งสองบรรทัด
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' ล้มเหลว : ด้วยsed: first RE may not be empty(BSD / macOS) และsed: -e expression #1, char 0: no previous regular expression(GNU) เนื่องจากในขณะที่บรรทัดที่ 1 กำลังถูกประมวลผล (เนื่องจากหมายเลขบรรทัดที่1เริ่มต้นช่วง) ยังไม่มีการใช้ regex ดังนั้น//ไม่ได้อ้างถึงอะไร
ด้วยข้อยกเว้นของ GNU sed's พิเศษ0,/re/ไวยากรณ์ใด ๆช่วงที่เริ่มต้นด้วยหมายเลขบรรทัด//ได้อย่างมีประสิทธิภาพป้องกันการใช้


25

คุณสามารถใช้ awk ทำสิ่งที่คล้ายกัน ..

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

คำอธิบาย:

/#include/ && !done

รันคำสั่งการกระทำระหว่าง {} เมื่อบรรทัดตรงกับ "#include" และเรายังไม่ได้ประมวลผล

{print "#include \"newfile.h\""; done=1;}

สิ่งนี้จะพิมพ์ #include "newfile.h" เราต้องหลีกเลี่ยงคำพูด จากนั้นเราตั้งค่าตัวแปรเรียบร้อยแล้วเป็น 1 ดังนั้นเราจึงไม่เพิ่มการรวมเพิ่มเติม

1;

ซึ่งหมายความว่า "พิมพ์บรรทัด" - ค่าเริ่มต้นการกระทำที่ว่างเปล่าเพื่อพิมพ์ $ 0 ซึ่งพิมพ์ออกมาทั้งบรรทัด สายการบินหนึ่งและง่ายต่อการเข้าใจกว่า IMO sed :-)


4
คำตอบนี้เป็นแบบพกพามากขึ้นกว่าการแก้ sed ซึ่งพึ่งพา GNU sed ฯลฯ .. (เช่น sed ใน OS-X ครับ!)
เจย์เทย์เลอร์

1
นี่เป็นสิ่งที่เข้าใจได้มากขึ้น แต่สำหรับฉันแล้วมันเพิ่มบรรทัดแทนที่จะแทนที่; ใช้คำสั่ง: awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
ธัญพืชนักฆ่า

เดียวกันที่นี่คำสั่งที่เข้าใจมากที่สุด แต่จะเพิ่มบรรทัดข้างต้นสตริงพบแทนการแทนที่
Flion

1
คำตอบนั้นอ่านง่ายมาก นี่คือเวอร์ชันของฉันที่แทนที่สตริงแทนที่จะเพิ่มบรรทัดใหม่ awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
Orsiris de Jong

18

ค่อนข้างเป็นคอลเลกชันที่ครอบคลุมของคำตอบในlinuxtopia sed คำถามที่พบบ่อย นอกจากนี้ยังเน้นว่าคำตอบบางคำที่ผู้ใช้ระบุไม่สามารถทำงานกับรุ่นที่ไม่ใช่ GNU เช่น

sed '0,/RE/s//to_that/' file

ในรุ่นที่ไม่ใช่ของ GNU จะต้องมี

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

อย่างไรก็ตามรุ่นนี้ใช้ไม่ได้กับ gnu sed

นี่คือเวอร์ชันที่ใช้งานได้กับทั้งสอง:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

อดีต:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename

แท้จริงแล้วผ่านการทดสอบการทำงานบน Ubuntu Linux v16 และ FreeBSD v10.2 ขอบคุณ.
Sopalajo de Arrierez

12
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

วิธีการทำงานของสคริปต์นี้: สำหรับบรรทัดระหว่าง 1 ถึง#includeบรรทัดแรก(หลังจากบรรทัดที่ 1) หากบรรทัดขึ้นต้นด้วย#includeให้เติมบรรทัดที่ระบุไว้ล่วงหน้า

อย่างไรก็ตามหาก#includeบรรทัดแรกอยู่ในบรรทัดที่ 1 ทั้งคู่ที่ 1 และบรรทัดถัดไป#includeจะมีการต่อบรรทัด หากคุณกำลังใช้ GNU sedมันมีส่วนขยายโดยที่0,/^#include/(แทน1,) จะทำสิ่งที่ถูกต้อง


11

เพียงเพิ่มจำนวนการเกิดในตอนท้าย:

sed s/#include/#include "newfile.h"\n#include/1

7
น่าเสียดายที่นี่ใช้งานไม่ได้ มันจะแทนที่สิ่งที่เพิ่งเกิดขึ้นในแต่ละบรรทัดของไฟล์และไม่ใช่สิ่งที่เกิดขึ้นครั้งแรกในไฟล์
David Dibben

1
นอกจากนี้ยังเป็นส่วนขยาย sed ของ GNU ไม่ใช่คุณลักษณะ sed มาตรฐาน
Jonathan Leffler

10
อืม ... เวลาผ่านไป POSIX 2008/2013 สำหรับsedระบุคำสั่งแทนที่ด้วย: [2addr]s/BRE/replacement/flagsและโปรดสังเกตว่า "ค่าของแฟล็กต้องเป็นศูนย์หรือมากกว่า: n การ แทนที่สำหรับการเกิดขึ้นที่ n ของ BRE ที่พบภายในพื้นที่รูปแบบเท่านั้น" ดังนั้นอย่างน้อยที่สุดใน POSIX 2008 การติดตาม1ไม่ใช่sedส่วนขยายGNU ที่จริงแล้วแม้จะอยู่ในมาตรฐานSUS / POSIX 1997แต่ก็ยังได้รับการสนับสนุนดังนั้นฉันจึงออกจากสายการผลิตไม่ดีในปี 2008
Jonathan Leffler

7

ทางออกที่เป็นไปได้:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :a
    n
    ba

คำอธิบาย:

  • อ่านบรรทัดจนกว่าเราจะพบ #include พิมพ์บรรทัดเหล่านี้จากนั้นเริ่มรอบใหม่
  • แทรกบรรทัดรวมใหม่
  • ป้อนลูปที่เพิ่งอ่านบรรทัด (โดยค่าเริ่มต้น sed จะพิมพ์บรรทัดเหล่านี้) เราจะไม่กลับไปที่ส่วนแรกของสคริปต์จากที่นี่

sed: file me4.sed line 4: ":" lacks a label
rogerdpack

เห็นได้ชัดว่ามีบางอย่างเปลี่ยนไปในเวอร์ชันล่าสุดและไม่อนุญาตให้ใช้ป้ายกำกับที่ว่างเปล่า อัปเดตคำตอบ
mitchnull

4

ฉันรู้ว่านี่เป็นบทความเก่า แต่ฉันมีวิธีแก้ปัญหาที่ฉันเคยใช้:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

โดยทั่วไปใช้ grep เพื่อพิมพ์เหตุการณ์แรกและหยุดอยู่ที่นั่น นอกจากนี้พิมพ์หมายเลขบรรทัดเช่น5:lineนอกจากพิมพ์หมายเลขบรรทัดคือไปป์ที่อยู่ใน sed และลบ: และอะไรก็ตามหลังจากนั้นคุณเหลือเพียงแค่หมายเลขบรรทัด ไพพ์ที่อยู่ใน sed ซึ่งเพิ่ม s /.*/ แทนที่ไปยังหมายเลขสิ้นสุดซึ่งส่งผลให้สคริปต์ 1 บรรทัดซึ่งถูกไพพ์ลงใน sed ล่าสุดเพื่อรันเป็นสคริปต์บนไฟล์

ดังนั้นหาก regex = #includeและแทนที่ = blahและเกิดขึ้นครั้งแรกพบ grep อยู่บนเส้น 5 แล้วข้อมูลประปาไปยังหน้าล่าสุด sed 5s/.*/blah/จะเป็น

ทำงานแม้ว่าการเกิดขึ้นครั้งแรกจะอยู่ในบรรทัดแรก


ฉันเกลียดสคริปต์หลายบรรทัดหรือคำสั่ง sed กับอะไรก็ตามยกเว้น s และชุดเครื่องนอนดังนั้นฉันจึงขึ้นเครื่องด้วยวิธีนี้ นี่คือสิ่งที่ฉันใช้สำหรับ usecase (ทำงานกับ bash): filepath = / etc / hosts; patt = '^ \ (127 \ .0 \ .0 \ .1. * \)'; repl = '\ 1 newhostalias'; sed $ (IFS =: linearray = ($ (grep -E -m 1 -n "$ patt" "$ filepath")) && echo $ {linearray [0]}) s / "$ patt" / "$ repl" / "$ filepath"
parity3

มันได้ผล. แม้เพียงกับ sed ที่มีสมาร์ทพอที่จะยอมรับsed -f -ซึ่งบางคนไม่ได้ แต่คุณสามารถหลีกเลี่ยงมัน :)
rogerdpack

3

หากใครมาที่นี่เพื่อแทนที่ตัวละครสำหรับการปรากฏตัวครั้งแรกในทุกบรรทัด (เช่นตัวฉันเอง) ให้ใช้สิ่งนี้:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

โดยการเปลี่ยน 1 เป็น 2 คุณสามารถแทนที่ a วินาทีทั้งหมดแทน


2
คุณไม่จำเป็นต้องทำสิ่งนี้'s/a/b/'หมายถึงmatch aและdo just first match for every matching line
Samveen

ฉันอยู่กับ Samveen นอกจากนี้ไม่ได้ตอบคำถามที่ถามนี่ ฉันแนะนำให้ลบคำตอบนี้
Socowi

คำถามคือ "เกิดขึ้นครั้งแรกในไฟล์" ไม่ใช่ "เกิดขึ้นครั้งแรกในบรรทัด"
Manuel Romeiro

3

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

sed -z 's/#include/#include "newfile.h"\n#include'

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

  • s/text.*//s/text[^\n]*//สามารถเขียนเป็น [^\n]จับคู่ทุกอย่างยกเว้นอักขระขึ้นบรรทัดใหม่ [^\n]*จะจับคู่สัญลักษณ์ทั้งหมดหลังจากtextจนกว่าจะถึงบรรทัดใหม่
  • s/^text//s/(^|\n)text//สามารถเขียนเป็น
  • s/text$//s/text(\n|$)//สามารถเขียนเป็น

2

ฉันจะทำด้วยสคริปต์ awk:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

จากนั้นเรียกใช้ด้วย awk:

awk -f awkscript headerfile.h > headerfilenew.h

อาจเลอะเทอะฉันใหม่กับสิ่งนี้


2

เป็นข้อเสนอแนะทางเลือกคุณอาจต้องการดูedคำสั่ง

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF

2

ในที่สุดฉันก็ได้รับสิ่งนี้เพื่อทำงานในสคริปต์ Bash ที่ใช้ในการแทรกการประทับเวลาที่ไม่ซ้ำกันในแต่ละรายการในฟีด RSS:

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

มันเปลี่ยนเกิดขึ้นครั้งแรกเท่านั้น

${nowms}คือเวลาในหน่วยมิลลิวินาทีที่กำหนดโดยสคริปต์ Perl $counterเป็นตัวนับที่ใช้สำหรับการควบคุมลูปภายในสคริปต์\อนุญาตให้คำสั่งดำเนินการต่อในบรรทัดถัดไป

ไฟล์ถูกอ่านและ stdout จะถูกเปลี่ยนเส้นทางไปยังไฟล์งาน

วิธีที่ฉันเข้าใจ1,/====RSSpermalink====/บอกเวลาที่จะหยุดโดยตั้งค่าการ จำกัด ช่วงและจากนั้นs/====RSSpermalink====/${nowms}/เป็นคำสั่ง sed ที่คุ้นเคยเพื่อแทนที่สตริงแรกด้วยสตริงที่สอง

ในกรณีของฉันฉันใส่คำสั่งในเครื่องหมายอัญประกาศคู่เพราะฉันใช้มันในสคริปต์ Bash กับตัวแปร


2

ใช้FreeBSD edและหลีกเลี่ยงedข้อผิดพลาด "ไม่ตรงกัน" ในกรณีที่ไม่มีincludeคำสั่งในไฟล์ที่จะประมวลผล:

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF

นี่เป็นคำที่คล้ายกับคำตอบของTimoอย่างน่าทึ่งแต่ถูกเพิ่มเข้ามาอีกหนึ่งปีต่อมา
Jonathan Leffler

2

สิ่งนี้อาจใช้ได้กับคุณ (GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

หรือหากหน่วยความจำไม่มีปัญหา:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...

0

คำสั่งดังต่อไปนี้ลบเกิดขึ้นครั้งแรกของสตริงภายในไฟล์ มันลบบรรทัดว่างเกินไป มันถูกนำเสนอในไฟล์ xml แต่มันจะทำงานกับไฟล์ใด ๆ

มีประโยชน์หากคุณทำงานกับไฟล์ xml และคุณต้องการลบแท็ก ในตัวอย่างนี้มันจะลบการเกิดขึ้นครั้งแรกของแท็ก "isTag"

คำสั่ง:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

ไฟล์ต้นฉบับ (source.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ไฟล์ผลลัพธ์ (output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ป.ล. : มันไม่ได้ผลสำหรับฉันบน Solaris SunOS 5.10 (ค่อนข้างเก่า) แต่ใช้งานได้กับ Linux 2.6 รุ่น sed เวอร์ชัน 4.1.5


นี่ดูเหมือนความคิดพื้นฐานที่น่าทึ่งเหมือนกับคำตอบก่อนหน้าจำนวนหนึ่งโดยมีข้อแม้เดียวกันกับที่ใช้กับ GNU เท่านั้นsed(ดังนั้นจึงไม่สามารถใช้กับ Solaris ได้) คุณควรลบสิ่งนี้โปรด - มันไม่ได้ให้ข้อมูลใหม่ที่โดดเด่นแก่คำถามที่มีอายุ4½ปีแล้วเมื่อคุณตอบ จริงอยู่ที่มันมีตัวอย่างที่ใช้งานได้ แต่นั่นเป็นคุณค่าที่สามารถถกเถียงได้เมื่อคำถามนั้นมีคำตอบให้มากที่สุดเท่าที่จะทำได้
Jonathan Leffler

0

ไม่มีอะไรใหม่ แต่อาจเป็นคำตอบที่เป็นรูปธรรมมากกว่านี้เล็กน้อย: sed -rn '0,/foo(bar).*/ s%%\1%p'

ตัวอย่าง: xwininfo -name unity-launcherสร้างผลลัพธ์เช่น:

xwininfo: Window id: 0x2200003 "unity-launcher"

  Absolute upper-left X:  -2980
  Absolute upper-left Y:  -198
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 2880
  Height: 98
  Depth: 24
  Visual: 0x21
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x20 (installed)
  Bit Gravity State: ForgetGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +-2980+-198  -2980+-198  -2980-1900  +-2980-1900
  -geometry 2880x98+-2980+-198

แยก ID หน้าต่างที่มีการxwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'สร้าง:

0x2200003

0

POSIXly (ใช้ได้กับ sed), ใช้ regex เพียงหนึ่งตัวเท่านั้น, ต้องการหน่วยความจำสำหรับหนึ่งบรรทัด (ตามปกติ):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

อธิบาย:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.

0

กรณีการใช้งานอาจเป็นไปได้ว่าเหตุการณ์ของคุณจะถูกกระจายไปทั่วไฟล์ แต่คุณรู้ว่าสิ่งที่คุณกังวลเพียงอย่างเดียวคือใน 10, 20 หรือ 100 บรรทัดแรก

จากนั้นเพียงแค่บรรทัดที่อยู่เหล่านั้นแก้ไขปัญหา - แม้ว่าถ้อยคำของ OP เกี่ยวกับครั้งแรกเท่านั้น

sed '1,10s/#include/#include "newfile.h"\n#include/'

0

ทางออกที่เป็นไปได้ที่นี่อาจเป็นการบอกคอมไพเลอร์ให้รวมส่วนหัวโดยไม่ได้กล่าวถึงในไฟล์ต้นฉบับ ใน GCC มีตัวเลือกเหล่านี้:

   -include file
       Process file as if "#include "file"" appeared as the first line of
       the primary source file.  However, the first directory searched for
       file is the preprocessor's working directory instead of the
       directory containing the main source file.  If not found there, it
       is searched for in the remainder of the "#include "..."" search
       chain as normal.

       If multiple -include options are given, the files are included in
       the order they appear on the command line.

   -imacros file
       Exactly like -include, except that any output produced by scanning
       file is thrown away.  Macros it defines remain defined.  This
       allows you to acquire all the macros from a header without also
       processing its declarations.

       All files specified by -imacros are processed before all files
       specified by -include.

คอมไพเลอร์ของ Microsoft มีตัวเลือก/ FI (รวมถึงบังคับ)

คุณลักษณะนี้มีประโยชน์สำหรับส่วนหัวทั่วไปเช่นการกำหนดค่าแพลตฟอร์ม Makefile เคอร์เนลของ Linux ใช้-includeสำหรับสิ่งนี้


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