Używając powłoki typu bash lub zshell, w jaki sposób mogę wykonać rekurencyjne polecenie „znajdź i zamień”? Innymi słowy, chcę zastąpić każde wystąpienie „foo” słowem „bar” we wszystkich plikach w tym katalogu i jego podkatalogach.
Używając powłoki typu bash lub zshell, w jaki sposób mogę wykonać rekurencyjne polecenie „znajdź i zamień”? Innymi słowy, chcę zastąpić każde wystąpienie „foo” słowem „bar” we wszystkich plikach w tym katalogu i jego podkatalogach.
Odpowiedzi:
To polecenie to zrobi (przetestowane zarówno w systemie Mac OS X Lion, jak i Kubuntu Linux).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Oto jak to działa:
find . -type f -name '*.txt'
znajduje w bieżącym katalogu ( .
) i poniżej wszystkie zwykłe pliki ( -type f
), których nazwy kończą się na.txt
|
przekazuje dane wyjściowe tego polecenia (lista nazw plików) do następnego poleceniaxargs
zbiera te nazwy plików i podaje je jeden po drugim sed
sed -i '' -e 's/foo/bar/g'
oznacza „edytuj plik na miejscu, bez kopii zapasowej, i wykonaj następujące zastąpienie ( s/foo/bar
) wiele razy w wierszu ( /g
)” (patrz man sed
)Zauważ, że część „bez kopii zapasowej” w linii 4 jest dla mnie OK, ponieważ zmieniane przeze mnie pliki i tak są pod kontrolą wersji, więc mogę łatwo cofnąć, jeśli wystąpił błąd.
-print0
opcji. Twoje polecenie zawiedzie w przypadku plików ze spacjami itp. W ich nazwie.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
zrobi to wszystko z GNU find.
sed: can't read : No such file or directory
kiedy uruchamiam find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, ale find . -name '*.md' -print0
daje listę wielu plików.
-i
i''
''
po sed -i
co jest ''
rola?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
To usuwa xargs
zależność.
sed
, więc zawiedzie w większości systemów. GNU sed
wymaga, abyś nie umieszczał spacji między -i
i ''
.
Jeśli używasz Git, możesz to zrobić:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
wyświetla tylko nazwy plików. -z
wypisuje bajt zerowy po każdym wyniku.
Skończyło się na tym, ponieważ niektóre pliki w projekcie nie miały nowej linii na końcu pliku, a sed dodał nową linię, nawet jeśli nie wprowadził żadnych innych zmian. (Brak komentarza na temat tego, czy pliki powinny mieć nowy wiersz na końcu.)
find ... -print0 | xargs -0 sed ...
rozwiązań nie tylko zajmuje dużo dłużej, ale także dodaje nowe wiersze do plików, które jeszcze go nie mają, co jest uciążliwe podczas pracy w repozytorium git. git grep
dla porównania szybko się świeci.
Oto moja funkcja zsh / perl, której używam do tego:
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
}
I wykonałbym to za pomocą
$ change foo bar **/*.java
(na przykład)
Próbować:
sed -i 's/foo/bar/g' $(find . -type f)
Testowane na Ubuntu 12.04.
To polecenie NIE będzie działać, jeśli nazwy podkatalogów i / lub nazwy plików zawierają spacje, ale jeśli je masz, nie używaj tego polecenia, ponieważ nie będzie działać.
Zasadą jest używanie spacji w nazwach katalogów i nazw plików.
http://linuxcommand.org/lc3_lts0020.php
Zobacz „Ważne fakty dotyczące nazw plików”
Teraz używam tego skryptu powłoki, który łączy rzeczy, których nauczyłem się z innych odpowiedzi i z przeszukiwania sieci. Umieściłem go w pliku o nazwie change
w folderze $PATH
i tak zrobiłem 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
Mój przypadek użycia była chciałem wymienić
foo:/Drive_Letter
z foo:/bar/baz/xyz
W moim przypadku udało mi się to zrobić za pomocą następującego kodu. Byłem w tym samym katalogu, w którym było mnóstwo plików.
find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'
mam nadzieję, że pomogło.
Poniższe polecenie działało dobrze w Ubuntu i CentOS; jednak w OS XI ciągle pojawiały się błędy:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
sed: 1: „./Root”: nieprawidłowy kod polecenia.
Kiedy próbowałem przekazać parametry przez xargs, działało dobrze bez błędów:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
-i
się -i ''
to chyba bardziej istotne niż fakt, że zmieniłeś -exec
się -print0 | xargs -0
. BTW, prawdopodobnie nie potrzebujesz -e
.
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Powyższe działało jak urok, ale z połączonymi katalogami muszę dodać -L
do niego flagę. Ostateczna wersja wygląda następująco:
# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'