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 sedsed -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.
-print0opcji. 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 directorykiedy uruchamiam find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', ale find . -name '*.md' -print0daje listę wielu plików.
-ii''
''po sed -ico jest ''rola?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
To usuwa xargszależność.
sed, więc zawiedzie w większości systemów. GNU sedwymaga, abyś nie umieszczał spacji między -ii ''.
Jeśli używasz Git, możesz to zrobić:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-lwyświetla tylko nazwy plików. -zwypisuje 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 grepdla 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 changew folderze $PATHi 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_Letterz 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/'
-isię -i ''to chyba bardziej istotne niż fakt, że zmieniłeś -execsię -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ć -Ldo 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'