Usando una shell come bash o zshell, come posso fare un 'find and replace' ricorsivo? In altre parole, voglio sostituire ogni occorrenza di "pippo" con "barra" in tutti i file in questa directory e nelle sue sottodirectory.
Usando una shell come bash o zshell, come posso fare un 'find and replace' ricorsivo? In altre parole, voglio sostituire ogni occorrenza di "pippo" con "barra" in tutti i file in questa directory e nelle sue sottodirectory.
Risposte:
Questo comando lo farà (testato su Mac OS X Lion e Kubuntu Linux).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Ecco come funziona:
find . -type f -name '*.txt'
trova, nella directory corrente ( .
) e sotto, tutti i file regolari ( -type f
) i cui nomi terminano in .txt
|
passa l'output di quel comando (un elenco di nomi di file) al comando successivo xargs
raccoglie quei nomi di file e li passa a uno a uno sed
sed -i '' -e 's/foo/bar/g'
significa "modifica il file in posizione, senza un backup, e apporta la seguente sostituzione ( s/foo/bar
) più volte per riga ( /g
) "(vedi man sed
) Nota che la parte "senza backup" nella riga 4 è OK per me, perché i file che sto cambiando sono comunque sotto controllo di versione, quindi posso facilmente annullare se c'è stato un errore.
-print0
opzione. Il tuo comando fallirà su file con spazi ecc. Nel loro nome.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
farà tutto questo con GNU find.
sed: can't read : No such file or directory
quando corro find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, ma find . -name '*.md' -print0
fornisce una lista di molti file.
-i
e il ''
''
dopo il sed -i
qual 'é ''
ruolo?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Questo rimuove il xargs
dipendenza.
sed
, quindi fallirà sulla maggior parte dei sistemi. GNU sed
richiede di non inserire spazi tra -i
e ''
.
Se stai usando Git, puoi farlo:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
elenca solo i nomi dei file. -z
stampa un byte null dopo ogni risultato.
Ho finito per fare questo perché alcuni file in un progetto non avevano una nuova riga alla fine del file e sed aggiunto una nuova riga anche quando non ha apportato altre modifiche. (Nessun commento sul fatto che i file debbano avere o meno una nuova riga alla fine).
find ... -print0 | xargs -0 sed ...
le soluzioni non solo richiedono molto più tempo, ma aggiungono anche nuove righe a file che non ne hanno già uno, il che è fastidioso quando si lavora all'interno di un repository git. git grep
si sta accendendo velocemente al confronto.
Ecco la mia funzione zsh / perl che uso per questo:
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
}
E lo eseguivo usando
$ change foo bar **/*.java
(per esempio)
Provare:
sed -i 's/foo/bar/g' $(find . -type f)
Testato su Ubuntu 12.04.
Questo comando NON funzionerà se i nomi di sottodirectory e / oi nomi di file contengono spazi, ma se li hai non usare questo comando in quanto non funzionerà.
In genere è una cattiva pratica utilizzare gli spazi nei nomi delle directory e nei nomi dei file.
http://linuxcommand.org/lc3_lts0020.php
Guarda "Informazioni importanti sui nomi dei file"
Ora uso questo script di shell, che combina le cose che ho imparato dalle altre risposte e dalla ricerca sul web. L'ho messo in un file chiamato change
in una cartella sul mio $PATH
e fatto 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
Il mio caso d'uso era che volevo sostituire foo:/Drive_Letter
con foo:/bar/baz/xyz
Nel mio caso sono riuscito a farlo con il seguente codice.
Ero nella stessa directory dove c'erano molti file.
find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'
spero che abbia aiutato
Il seguente comando ha funzionato bene su Ubuntu e CentOS; tuttavia, sotto OS X ho continuato a ricevere errori:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
sed: 1: "./Root": codice di comando non valido.
Quando ho provato a passare i parametri tramite xargs, ha funzionato senza errori:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
-i
a -i ''
è probabilmente più rilevante del fatto che tu abbia cambiato -exec
a -print0 | xargs -0
. A proposito, probabilmente non hai bisogno del -e
.