จะทำการแทนที่ข้อความในลำดับชั้นของโฟลเดอร์ขนาดใหญ่ได้อย่างไร


11

ฉันต้องการค้นหาและแทนที่ข้อความในไฟล์จำนวนมากยกเว้นบางกรณี สำหรับแต่ละบรรทัดฉันต้องการให้ฉันถามว่าฉันต้องการแทนที่บรรทัดนั้นหรือไม่ บางสิ่งที่คล้ายกับเสียงเรียกเข้า:%s/from/to/gc(พร้อมกับcเพื่อให้พร้อมรับคำยืนยัน) แต่ข้ามชุดโฟลเดอร์ มีเครื่องมือบรรทัดคำสั่งหรือสคริปต์ที่ดีที่สามารถใช้ได้หรือไม่?


เกี่ยวกับความสำคัญของการจัดรูปแบบที่เหมาะสม: ในขั้นต้นฉันจะอ่านคำสั่งของคุณs/from/to/gด้วยความผิดพลาดของการจัดรูปแบบหลังจากนั้นแทนที่จะs/from/to/gcเน้นที่สิ่งcที่คุณพยายามจะเขียน (คุณไม่สามารถทำได้ด้วย Markdown คุณสามารถทำได้ด้วย<code>และ<strong>แท็ก HTML)
Gilles 'หยุดความชั่วร้าย' ใน

คำตอบ:


19

ทำไมไม่ใช้เสียงเรียกเข้า?

เปิดไฟล์ทั้งหมดเป็นกลุ่ม

vim $(find . -type f)

หรือเปิดเฉพาะไฟล์ที่เกี่ยวข้อง (แนะนำโดย Caleb)

vim $(grep 'from' . -Rl)

จากนั้นให้รันการแทนที่ในบัฟเฟอร์ทั้งหมด

:bufdo %s/from/to/gc | update

นอกจากนี้คุณยังสามารถทำได้ด้วยsedแต่ความรู้ของฉันมี จำกัด


ขอบคุณคำตอบของคุณทำให้ฉันทำซ้ำได้สองครั้ง: ฉันรู้ว่าฉันพลาดบิตโต้ตอบทั้งหมด ฉันไม่คิดว่าเป็นไปได้แม้กับ sed (ช่องอินพุต / เอาต์พุตไม่เพียงพอ)
Gilles 'หยุดความชั่วร้าย' ใน

1
คุณอาจเร่งความเร็วโดยไม่เปิดไฟล์ทั้งหมดในบัฟเฟอร์ปัจจุบันโดยใช้grepแทนfindเพื่อให้คุณเปิดเฉพาะไฟล์ที่รู้จักกันเท่านั้น vim $(grep 'from' . -Rl)
Caleb

ขอบคุณต้องมีc (astreriks รอบ c) ไหม? หรือปัญหาการจัดรูปแบบของมัน?
balki

@balki นั่นเป็นปัญหา "การจัดรูปแบบ" แก้ไข
Gert

5

คุณสามารถทำสิ่งที่หยาบกับสคริปต์ Perl ขนาดเล็กซึ่งได้รับคำสั่งให้ดำเนินการเปลี่ยนบรรทัดโดยบรรทัด ( -l -pe) ในสถานที่ในไฟล์ที่ส่งผ่านเป็นอาร์กิวเมนต์ ( -i):

perl -i -l -pe '
    if (/from/) {                            # is the source text present on this line?
        printf STDERR ("%s: %s [y/N]? ", $ARGV, $_);  # display a prompt
        $r=<STDIN>;                                   # read user response
        if ($r =~ /^[Yy]/) {                          # if user entered Y:
            s/from/to/g;                              # replace all occurences on this line
    }' /path/to/files

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

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

perl -i -l -pe '…' **/*(.)

หากเชลล์ของคุณทุบตี≥4คุณสามารถเรียกใช้perl … **/*แต่นั่นจะสร้างข้อความแสดงข้อผิดพลาดปลอมเพราะ sed จะพยายาม (และล้มเหลว) ให้เรียกใช้บนไดเรกทอรี หากคุณต้องการทำการแทนที่ในชุดของไฟล์เช่นไฟล์ C เท่านั้นคุณสามารถ จำกัด การจับคู่ (ซึ่งใช้ได้กับ bash ≥4หรือ zsh):

perl -i -l -pe '…' **/*.[hc]

หากคุณต้องการควบคุมปลีกย่อยมากกว่าที่ไฟล์ที่คุณกำลังแทนที่หรือเปลือกของคุณไม่ได้สร้างการจับคู่ไดเรกทอรี recursive **หรือถ้าคุณมีไฟล์มากเกินไปและได้รับ“บรรทัดคำสั่งยาวเกินไปข้อผิดพลาด” findการใช้งาน ตัวอย่างเช่นในการแทนที่ไฟล์ทั้งหมดที่มีชื่อ*.hหรือ*.cในไดเรกทอรีปัจจุบันและไดเรกทอรีย่อย (ในระบบเก่าคุณอาจต้องใช้\;แทนที่+ท้ายบรรทัด ( +แบบฟอร์มเร็วกว่า แต่ไม่สามารถใช้ได้ทุกที่)

find . -type f -name '*.[hc]' -exec perl -i -l -pe '…' {} +

ที่ถูกกล่าวว่าฉันจะติดแก้ไขบรรณาธิการหากคุณต้องการการโต้ตอบ Gert ได้แสดงวิธีการนี้ใน Vimแม้ว่าจะต้องเปิดไฟล์ทั้งหมดที่คุณต้องการค้นหาซึ่งอาจเป็นปัญหาหากมีจำนวนมาก

ใน Emacs นี่คือวิธีที่คุณสามารถทำได้:

  1. รวบรวมชื่อไฟล์ด้วยM-x find-name-dired(ระบุไดเรกทอรีระดับบนสุด) หรือM-x find-dired(ระบุfindบรรทัดคำสั่งโดยพลการ)
  2. ในบัฟเฟอร์dired ที่เป็นผลลัพธ์ให้กดtเพื่อทำเครื่องหมายไฟล์ทั้งหมดจากนั้นQ( dired-do-query-replace-regexp)เพื่อดำเนินการแทนที่โดยพร้อมท์กับไฟล์ที่ทำเครื่องหมายไว้

1

sdiff(ดูhttp://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff ) อาจมีประโยชน์ในที่นี่ ด้วยคุณสามารถทำแพทช์แบบโต้ตอบ ดังนั้นการทำมันด้วยไฟล์ชั่วคราวที่คุณสร้างขึ้นโดยการดำเนินการทดแทนโดยใช้sedอาจเป็นวิธีการแก้ปัญหาที่เป็นไปได้:

# use file descriptor 3 to still allow use of stdin
while IFS= read -r -d '' file <&3; do

  # write the result of the replacement into a temporary file
  sed -r 's/something/something_else/g' -- "$file" > replacer_tmp

  if cmp -s -- "$file" replacer_tmp; then
    continue; # nothing was replaced
  fi

  echo "There is something to replace in '$file'! Starting interactive diff."
  echo

  sdiff -o "$file" -s -d -- "$file" replacer_tmp

  echo

done 3< <(find . -type f -print0)

(ไฟล์วนซ้ำที่ใช้การทดแทนกระบวนการที่ไม่ใช่ POSIX และread -dสนับสนุนโดยเช่นbash)

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