ฉันต้องการแทนที่k
อินสแตนซ์แรกของคำ
ฉันจะทำสิ่งนี้ได้อย่างไร
เช่น. ไฟล์ Say foo.txt
มี 100 อินสแตนซ์ของคำว่า 'linux'
ฉันต้องแทนที่ 50 รายการแรกเท่านั้น
ฉันต้องการแทนที่k
อินสแตนซ์แรกของคำ
ฉันจะทำสิ่งนี้ได้อย่างไร
เช่น. ไฟล์ Say foo.txt
มี 100 อินสแตนซ์ของคำว่า 'linux'
ฉันต้องแทนที่ 50 รายการแรกเท่านั้น
คำตอบ:
ส่วนแรกด้านล่างอธิบายถึงการใช้sed
เพื่อเปลี่ยน k- เกิดขึ้นครั้งแรกในบรรทัด ส่วนที่สองขยายวิธีนี้เพื่อเปลี่ยนเฉพาะการเกิด k ครั้งแรกในไฟล์โดยไม่คำนึงถึงบรรทัดที่ปรากฏ
ด้วย sed มาตรฐานมีคำสั่งให้แทนที่การเกิดขึ้นที่ k ของคำบนบรรทัด ถ้าk
เป็น 3 เช่น:
sed 's/old/new/3'
หรือหนึ่งสามารถแทนที่เกิดขึ้นทั้งหมดด้วย:
sed 's/old/new/g'
ทั้งสองอย่างนี้เป็นสิ่งที่คุณต้องการ
GNU sed
เสนอส่วนขยายที่จะเปลี่ยนการเกิดตัว k และหลังจากนั้นทั้งหมด ถ้า k เป็น 3 ตัวอย่างเช่น:
sed 's/old/new/g3'
สามารถรวมกันเพื่อทำสิ่งที่คุณต้องการ ในการเปลี่ยน 3 เหตุการณ์แรก:
$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old
\n
มีประโยชน์ตรงไหนเพราะเรามั่นใจได้ว่ามันจะไม่เกิดขึ้นบนเส้น
เราใช้sed
คำสั่งการแทนที่สามคำ:
s/\<old\>/\n/g4
นี้ส่วนขยาย GNU เพื่อแทนที่สี่และเกิดขึ้นตามมาทั้งหมดด้วยold
\n
คุณลักษณะเพิ่มเติมของ regex \<
ถูกใช้เพื่อให้ตรงกับจุดเริ่มต้นของคำและ\>
เพื่อให้ตรงกับจุดสิ้นสุดของคำ สิ่งนี้รับประกันว่าจะจับคู่คำที่สมบูรณ์เท่านั้น regex ขยายต้องมีตัวเลือกในการ-E
sed
s/\<old\>/new/g
เพียงสามเกิดขึ้นครั้งแรกที่ยังคงอยู่และแทนที่พวกเขาทั้งหมดนี้ด้วยold
new
s/\n/old/g
เหตุการณ์ที่สี่และที่เหลือทั้งหมดของold
ถูกแทนที่ด้วย\n
ในขั้นตอนแรก สิ่งนี้จะคืนพวกเขากลับสู่สถานะดั้งเดิม
หาก GNU sed ไม่พร้อมใช้งานและคุณต้องการเปลี่ยน 3 รายการแรกเป็นเป็นold
ให้new
ใช้สามs
คำสั่ง:
$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old
นี้ทำงานได้ดีเมื่อk
เป็นจำนวนน้อย k
แต่ตาชั่งไม่ดีไปจนถึงขนาดใหญ่
เนื่องจาก seds ที่ไม่ใช่ GNU บางตัวไม่สนับสนุนการรวมคำสั่งกับเครื่องหมายอัฒภาคแต่ละคำสั่งที่นี่จึงถูกนำเสนอพร้อมตัว-e
เลือกของตนเอง นอกจากนี้ยังอาจมีความจำเป็นต้องตรวจสอบว่าคุณsed
สนับสนุนคำสัญลักษณ์ขอบเขตและ\<
\>
เราสามารถบอกให้ sed อ่านไฟล์ทั้งหมดจากนั้นทำการแทนที่ ตัวอย่างเช่นในการแทนที่การเกิดขึ้นสามครั้งแรกของการold
ใช้ sed แบบ BSD:
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
คำสั่ง sed H;1h;$!d;x
อ่านไฟล์ทั้งหมดใน
เนื่องจากข้างต้นไม่ได้ใช้ส่วนขยาย GNU ใด ๆ จึงควรทำงานกับ BSD (OSX) sed หมายเหตุคิดว่าวิธีนี้ต้องใช้sed
ที่สามารถจัดการกับสายยาว GNU น่าsed
จะใช้ได้ ผู้ที่ใช้เวอร์ชั่นที่ไม่ใช่ GNU sed
ควรทดสอบความสามารถในการจัดการกับสายยาว
ด้วยความช่วยเหลือของ GNU เราสามารถใช้g
เคล็ดลับที่อธิบายไว้ข้างต้นได้ แต่\n
แทนที่ด้วยด้วย\x00
เพื่อแทนที่เหตุการณ์สามเหตุการณ์แรก:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
วิธีการนี้วัดได้ดีและk
มีขนาดใหญ่ แม้ว่าจะถือว่า\x00
ไม่ได้อยู่ในสตริงเดิมของคุณ เนื่องจากเป็นไปไม่ได้ที่จะใส่อักขระ\x00
ในสตริง bash จึงเป็นข้อสันนิษฐานที่ปลอดภัย
tr '\n' '|' < input_file | sed …
และแนะนำ แต่แน่นอนว่าการแปลงอินพุตทั้งหมดเป็นหนึ่งบรรทัดและบางส่วนที่ไม่ใช่ GNU seds ไม่สามารถจัดการกับสายยาวโดยพลการ (2) คุณพูดว่า“ …ข้างต้นสตริงที่ยกมา'|'
ควรถูกแทนที่ด้วยอักขระใด ๆ หรือสตริงของอักขระ…” แต่คุณไม่สามารถใช้tr
เพื่อแทนที่อักขระด้วยสตริง (ความยาว> 1) (3) -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
ในตัวอย่างล่าสุดของคุณที่คุณพูด -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
นี้น่าจะเป็นสำหรับการพิมพ์ผิด
คำสั่ง awk สามารถใช้เพื่อแทนที่เหตุการณ์ N แรกของคำด้วยการแทนที่
คำสั่งจะแทนที่หากคำนั้นตรงกันทั้งหมด
ในตัวอย่างด้านล่างฉันกำลังแทนที่27
เหตุการณ์แรกของold
ด้วยnew
ใช้งานย่อย
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
คำสั่งนี้วนซ้ำในแต่ละฟิลด์จนกว่าจะตรงกับ
old
มันตรวจสอบตัวนับที่ต่ำกว่า 27 เพิ่มขึ้นและทดแทนการแข่งขันครั้งแรกในบรรทัด จากนั้นย้ายไปยังฟิลด์ / บรรทัดถัดไปและทำซ้ำ
การแทนที่ฟิลด์ด้วยตนเอง
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
คล้ายกับคำสั่งก่อน แต่มันแล้วมีเครื่องหมายที่สนามมันก็ขึ้นอยู่กับ
($i)
มันก็เปลี่ยนค่าของข้อมูลที่ได้จากการold
new
ทำการตรวจสอบก่อน
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
การตรวจสอบว่าบรรทัดนั้นมีข้อมูลเก่าและตัวนับต่ำกว่า 27
SHOULD
ให้เพิ่มความเร็วเล็กน้อยเพราะจะไม่ประมวลผลบรรทัดเมื่อสิ่งเหล่านี้เป็นเท็จ
ผล
เช่น
old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old
ไปยัง
new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old
สมมติว่าคุณต้องการแทนที่สตริงสามอินสแตนซ์แรกเท่านั้น ...
seq 11 100 311 |
sed -e 's/1/\
&/g' \ #s/match string/\nmatch string/globally
-e :t \ #define label t
-e '/\n/{ x' \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{' \ #if not 3 characters in hold space do
-e 's/$/./' \ #add a new char to hold space
-e x \ #exchange hold/pattern spaces again
-e 's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e 'b t' \ #branch back to label t
-e '};x' \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g' #end match function; remove all newline characters
หมายเหตุ: ข้างต้นอาจไม่ทำงานกับความคิดเห็นฝังตัว
... หรือในตัวอย่างของฉันใน '1' ...
22
211
211
311
ฉันใช้สองเทคนิคที่น่าทึ่ง ในครั้งแรกที่เกิดขึ้นของทุกคนบนเส้นจะถูกแทนที่ด้วย1
\n1
ด้วยวิธีนี้เมื่อฉันทำการเปลี่ยนแบบเรียกซ้ำครั้งถัดไปฉันสามารถแน่ใจได้ว่าจะไม่แทนที่การเกิดขึ้นสองครั้งหากสตริงการแทนที่ของฉันมีสตริงการแทนที่ของฉัน ตัวอย่างเช่นถ้าฉันแทนที่he
ด้วยhey
มันจะยังคงทำงาน
ฉันทำสิ่งนี้เช่น:
s/1/\
&/g
ประการที่สองฉันนับการแทนที่ด้วยการเพิ่มตัวละครในh
พื้นที่เก่าสำหรับแต่ละเหตุการณ์ เมื่อฉันถึงสามไม่เกิดขึ้นอีก หากคุณใช้สิ่งนี้กับข้อมูลของคุณและเปลี่ยนการเปลี่ยน\{3\}
ทั้งหมดที่คุณต้องการและที่/\n1/
อยู่ในสิ่งที่คุณต้องการเปลี่ยนคุณควรเปลี่ยนเฉพาะที่คุณต้องการเท่านั้น
ฉันทำทุก-e
อย่างเพื่อให้อ่านได้เท่านั้น POSIXly มันสามารถเขียนดังนี้:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
และด้วย GNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
โปรดจำไว้ว่านั่นsed
คือการเรียงบรรทัด - มันไม่ได้อ่านในไฟล์ทั้งหมดแล้วลองวนกลับไปมาเหมือนในกรณีของบรรณาธิการอื่น ๆ sed
ง่ายและมีประสิทธิภาพ ที่กล่าวมามักสะดวกในการทำสิ่งต่อไปนี้:
นี่คือฟังก์ชั่นเชลล์เล็ก ๆ ที่รวมมันเข้ากับคำสั่งที่ถูกเรียกใช้งานอย่างง่าย:
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
ดังนั้นด้วยสิ่งที่ฉันสามารถทำได้:
seq 11 100 311 | firstn 7 1 5
... และรับ ...
55
555
255
311
...หรือ...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
... เพื่อรับ ...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
... หรือเพื่อให้ตรงกับตัวอย่างของคุณ(ตามลำดับความสำคัญน้อยกว่า) :
yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux
ทางเลือกสั้น ๆ ใน Perl:
perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file
เปลี่ยนค่าของ `$ n $ เป็นความชอบของคุณ
มันทำงานอย่างไร:
new
สำหรับold
( s/old/new/
) และเมื่อใดก็ตามที่สามารถมันเพิ่มตัวแปร$i
( ++$i
)1 while ...
) ตราบใดที่มันมีการ$n
ทดแทนน้อยกว่าทั้งหมดและสามารถทำการทดแทนอย่างน้อยหนึ่งรายการในบรรทัดนั้นใช้เปลือกวงและex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
ใช่มันโง่เล็กน้อย
;)
หมายเหตุ: สิ่งนี้อาจล้มเหลวหากไฟล์มีอินสแตนซ์น้อยกว่า 50 old
รายการ (ฉันไม่ได้ทำการทดสอบ) ถ้าเป็นเช่นนั้นก็จะทำให้ไฟล์ไม่ได้รับการแก้ไข
ดีกว่าใช้ Vim
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
คำอธิบาย:
q # Start recording macro
q # Into register q
gg # Go to start of file
/old<CR> # Go to first instance of 'old'
:s/old/new/<CR> # Change it to 'new'
q # Stop recording
49@q # Replay macro 49 times
:x # Save and exit
วิธีแก้ปัญหาที่ง่าย แต่ไม่เร็วมากคือการวนซ้ำคำสั่งที่อธิบายไว้ใน /programming/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a -ไฟล์
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
คำสั่งนี้ sed โดยเฉพาะอย่างยิ่งอาจจะทำงานเฉพาะสำหรับ GNU sed และถ้าnewwordไม่ได้เป็นส่วนหนึ่งของoldword สำหรับผู้ที่ไม่ใช่ GNU โปรดดูที่นี่วิธีการแทนที่รูปแบบแรกในไฟล์
ด้วย GNU awk
คุณสามารถตั้งค่าตัวคั่นเรคคอร์ดRS
เป็นคำที่ต้องการแทนที่ด้วยเขตแดนของคำ จากนั้นเป็นกรณีของการตั้งค่าตัวคั่นเร็กคอร์ดในเอาท์พุทเป็นคำแทนที่สำหรับเรกk
คอร์ดแรกในขณะที่ยังคงรักษาตัวแยกเรกคอร์ดดั้งเดิมสำหรับส่วนเหลือ
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
หรือ
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file