การใช้เชลล์เช่น bash หรือ zshell ฉันจะทำ 'ค้นหาและแทนที่' แบบเรียกซ้ำได้อย่างไร? กล่าวอีกนัยหนึ่งฉันต้องการแทนที่ 'foo' ด้วย 'bar' ทุกครั้งในไฟล์ทั้งหมดในไดเรกทอรีนี้และไดเรกทอรีย่อย
การใช้เชลล์เช่น bash หรือ zshell ฉันจะทำ 'ค้นหาและแทนที่' แบบเรียกซ้ำได้อย่างไร? กล่าวอีกนัยหนึ่งฉันต้องการแทนที่ 'foo' ด้วย 'bar' ทุกครั้งในไฟล์ทั้งหมดในไดเรกทอรีนี้และไดเรกทอรีย่อย
คำตอบ:
คำสั่งนี้จะทำ (ทดสอบทั้ง 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'
นี่คือวิธีการทำงาน:
find . -type f -name '*.txt'
ค้นหาในไดเรกทอรีปัจจุบัน ( .
) และด้านล่างไฟล์ปกติทั้งหมด ( -type f
) ที่มีชื่อลงท้ายด้วย.txt
|
ส่งผ่านเอาต์พุตของคำสั่งนั้น (รายการชื่อไฟล์) ไปยังคำสั่งถัดไปxargs
รวบรวมชื่อไฟล์เหล่านั้นและมอบให้พวกเขาทีละคนเพื่อ sed
sed -i '' -e 's/foo/bar/g'
หมายถึง "แก้ไขไฟล์โดยไม่ต้องสำรองข้อมูลและทำการทดแทนต่อไปนี้ ( s/foo/bar
) หลายครั้งต่อบรรทัด ( /g
)" (ดูman sed
)โปรดทราบว่าส่วน 'ไม่มีการสำรองข้อมูล' ในบรรทัด 4 นั้นใช้ได้สำหรับฉันเพราะไฟล์ที่ฉันกำลังเปลี่ยนแปลงอยู่ภายใต้การควบคุมเวอร์ชันดังนั้นฉันสามารถยกเลิกได้อย่างง่ายดายหากเกิดข้อผิดพลาด
-print0
ตัวเลือก คำสั่งของคุณจะล้มเหลวในไฟล์ที่มีช่องว่าง ฯลฯ ในชื่อของพวกเขา
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
ทำสิ่งทั้งหมดนี้กับ GNU find
sed: can't read : No such file or directory
เมื่อฉันเรียกใช้find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
แต่find . -name '*.md' -print0
ให้รายชื่อของไฟล์จำนวนมาก
-i
และ''
''
หลังจากที่sed -i
เป็นสิ่งที่''
มีบทบาท?
หากคุณใช้ Git คุณสามารถทำสิ่งนี้ได้:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
แสดงรายการชื่อไฟล์เท่านั้น -z
พิมพ์ไบต์ว่างหลังจากแต่ละผลลัพธ์
ฉันลงเอยด้วยการทำเช่นนี้เพราะไฟล์บางไฟล์ในโครงการไม่มีการขึ้นบรรทัดใหม่ในตอนท้ายของไฟล์และเพิ่มบรรทัดใหม่แม้ว่าจะไม่มีการเปลี่ยนแปลงอื่น ๆ ก็ตาม (ไม่มีความคิดเห็นว่าไฟล์ควรมีการขึ้นบรรทัดใหม่ในตอนท้าย🙂)
find ... -print0 | xargs -0 sed ...
โซลูชันที่เหลือไม่เพียงใช้เวลานาน แต่ยังเพิ่มบรรทัดใหม่ให้กับไฟล์ที่ไม่มีอยู่แล้วซึ่งเป็นสิ่งที่สร้างความรำคาญเมื่อทำงานภายใน repo คอมไพล์ git grep
เปรียบเทียบแสงอย่างรวดเร็ว
นี่คือฟังก์ชัน 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
(ตัวอย่างเช่น)
ลอง:
sed -i 's/foo/bar/g' $(find . -type f)
ทดสอบบน Ubuntu 12.04
คำสั่งนี้จะไม่ทำงานหากชื่อไดเรกทอรีย่อยและ / หรือชื่อไฟล์มีช่องว่าง แต่ถ้าคุณมีให้พวกเขาไม่ได้ใช้คำสั่งนี้เพราะมันจะไม่ทำงาน
โดยทั่วไปแล้วการใช้ช่องว่างในชื่อไดเรกทอรีและชื่อไฟล์เป็นวิธีปฏิบัติที่ไม่ดี
http://linuxcommand.org/lc3_lts0020.php
ดู "ข้อเท็จจริงสำคัญเกี่ยวกับชื่อไฟล์"
ตอนนี้ฉันใช้เชลล์สคริปต์นี้ซึ่งรวมสิ่งต่าง ๆ ที่ฉันเรียนรู้จากคำตอบอื่น ๆ และจากการค้นหาเว็บ ฉันวางมันลงในไฟล์ที่เรียกว่าchange
ในโฟลเดอร์ของฉันและทำ$PATH
chmod +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
กรณีการใช้งานของฉันคือฉันต้องการแทนที่
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'
หวังว่าจะช่วย
คำสั่งต่อไปนี้ทำงานได้ดีบน 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 -0
BTW -e
คุณอาจไม่จำเป็นต้องใช้
# 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'