ฉันจะค้นหาและแทนที่แบบเรียกซ้ำจากบรรทัดคำสั่งได้อย่างไร


84

การใช้เชลล์เช่น bash หรือ zshell ฉันจะทำ 'ค้นหาและแทนที่' แบบเรียกซ้ำได้อย่างไร? กล่าวอีกนัยหนึ่งฉันต้องการแทนที่ 'foo' ด้วย 'bar' ทุกครั้งในไฟล์ทั้งหมดในไดเรกทอรีนี้และไดเรกทอรีย่อย


คำตอบทางเลือกสำหรับคำถามเดียวกันสามารถดูได้ที่นี่stackoverflow.com/questions/9704020/…
dunxd


มันอาจเป็นความคิดที่ดีที่จะลองสิ่งนี้เป็นกลุ่ม ด้วยวิธีนี้คุณสามารถใช้คุณสมบัติการยืนยันเพื่อให้แน่ใจว่าคุณจะไม่สลับสิ่งที่คุณไม่ต้องการ ฉันไม่แน่ใจว่าสามารถทำไดเรคทอรีกว้างได้หรือไม่
Samy Bencherif

คำตอบ:


104

คำสั่งนี้จะทำ (ทดสอบทั้ง Mac OS X Lion และ Kubuntu Linux)

# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

นี่คือวิธีการทำงาน:

  1. find . -type f -name '*.txt'ค้นหาในไดเรกทอรีปัจจุบัน ( .) และด้านล่างไฟล์ปกติทั้งหมด ( -type f) ที่มีชื่อลงท้ายด้วย.txt
  2. | ส่งผ่านเอาต์พุตของคำสั่งนั้น (รายการชื่อไฟล์) ไปยังคำสั่งถัดไป
  3. xargs รวบรวมชื่อไฟล์เหล่านั้นและมอบให้พวกเขาทีละคนเพื่อ sed
  4. sed -i '' -e 's/foo/bar/g'หมายถึง "แก้ไขไฟล์โดยไม่ต้องสำรองข้อมูลและทำการทดแทนต่อไปนี้ ( s/foo/bar) หลายครั้งต่อบรรทัด ( /g)" (ดูman sed)

โปรดทราบว่าส่วน 'ไม่มีการสำรองข้อมูล' ในบรรทัด 4 นั้นใช้ได้สำหรับฉันเพราะไฟล์ที่ฉันกำลังเปลี่ยนแปลงอยู่ภายใต้การควบคุมเวอร์ชันดังนั้นฉันสามารถยกเลิกได้อย่างง่ายดายหากเกิดข้อผิดพลาด


9
ไม่เคยไปป์หาเอาต์พุตไปที่ xargs โดยไม่มี-print0ตัวเลือก คำสั่งของคุณจะล้มเหลวในไฟล์ที่มีช่องว่าง ฯลฯ ในชื่อของพวกเขา
slhck

20
นอกจากนี้เพียงแค่find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +ทำสิ่งทั้งหมดนี้กับ GNU find
Daniel Andersson

6
ฉันได้รับsed: can't read : No such file or directoryเมื่อฉันเรียกใช้find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'แต่find . -name '*.md' -print0ให้รายชื่อของไฟล์จำนวนมาก
Martin Thoma

9
สิ่งนี้ใช้ได้กับฉันถ้าฉันลบช่องว่างระหว่าง-iและ''
แคนาดาลุค

3
ความหมายของคืออะไร''หลังจากที่sed -iเป็นสิ่งที่''มีบทบาท?
Jas

27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

สิ่งนี้จะลบการxargsพึ่งพา


4
สิ่งนี้ไม่ทำงานกับ GNU sedดังนั้นจะล้มเหลวในระบบส่วนใหญ่ GNU sedคุณจะต้องใส่ไม่มีช่องว่างระหว่างและ-i ''
slhck

1
คำตอบที่ได้รับการยอมรับจะช่วยอธิบายได้ดีกว่า แต่ +1 สำหรับการใช้ไวยากรณ์ที่ถูกต้อง
59

9

หากคุณใช้ Git คุณสามารถทำสิ่งนี้ได้:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-lแสดงรายการชื่อไฟล์เท่านั้น -zพิมพ์ไบต์ว่างหลังจากแต่ละผลลัพธ์

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


1
+1 ยิ่งใหญ่สำหรับโซลูชันนี้ find ... -print0 | xargs -0 sed ...โซลูชันที่เหลือไม่เพียงใช้เวลานาน แต่ยังเพิ่มบรรทัดใหม่ให้กับไฟล์ที่ไม่มีอยู่แล้วซึ่งเป็นสิ่งที่สร้างความรำคาญเมื่อทำงานภายใน repo คอมไพล์ git grepเปรียบเทียบแสงอย่างรวดเร็ว
Josh Kupershmidt

3

นี่คือฟังก์ชัน zsh / perl ของฉันที่ฉันใช้สำหรับสิ่งนี้:

change () {
        from=$1 
        shift
        to=$1 
        shift
        for file in $*
        do
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"
        done
}

และฉันจะรันมันโดยใช้

$ change foo bar **/*.java

(ตัวอย่างเช่น)


2

ลอง:

sed -i 's/foo/bar/g' $(find . -type f)

ทดสอบบน Ubuntu 12.04

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

โดยทั่วไปแล้วการใช้ช่องว่างในชื่อไดเรกทอรีและชื่อไฟล์เป็นวิธีปฏิบัติที่ไม่ดี

http://linuxcommand.org/lc3_lts0020.php

ดู "ข้อเท็จจริงสำคัญเกี่ยวกับชื่อไฟล์"


ลองใช้เมื่อคุณมีไฟล์ที่มีช่องว่างในชื่อของพวกเขา (มีกฎง่ายๆที่กล่าวว่า "หากสิ่งที่ดูเหมือนว่าดีเกินจริงอาจเป็นไปได้" ถ้าคุณมี "ค้นพบ" วิธีแก้ปัญหาที่มีขนาดกะทัดรัดกว่าสิ่งที่คนอื่นโพสต์ใน3½ปีคุณควรถามตัวเองว่าทำไม อาจจะเป็น.)
Scott

1

ใช้เชลล์สคริปต์นี้

ตอนนี้ฉันใช้เชลล์สคริปต์นี้ซึ่งรวมสิ่งต่าง ๆ ที่ฉันเรียนรู้จากคำตอบอื่น ๆ และจากการค้นหาเว็บ ฉันวางมันลงในไฟล์ที่เรียกว่าchangeในโฟลเดอร์ของฉันและทำ$PATHchmod +x change

#!/bin/bash
function err_echo {
  >&2 echo "$1"
}

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1
}

[ $# -eq 0 ] && err_echo "No args given" && usage

old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
done

0

กรณีการใช้งานของฉันคือฉันต้องการแทนที่ foo:/Drive_Letterด้วยfoo:/bar/baz/xyz ในกรณีของฉันฉันสามารถทำมันด้วยรหัสต่อไปนี้ ฉันอยู่ในไดเรกทอรีเดียวกันซึ่งมีไฟล์จำนวนมาก

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

หวังว่าจะช่วย


0

คำสั่งต่อไปนี้ทำงานได้ดีบน Ubuntu และ CentOS อย่างไรก็ตามภายใต้ OS XI ยังคงได้รับข้อผิดพลาด:

find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;

sed: 1: "./Root": รหัสคำสั่งไม่ถูกต้อง

เมื่อฉันพยายามผ่าน params ผ่าน xargs มันใช้งานได้ดีโดยไม่มีข้อผิดพลาด:

find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'

ความจริงที่ว่าคุณเปลี่ยน-iไป-i ''อาจจะมีความเกี่ยวข้องมากกว่าความจริงที่ว่าคุณเปลี่ยนไป-exec -print0 | xargs -0BTW -eคุณอาจไม่จำเป็นต้องใช้
สกอตต์

0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

ด้านบนทำงานเหมือนเครื่องราง แต่ด้วยไดเรกทอรีที่เชื่อมโยงฉันต้องเพิ่มการ-Lตั้งค่าสถานะ รุ่นสุดท้ายดูเหมือนว่า:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.