Sử dụng shell như bash hoặc zshell, làm cách nào tôi có thể thực hiện đệ quy 'tìm và thay thế'? Nói cách khác, tôi muốn thay thế mọi lần xuất hiện của 'foo' bằng 'bar' trong tất cả các tệp trong thư mục này và các thư mục con của nó.
Sử dụng shell như bash hoặc zshell, làm cách nào tôi có thể thực hiện đệ quy 'tìm và thay thế'? Nói cách khác, tôi muốn thay thế mọi lần xuất hiện của 'foo' bằng 'bar' trong tất cả các tệp trong thư mục này và các thư mục con của nó.
Câu trả lời:
Lệnh này sẽ thực hiện (được thử nghiệm trên cả Mac OS X Lion và Kubfox Linux).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Đây là cách nó hoạt động:
find . -type f -name '*.txt'
tìm thấy, trong thư mục hiện tại ( .
) và bên dưới, tất cả các tệp thông thường ( -type f
) có tên kết thúc bằng.txt
|
chuyển đầu ra của lệnh đó (danh sách tên tệp) sang lệnh tiếp theoxargs
tập hợp những tên tập tin đó và đưa chúng từng cái một sed
sed -i '' -e 's/foo/bar/g'
có nghĩa là "chỉnh sửa tệp tại chỗ, không có bản sao lưu và thực hiện thay thế sau ( s/foo/bar
) nhiều lần trên mỗi dòng ( /g
)" (xem man sed
)Lưu ý rằng phần 'không có bản sao lưu' trong dòng 4 là phù hợp với tôi, vì dù sao các tệp tôi đang thay đổi đều nằm dưới sự kiểm soát phiên bản, vì vậy tôi có thể dễ dàng hoàn tác nếu có lỗi.
-print0
tùy chọn. Lệnh của bạn sẽ thất bại trên các tệp có khoảng trắng, v.v. trong tên của chúng.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
sẽ làm tất cả điều này với GNU find.
sed: can't read : No such file or directory
khi tôi chạy find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, nhưng find . -name '*.md' -print0
đưa ra một danh sách nhiều tệp.
-i
và''
''
sau khi sed -i
sự là những gì ''
vai trò?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Điều này loại bỏ sự xargs
phụ thuộc.
sed
, vì vậy sẽ thất bại trên hầu hết các hệ thống. GNU sed
yêu cầu bạn không đặt khoảng trống giữa -i
và ''
.
Nếu bạn đang sử dụng Git thì bạn có thể làm điều này:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
chỉ liệt kê tên tập tin. -z
in một byte null sau mỗi kết quả.
Tôi đã kết thúc việc này vì một số tệp trong một dự án không có dòng mới ở cuối tệp và sed đã thêm một dòng mới ngay cả khi nó không có thay đổi nào khác. (Không có nhận xét nào về việc các tập tin có nên có một dòng mới ở cuối hay không.)
find ... -print0 | xargs -0 sed ...
giải pháp còn lại không chỉ mất nhiều thời gian hơn mà còn thêm các dòng mới vào các tệp chưa có, điều này gây phiền toái khi làm việc bên trong repo git. git grep
là ánh sáng nhanh bằng cách so sánh.
Đây là hàm zsh / perl của tôi, tôi sử dụng cho việc này:
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
}
Và tôi sẽ thực hiện nó bằng cách sử dụng
$ change foo bar **/*.java
(ví dụ)
Thử:
sed -i 's/foo/bar/g' $(find . -type f)
Đã thử nghiệm trên Ubuntu 12.04.
Lệnh này sẽ KHÔNG hoạt động nếu tên thư mục con và / hoặc tên tệp chứa khoảng trắng, nhưng nếu bạn không sử dụng lệnh này thì nó sẽ không hoạt động.
Nói chung, việc sử dụng khoảng trắng trong tên thư mục và tên tệp là một thực tế xấu.
http://linuxcommand.org/lc3_lts0020.php
Nhìn vào "Sự thật quan trọng về tên tệp"
Bây giờ tôi sử dụng tập lệnh shell này, kết hợp những điều tôi học được từ các câu trả lời khác và từ việc tìm kiếm trên web. Tôi đặt nó trong một tập tin được gọi change
trong một thư mục trên $PATH
và đã làm 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
Trường hợp sử dụng của tôi là tôi muốn thay thế
foo:/Drive_Letter
bằng foo:/bar/baz/xyz
Trong trường hợp của tôi, tôi đã có thể làm điều đó với mã sau đây. Tôi đã ở cùng một vị trí thư mục nơi có nhiều tập tin.
find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'
Hy vọng rằng đã giúp.
Lệnh sau hoạt động tốt trên Ubuntu và CentOS; tuy nhiên, trong OS XI liên tục gặp lỗi:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
sed: 1: "./Root": mã lệnh không hợp lệ.
Khi tôi thử chuyển thông số qua xargs, nó hoạt động tốt mà không có lỗi:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
-i
đến -i ''
có lẽ là thích hợp hơn so với thực tế là bạn đã thay đổi -exec
đến -print0 | xargs -0
. BTW, bạn có thể không cần -e
.
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Ở trên hoạt động như một bùa mê, nhưng với các thư mục được liên kết, tôi sẽ thêm -L
cờ vào nó. Phiên bản cuối cùng trông như:
# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'