แทนที่สตริงด้วยดัชนีตามลำดับ


10

ใครสามารถแนะนำวิธีที่สง่างามเพื่อให้บรรลุนี้

การป้อนข้อมูล:

test  instant  ()

test  instant  ()

...
test  instant  ()    //total 1000 lines

ผลลัพธ์ควรเป็น:

test      instant1  ()

test      instant2  ()

test      instant1000()

บรรทัดว่างอยู่ในไฟล์อินพุตของฉันและมีไฟล์จำนวนมากภายใต้ไดเรกทอรีเดียวกันที่ฉันต้องดำเนินการในครั้งเดียว

ฉันพยายามทำสิ่งนี้เพื่อแทนที่ไฟล์จำนวนมากใน dir เดียวกันและไม่ทำงาน

for file in ./*; do perl -i -000pe 's/instance$& . ++$n/ge' "$file"; done

ข้อผิดพลาด:

Substitution replacement not terminated at -e line 1.
Substitution replacement not terminated at -e line 1.

และฉันก็ลองทำเช่นนี้:

perl -i -pe 's/instant/$& . ++$n/ge' *.vs

มันทำงานได้ แต่ดัชนีก็เพิ่มขึ้นเรื่อย ๆ จากหนึ่งไปอีกไฟล์หนึ่ง ฉันต้องการตั้งค่าใหม่เป็น 1 เมื่อเปลี่ยนเป็นไฟล์ใหม่ ข้อเสนอแนะที่ดี?

find . -type f -exec perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

ใช้งานได้ แต่มันถูกแทนที่ไฟล์อื่น ๆ ทั้งหมดไม่ควรถูกแทนที่ ฉันต้องการแทนที่ไฟล์ด้วย*.txtเท่านั้น


และพวกเขาทั้งหมดประกอบด้วยเฉพาะบรรทัดว่างอย่างใดอย่างหนึ่งหรือtest instant ()?
terdon

ฉันใส่เส้นเว้นวรรคสองบรรทัดกลับเข้าไปพวกเขามักจะเป็นสัญญาณของผู้ใช้ใหม่ที่ไม่รู้ว่าจะใช้มาร์กอัปของเว็บไซต์นี้ได้อย่างไรนั่นคือเหตุผลที่ terdon ลบมันออกในขณะที่เยื้องบล็อกเนื้อหาไฟล์ของคุณอย่างเหมาะสม หวังว่ามันจะโอเคตอนนี้
Timo

คำตอบ:


14
perl -pe 's/instant/$& . ++$n/ge'

หรือกับ GNU awk:

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

หากต้องการแก้ไขไฟล์ในสถานที่ให้เพิ่ม-iตัวเลือกไปที่perl:

perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' ./*.vs

หรือเรียกซ้ำ:

find . -name '*.vs' -type f -exec perl -pi -e '
  s/instant/$& . ++$n{$ARGV}/ge' {} +

คำอธิบาย

perl -pe 's/instant/$& . ++$n/ge'

-pคือการประมวลผลอินพุตบรรทัดต่อบรรทัดประเมินนิพจน์ที่ส่งผ่านไปยัง-eแต่ละบรรทัดและพิมพ์ สำหรับแต่ละบรรทัดเราแทน (ใช้s/re/repl/flagsประกอบการ) instantสำหรับตัวเอง ( $&) ++$nและมูลค่าเพิ่มขึ้นของตัวแปร gธงคือการทำให้การทดแทนทั่วโลก (ไม่ใช่แค่ครั้งเดียว) และeเพื่อให้การเปลี่ยนถูกตีความว่าเป็นรหัส Perl เพื่ออีประเมินค่า (ไม่สตริงคงที่)

สำหรับการแก้ไขแบบ in-place โดยที่หนึ่งการร้องขอ perl ดำเนินการมากกว่าหนึ่งไฟล์เราต้องการ$nรีเซ็ตที่แต่ละไฟล์ เราใช้แทน$n{$ARGV}( $ARGVไฟล์ที่ประมวลผลในปัจจุบันอยู่ที่ไหน)

awkหนึ่งสมควรได้รับบิตของคำอธิบาย

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

เรากำลังใช้ความสามารถของ GNU awkในการแยกเร็กคอร์ดในสตริงโดยพลการ (แม้แต่ regexps) ด้วย-vRS=instantเราตั้งค่าการบันทึกแยกinstantไป RTเป็นตัวแปรที่เก็บสิ่งที่ตรงกันRSดังนั้นโดยทั่วไปinstantยกเว้นระเบียนสุดท้ายที่เป็นสตริงว่าง ในอินพุตด้านบนเร็กคอร์ด ( $0) และเทอร์มิเนเตอร์การบันทึก ( RT) คือ ( [$0|RT]):

[test  |instant][  ()
test  |instant][  ()
...
test  |instant][  ()    //total 1000 lines|]

ดังนั้นสิ่งที่เราต้องทำคือใส่หมายเลขที่เพิ่มขึ้นในตอนเริ่มต้นของทุกเรคคอร์ดยกเว้นหมายเลขแรก

เราทำอะไรข้างต้น สำหรับบันทึกแรกnจะว่างเปล่า เราตั้งค่า ORS (ตัวส่งสัญญาณออก ) เป็น RT เพื่อให้awk พิมพ์n $0 RTออกมา มันทำตามนิพจน์ที่สอง ( ++n) ซึ่งเป็นเงื่อนไขที่มักจะประเมินเป็นจริง (หมายเลขที่ไม่เป็นศูนย์) และดังนั้นจึงมีการดำเนินการเริ่มต้น (การพิมพ์$0 ORS) สำหรับทุกเร็กคอร์ด



5

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

  • Perl

    perl -00pe 's/instant/$& . $./e' file 

    -pหมายถึง "พิมพ์ทุกบรรทัด" -eหลังจากการใช้สคริปต์สิ่งที่จะได้รับกับ การ-00เปิด "โหมดย่อหน้า" ดังนั้นการบันทึก (บรรทัด) จะถูกกำหนดโดยอักขระขึ้นบรรทัดใหม่ ( \n) ซึ่งจะช่วยให้สามารถจัดการกับบรรทัดเว้นสองบรรทัดได้อย่างถูกต้อง $&เป็นรูปแบบสุดท้ายที่ตรงกันและ$.เป็นหมายเลขบรรทัดปัจจุบันของไฟล์อินพุต eในs///eช่วยให้ฉันเพื่อประเมินการแสดงออกในการประกอบการทดแทน

  • awk (สิ่งนี้ถือว่าข้อมูลของคุณตรงตามที่แสดงโดยมีช่องว่างสามช่องคั่น)

    awk '{if(/./) print $1,$2 ++k,$3; else print}' file 

    ที่นี่เราเพิ่มkตัวแปรkเฉพาะเมื่อบรรทัดปัจจุบันไม่ว่างเปล่า/./ในกรณีนี้เรายังพิมพ์ข้อมูลที่จำเป็น มีการพิมพ์บรรทัดว่างตามที่เป็นอยู่

  • เปลือกหอยต่างๆ

     n=0; while read -r a b c; do 
       if [ "$a" ] ; then 
          (( n++ ))
          printf "%s %s%s %s\n" "$a" "$b" "$n" "$c"
       else
          printf "%s %s %s\n" "$a" "$b" "$c"
       fi
     done < file 

    นี่คือสายการป้อนข้อมูลแต่ละคนจะแยกโดยอัตโนมัติในช่องว่างและเขตข้อมูลจะถูกบันทึกเป็น$a, และ$b $cจากนั้นภายในวงที่$cมีการเติมโดยหนึ่งสำหรับแต่ละสายที่ไม่ว่างเปล่าและมันก็คุ้มค่าในปัจจุบันจะอยู่ด้านข้างสนามที่สอง$a$b

หมายเหตุ: วิธีแก้ปัญหาทั้งหมดข้างต้นถือว่าทุกบรรทัดในไฟล์มีรูปแบบเดียวกัน ถ้าไม่ใช่คำตอบของ @ Stephane คือหนทางที่จะไป


สำหรับการจัดการกับไฟล์จำนวนมากและสมมติว่าคุณต้องการทำสิ่งนี้กับไฟล์ทั้งหมดในไดเรกทอรีปัจจุบันคุณสามารถใช้สิ่งนี้:

for file in ./*; do perl -i -00pe 's/instant/$& . $./e' "$file"; done

ระวัง: นั่นถือว่าชื่อไฟล์ง่ายโดยไม่มีช่องว่างถ้าจำเป็นที่จะต้องจัดการกับบางสิ่งบางอย่างที่ซับซ้อนมากขึ้นไป (สมมติksh93, zshหรือbash):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -00pe 's/instant/$& . $./e' "$file"
done

สคริปต์ Perl ทำงาน อย่างไรก็ตามมีปัญหาเล็ก ๆ อย่างหนึ่งถ้าเส้นเป็นช่องว่างสองครั้ง
user3342338

@ user3342338 ใช่นั่นจะเพิ่มตัวนับตั้งแต่ฉันใช้หมายเลขบรรทัดปัจจุบัน นี้เป็นอย่างมากวิธีไร้เดียงสาที่ผมกล่าวว่าสเตฟานเป็นที่แข็งแกร่งมากขึ้น ไม่มีงานเหล่านี้หากคุณมีบรรทัดว่างหรือหากบรรทัดใด ๆ ของคุณเบี่ยงเบนจากสิ่งที่คุณแสดง
terdon

@ user3342338 ดูคำตอบที่ปรับปรุงแล้ว ตอนนี้พวกเขาควรทำงานสำหรับไฟล์ที่มีระยะห่างสองเท่า
terdon

คำตอบที่ยอดเยี่ยมและตัวเลือกของวิธีการทางเลือก !! ขอบคุณ
Madivad

0

หากคุณต้องการแก้ปัญหาด้วยsedคุณสามารถใช้สิ่งนี้ (ในbash):

i=0
while read -r line; do
  sed "s/\(instant\)/\1${i}/" <<< "${line}"
  [[ ${line} =~ instant ]] && i=$(( i + 1 ))
done < file

หรือโซลูชันแบบพกพาอื่น ๆ จะเป็น:

i=0
while read -r line; do
  echo "${line}" | sed "s/\(instant\)/\1${i}/"
  if echo "${line}" | grep -q inst; then
    i=$(( i + 1 ))
  fi
done < file
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.