Jak mogę wykonać rekurencyjne wyszukiwanie i zamianę z wiersza poleceń?

84

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.

Nathan Long
źródło
Alternatywną odpowiedź na te same pytania można znaleźć tutaj stackoverflow.com/questions/9704020/…
dunxd
Dobrym pomysłem może być wypróbowanie tego w vimie. W ten sposób możesz użyć funkcji potwierdzania, aby upewnić się, że nie podmienisz czegoś, czego nie zamierzasz robić. Nie jestem pewien, czy można to zrobić w całym katalogu.
Samy Bencherif

Odpowiedzi:

104

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:

  1. 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
  2. | przekazuje dane wyjściowe tego polecenia (lista nazw plików) do następnego polecenia
  3. xargs zbiera te nazwy plików i podaje je jeden po drugim sed
  4. 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.

Nathan Long
źródło
9
Nigdy nie potokuj wyszukiwania danych wyjściowych do xargs bez tej -print0opcji. Twoje polecenie zawiedzie w przypadku plików ze spacjami itp. W ich nazwie.
slhck
20
Po prostu find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +zrobi to wszystko z GNU find.
Daniel Andersson
6
Dostaję, 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.
Martin Thoma,
9
Działa to dla mnie jeśli usunąć przestrzeń pomiędzy -ii''
kanadyjski Łk
3
Jaki jest sens ''po sed -ico jest ''rola?
Jas
27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

To usuwa xargszależność.

Ztyx
źródło
4
To nie działa z GNU sed, więc zawiedzie w większości systemów. GNU sedwymaga, abyś nie umieszczał spacji między -ii ''.
slhck 16.01.2013
1
Przyjęte odpowiedzi lepiej tłumaczą. ale +1 za użycie poprawnej składni.
orodbhen
9

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.)

David Winiecki
źródło
1
Duża +1 dla tego rozwiązania. Reszta 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.
Josh Kupershmidt
3

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)

Brian Agnew
źródło
2

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”

nexayq
źródło
Wypróbuj, gdy masz plik (i) ze spacjami w ich nazwach. (Istnieje ogólna zasada, która mówi: „Jeśli coś wydaje się zbyt piękne, aby mogło być prawdziwe, prawdopodobnie tak jest.” Jeśli „odkryłeś” rozwiązanie, które jest bardziej kompaktowe niż cokolwiek, co ktoś opublikował w ciągu 3 i pół roku, powinieneś zadać sobie pytanie, dlaczego to może być.)
Scott
1

Użyj tego skryptu powłoki

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
Nathan Long
źródło
0

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.

neo7
źródło
0

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/'
John Morgan
źródło
Fakt, że zmieniło -isię -i ''to chyba bardziej istotne niż fakt, że zmieniłeś -execsię -print0 | xargs -0. BTW, prawdopodobnie nie potrzebujesz -e.
Scott,
0
# 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'
sMajeed
źródło