วิธีการแทนที่รูปแบบที่ N เกิดขึ้นในไฟล์ได้อย่างไร


10

วิธีแทนที่สตริงที่สามในไฟล์โดยใช้sedคำสั่ง

ตัวอย่าง:

เปลี่ยนเฉพาะเหตุการณ์ที่สามของisเป็นusในไฟล์

ไฟล์อินพุตของฉันประกอบด้วย:

hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged.

ฉันคาดหวังผลลัพธ์คือ:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.

3
อินพุตและเอาต์พุตเหมือนกัน
Hauke ​​Laging

4
sedไม่ใช่เครื่องมือที่เหมาะสมสำหรับงาน
choroba

@don_crissti ฉันแก้ไขมันแล้ว OP ไม่ได้ใช้เครื่องมือการจัดรูปแบบ (โดยวิธี Sureshkumar ดูที่นี่เพื่อขอความช่วยเหลือในการแก้ไขคำถามของคุณ) และบรรณาธิการที่ต่อเนื่องมีความเข้าใจผิดในสิ่งที่ต้องการ
terdon

คำตอบ:


11

ทำได้ง่ายขึ้นมากด้วย perlมันมากทำง่ายขึ้นด้วย

ในการเปลี่ยน 3 เกิดขึ้น:

perl -pe 's{is}{++$n == 3 ? "us" : $&}ge'

ในการเปลี่ยนทุก 3 เกิดขึ้น:

perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'

3

เมื่อสตริงการแทนที่เกิดขึ้นเพียงครั้งเดียวต่อบรรทัดคุณสามารถรวมโปรแกรมอรรถประโยชน์ที่แตกต่างกัน
เมื่ออินพุตอยู่ในไฟล์ "อินพุต" และคุณกำลังแทนที่ "คือ" โดย "เรา" คุณสามารถใช้

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'

ในตัวอย่างในคำถามมีมากกว่าหนึ่งisต่อบรรทัด
terdon

ฉันคิดว่าคุณกำลังค้นหา "is" พร้อมช่องว่าง ฉันสามารถแก้ไขคำตอบของฉันด้วยคำสั่ง tr เช่น @jimmij ที่ใช้ แต่วิธีการแก้ปัญหาของฉันจะด้อยกว่าเขามาก
วอลเตอร์

ฉันไม่ใช่ผู้ถาม :) ฉันคิดว่าสิ่งเดียวกันซึ่งเป็นสาเหตุที่ฉันได้ upvoted คำตอบของคุณ แต่ถ้าคุณดูคำถามต้นฉบับรุ่น (คลิกที่ลิงค์ "แก้ไข X นาทีที่ผ่านมา") คุณจะเห็นว่า OP คาดว่าจะอยู่ในนี้จะเปลี่ยนเป็นเช่นนั้น โดยไม่จำเป็นต้องมีแมวอยู่
terdon

2

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

sed -i '/is/{: 1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; q}' text.file

หากการตัดสินใจของคุณเช่นchorobaคุณสามารถแก้ไขด้านบนเป็น

sed '/is/{:1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; :2 ; n ; $!b2}' text.file

ซึ่งเอาต์พุตทุกบรรทัด

หรือคุณต้องใส่ทุกบรรทัดในพื้นที่รูปแบบ (ในหน่วยความจำดังนั้นระวังข้อ จำกัด ขนาด) และทำการทดแทน

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file

2

คุณสามารถใช้sedสำหรับการที่หากบรรทัดใหม่ก่อนหน้านี้จะถูกแทนที่เป็นอักขระอื่น ๆ เช่น:

tr '\n' '\000' | sed 's/is/us/3' | tr '\000' '\n'

และเหมือนกันกับ pure (GNU) sed:

sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'

(การขึ้นsedบรรทัดใหม่ถูกขโมยอย่างไร้ยางอายจากhttps://stackoverflow.com/a/1252191/4488514 )


หากคุณกำลังจะใช้ GNU ไวยากรณ์เฉพาะคุณเช่นกันอาจจะใช้sed sed -z 's/is/us/3'
Stéphane Chazelas

@ StéphaneChazelas -zต้องเป็นคุณสมบัติใหม่เอี่ยมฉันGNU sed version 4.2.1ไม่รู้อะไรเลยเกี่ยวกับตัวเลือกนี้
jimmij

1
เพิ่มใน 4.2.2 (2012) ในโซลูชันที่สองของคุณคุณไม่ต้องการแปลงเป็น\x0ขั้นตอน
Stéphane Chazelas

ขออภัยเกี่ยวกับการแก้ไข ฉันไม่เห็นคำถามต้นฉบับและมีบางคนเข้าใจผิดและแก้ไขข้อผิดพลาด ฉันเปลี่ยนกลับเป็นเวอร์ชันก่อนหน้า
terdon

1
p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\}            \
     -e's/(\n*)(.*)/ \2 \1/'       \
     -e"s/is[$p]?[$s]/\n&/g"       \
     -e"s/([^$s])\n/\1/g;1G"       \
-e:c -e"s/\ni(.* )\n{3}/u\1/"      \
     -e"/\n$/!s/\n//g;/\ni/G"      \
     -e's//i/;//tc'                \
     -e's/^ (.*) /\1/;P;$d;N;D'

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

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

1. is is isis
2. is does

... มันจะพิมพ์ ...

1. is is isis
2. us does

มันจัดการกับกรณีขอบโดยการใส่ช่องว่างที่หัวและส่วนท้ายของทุกบรรทัด ทำให้ขอบเขตของคำง่ายขึ้นเล็กน้อยในการตรวจสอบให้แน่ใจ

ถัดไปจะค้นหาises ที่ถูกต้องโดยการแทรก\newline ก่อนที่เหตุการณ์ทั้งหมดนั้นจะเกิดขึ้นisทันทีนำหน้าศูนย์หรืออักขระเครื่องหมายวรรคตอนหนึ่งอันตามด้วยช่องว่างในทันที มันจะส่งผ่านอีกครั้งและลบ\newlines ทั้งหมดที่นำหน้าด้วยอักขระที่ไม่ใช่ช่องว่างทันที เครื่องหมายนี้ทิ้งไว้ข้างหลังจะตรงis.และisแต่ไม่thisหรือ?isหรือ

รวบรวมต่อไปแต่ละเครื่องหมายถึงหางของสตริง - สำหรับทุก\niการแข่งขันบนเส้นมันผนวก\newline ถึงหางของสตริงและแทนที่มันด้วยกับทั้งหรือi uหากมี 3 \newlines ในแถวที่รวมกันที่ส่วนท้ายของสตริงจะใช้ u - else i ครั้งแรกที่ใช้ au เป็นครั้งสุดท้าย - การแทนที่จะกำหนดลูปที่ไม่มีที่สิ้นสุดซึ่งจะลดลงget line, print line, get line, print line,เรื่อย ๆ

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

ฉันจะเพิ่มlคำสั่ง ook ที่ส่วนหัวของห่วงเช่น:

l; s/\ni(.* )\n{9}/u\1/...

... และดูว่ามันทำงานอย่างไรกับข้อมูลนี้:

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

... ดังนั้นนี่คือสิ่งที่ทำ:

 hai this \nis linux. \n$        #behind the scenes
hai this is linux.               #actually printed
 hai this \nis unix. \n\n$       #it builds the marker string
hai this is unix.
  \n\n\n$                        #only for lines matching the

  \n\n\n$                        #pattern - and not otherwise.

 hai this \nis mac. \n\n\n$      #here's the match - 3 ises so far in file.
hai this us mac.                 #printed
hai this is unchanged is.        #no look here - this line is never evaled

มันสมเหตุสมผลมากกว่าบางทีอาจมีises ต่อบรรทัดมากขึ้น:

nthword()(  p='[:punct:]' s='[:space:]'         
    sed -e '1!{/\n/!b' -e\}             \
        -e 's/\(\n*\)\(.*\)/ \2 \1/'    \
        -e "s/$1[$p]\{0,1\}[$s]/\n&/g"  \
        -e "s/\([^$s]\)\n/\1/g;1G;:c"   \
        -e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2\1/" \
        -e '/\n$/!s/\n//g;/\n'"$1/G"    \
        -e "s//$1/;//tc" -e 's/^ \(.*\) /\1/'     \
        -e 'P;$d;N;D'
)        

นั่นคือสิ่งเดียวกัน แต่เขียน w / POSIX BRE และการจัดการอาร์กิวเมนต์เบื้องต้น

 printf 'is is. is? this is%.0s\n' {1..4}  | nthword is us 12

... ได้รับ ...

is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is

... และถ้าฉันเปิดใช้งาน${dbg}:

printf 'is is. is? this is%.0s\n' {1..4}  | 
dbg=1 nthword is us 12

... เราสามารถดูมันซ้ำ ...

 \nis \nis. \nis? this \nis \n$
 is \nis. \nis? this \nis \n\n$
 is is. \nis? this \nis \n\n\n$
 is is. is? this \nis \n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n\n\n\n\n$
is is. is? this us
is is. is? this is

คุณทราบตัวอย่างของคุณว่า "isis" หรือไม่
flarn2006

@ flarn2006 - ฉันค่อนข้างแน่ใจว่ามันบอกว่าเป็น
mikeserv

0

นี่คือโซลูชันเชิงตรรกะที่ใช้sedและtrแต่ต้องเขียนในสคริปต์เพื่อให้ทำงานได้ โค้ดด้านล่างแทนที่ทุกเกิดขึ้นที่ 3ของคำที่ระบุไว้ในsedคำสั่ง แทนที่i=3ด้วยเพื่อให้งานนี้สำหรับการใด ๆi=nn

รหัส:

# replace new lines with '^' character to get everything onto a single line
tr '\n' '^' < input.txt > output.txt

# count number of occurrences of the word to be replaced
num=`grep -o "apple" "output.txt" | wc -l`

# in successive iterations, replace the i + (n-1)th occurrence
n=3
i=3
while [ $i -le $num ]
do
    sed -i '' "s/apple/lemon/${i}" 'output.txt'
    i=$(( i + (n-1) ))
done

# replace the '^' back to new line character
tr '^' '\n' < output.txt > tmp && mv tmp output.txt


ทำไมจึงใช้งานได้:

a b b b b a c a d a b b b a b e b z b s b a bสมมติว่าแฟ้มข้อความเป็น

  • เมื่อ n = 2: bเราต้องการที่จะเปลี่ยนทุกการเกิดที่สองของ

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • ก่อนอื่นเราแทนที่เหตุการณ์ที่สองจากนั้นก็เกิดเหตุการณ์ที่ 3 จากนั้นก็เปลี่ยนที่ 4, 5 และอื่น ๆ นับตามลำดับที่แสดงด้านบนเพื่อดูตัวคุณเอง
  • เมื่อ n = 3: bเราต้องการที่จะเปลี่ยนทุกที่เกิดขึ้นในสามของ

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • ก่อนอื่นเราแทนที่เหตุการณ์ที่ 3 จากนั้นเป็นวันที่ 5 จากนั้นก็วันที่ 7, 9, 11 และอื่น ๆ
  • เมื่อ n = 4: bเราต้องการที่จะเปลี่ยนทุกที่เกิดขึ้นในสามของ

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