Bash veya zshell gibi bir kabuk kullanarak nasıl özyinelemeli 'bul ve değiştir' yapabilirim? Başka bir deyişle, bu dizindeki ve alt dizinlerindeki tüm dosyalardaki her 'foo' oluşumunu 'bar' ile değiştirmek istiyorum.
Bash veya zshell gibi bir kabuk kullanarak nasıl özyinelemeli 'bul ve değiştir' yapabilirim? Başka bir deyişle, bu dizindeki ve alt dizinlerindeki tüm dosyalardaki her 'foo' oluşumunu 'bar' ile değiştirmek istiyorum.
Yanıtlar:
Bu komut yapacak (hem Mac OS X Lion hem de Kubuntu Linux'ta test edilmiştir).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
İşte nasıl çalışıyor:
find . -type f -name '*.txt'
Geçerli dizinde ( .
) ve altında, -type f
isimleri biten tüm düzenli dosyaları ( ) bulur ..txt
|
bu komutun çıktısını (bir dosya listesi) bir sonraki komuta geçirirxargs
bu dosya adlarını toplar ve birer birer verir sed
sed -i '' -e 's/foo/bar/g'
"dosyayı yedekleme yerine yerinde düzenleme ve aşağıdaki değiştirme ( s/foo/bar
) satır başına birden çok kez yapma ( /g
)" anlamına gelir (bkz. man sed
)4. satırdaki 'yedekleme olmadan' bölümünün benim için uygun olduğunu unutmayın, çünkü değiştirdiğim dosyalar yine de sürüm kontrolü altında, bu nedenle bir hata olursa kolayca geri alabilirim.
-print0
seçeneği olmadan xargs'e çıktı bulamaz . Komutunuz, adlarında boşluk vs. olan dosyalarda başarısız olacaktır.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
tüm bunları GNU bulgusu ile yapacağız.
sed: can't read : No such file or directory
ben çalıştırdığınızda find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, ancak find . -name '*.md' -print0
birçok dosyaların bir listesini verir.
-i
ve''
''
ardından sed -i
neyin anlamı nedir ''
?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Bu xargs
bağımlılığı ortadan kaldırır .
sed
, bu yüzden çoğu sistemde başarısız olacak. GNU ve sed
arasında boşluk bırakmanızı gerektirmez . -i
''
Git kullanıyorsanız, şunları yapabilirsiniz:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
sadece dosya isimlerini listeler. -z
Her sonuçtan sonra boş bir bayt yazdırır.
Bunu bitirdim çünkü bir projedeki bazı dosyaların dosyanın sonunda yeni bir satırı yoktu ve sed başka bir değişiklik yapmamış olsa bile yeni bir satır ekledi. (Dosyaların sonunda yeni bir satır olması gerekip gerekmediğine dair yorum yok. 🙂)
find ... -print0 | xargs -0 sed ...
Çözümlerin geri kalanı sadece çok daha uzun sürmekle kalmıyor, aynı zamanda önceden bulunmayan dosyalara yeni satırlar ekliyor, bu da git repo içinde çalışırken sıkıntı yaratıyor. git grep
karşılaştırıldığında hızlı bir şekilde aydınlatıyor.
İşte bunun için kullandığım benim zsh / perl işlevi:
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
}
Ve bunu kullanarak yürütürdüm
$ change foo bar **/*.java
(Örneğin)
Deneyin:
sed -i 's/foo/bar/g' $(find . -type f)
Ubuntu 12.04'te test edilmiştir.
Bu komut, eğer alt dizin isimleri ve / veya dosya isimleri boşluklar içeriyorsa işe yaramaz, fakat sizde varsa, bu komutu çalışmaz gibi kullanmazlar.
Dizin isimlerinde ve dosya isimlerinde boşluk kullanmak genellikle kötü bir uygulamadır.
http://linuxcommand.org/lc3_lts0020.php
"Dosya adları hakkında önemli bilgiler" konusuna bakın.
Şimdi, diğer cevaplardan ve web’deki aramalardan öğrendiğim şeyleri birleştiren bu kabuk betiğini kullanıyorum. Benim change
üzerinde bir klasörde adı verilen bir dosyaya yerleştirdim $PATH
ve yaptı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
Benim kullanım durumu ben değiştirmek istediğini oldu
foo:/Drive_Letter
ile foo:/bar/baz/xyz
Benim durumumda Aşağıdaki kod ile bunu başardı. Aynı klasör konumunda, çok sayıda dosyanın bulunduğu yerdeydim.
find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'
yardımcı oldu umarım.
Aşağıdaki komut Ubuntu ve CentOS'ta iyi çalıştı; ancak, OS XI kapsamında hatalar almaya devam etti:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
sed: 1: "./Root": geçersiz komut kodu.
Paraşütçeleri xargs aracılığıyla geçirmeyi denediğimde hatasız bir şekilde çalıştı:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
-i
ile -i ''
muhtemelen değişmiş olmasından daha önemlidir -exec
için -print0 | xargs -0
. Btw, muhtemelen ihtiyacınız yok -e
.
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Yukarıdaki bir cazibe gibi çalıştı, ancak bağlantılı dizinlerde, -L
ona bayrak ekledim. Son versiyon şuna benziyor:
# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'