Menggunakan shell seperti bash atau zshell, bagaimana saya bisa melakukan 'temukan dan ganti' secara rekursif? Dengan kata lain, saya ingin mengganti setiap kemunculan 'foo' dengan 'bar' di semua file dalam direktori ini dan subdirektori.
Menggunakan shell seperti bash atau zshell, bagaimana saya bisa melakukan 'temukan dan ganti' secara rekursif? Dengan kata lain, saya ingin mengganti setiap kemunculan 'foo' dengan 'bar' di semua file dalam direktori ini dan subdirektori.
Jawaban:
Perintah ini akan melakukannya (diuji pada Mac OS X Lion dan Kubuntu Linux).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Begini cara kerjanya:
find . -type f -name '*.txt'
menemukan, dalam direktori saat ini ( .
) dan di bawah, semua file biasa ( -type f
) yang namanya berakhir dengan.txt
|
meneruskan output dari perintah itu (daftar nama file) ke perintah berikutnyaxargs
mengumpulkan nama file itu dan menyerahkannya satu per satu ke sed
sed -i '' -e 's/foo/bar/g'
berarti "mengedit file di tempat, tanpa cadangan, dan melakukan penggantian berikut ( s/foo/bar
) beberapa kali per baris ( /g
)" (lihat man sed
)Perhatikan bahwa bagian 'tanpa cadangan' di baris 4 OK untuk saya, karena file yang saya ubah berada di bawah kontrol versi, jadi saya dapat dengan mudah membatalkan jika ada kesalahan.
-print0
opsi. Perintah Anda akan gagal pada file dengan spasi dll dalam namanya.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
lakukan semua ini dengan GNU find.
sed: can't read : No such file or directory
ketika saya menjalankan find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, tetapi find . -name '*.md' -print0
memberikan daftar banyak file.
-i
dan''
''
setelah sed -i
apa ''
peran?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Ini menghilangkan xargs
ketergantungan.
sed
, jadi akan gagal pada kebanyakan sistem. GNU sed
mengharuskan Anda untuk tidak memberi jarak antara -i
dan ''
.
Jika Anda menggunakan Git maka Anda dapat melakukan ini:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
hanya mencantumkan nama file. -z
mencetak byte nol setelah setiap hasil.
Saya akhirnya melakukan ini karena beberapa file dalam proyek tidak memiliki baris baru di akhir file, dan menambahkan baris baru bahkan ketika itu tidak membuat perubahan lain. (Tidak ada komentar apakah file harus memiliki baris baru di akhir. 🙂)
find ... -print0 | xargs -0 sed ...
solusi tidak hanya membutuhkan waktu lebih lama tetapi juga menambahkan baris baru ke file yang belum memilikinya, yang merupakan gangguan ketika bekerja di dalam git repo. git grep
pencahayaan cepat sebagai perbandingan.
Inilah fungsi zsh / perl saya yang saya gunakan untuk ini:
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
}
Dan saya akan mengeksekusinya menggunakan
$ change foo bar **/*.java
(sebagai contoh)
Mencoba:
sed -i 's/foo/bar/g' $(find . -type f)
Diuji pada Ubuntu 12,04.
Perintah ini TIDAK akan berfungsi jika nama subdirektori dan / atau nama file mengandung spasi, tetapi jika Anda memilikinya, jangan gunakan perintah ini karena tidak akan berfungsi.
Ini adalah praktik yang buruk untuk menggunakan spasi dalam nama direktori dan nama file.
http://linuxcommand.org/lc3_lts0020.php
Lihat "Fakta penting tentang nama file"
Saya sekarang menggunakan skrip shell ini, yang menggabungkan hal-hal yang saya pelajari dari jawaban lain dan dari pencarian di web. Saya menempatkannya di file yang disebut change
folder $PATH
dan saya lakukan 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
Kasus penggunaan saya adalah saya ingin mengganti
foo:/Drive_Letter
dengan foo:/bar/baz/xyz
Dalam kasus saya, saya bisa melakukannya dengan kode berikut. Saya berada di lokasi direktori yang sama di mana ada banyak file.
find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'
harapan itu membantu.
Perintah berikut bekerja dengan baik di Ubuntu dan CentOS; Namun, di bawah OS XI terus mendapatkan kesalahan:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
sed: 1: "./Root": kode perintah tidak valid.
Ketika saya mencoba melewati params melalui xargs itu berfungsi dengan baik tanpa kesalahan:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
-i
menjadi -i ''
mungkin lebih relevan daripada fakta bahwa Anda berubah -exec
menjadi -print0 | xargs -0
. BTW, Anda mungkin tidak perlu -e
.
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Di atas berfungsi seperti pesona, tetapi dengan direktori yang ditautkan, saya harus menambahkan -L
bendera padanya. Versi finalnya terlihat seperti:
# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'