Come posso eseguire una ricerca ricorsiva e sostituirla dalla riga di comando?


62

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.


Una risposta alternativa per le stesse domande può essere trovata qui stackoverflow.com/questions/9704020/...
dunxd

Risposte:


80

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:

  1. find . -type f -name '*.txt' trova, nella directory corrente ( . ) e sotto, tutti i file regolari ( -type f ) i cui nomi terminano in .txt
  2. | passa l'output di quel comando (un elenco di nomi di file) al comando successivo
  3. xargs raccoglie quei nomi di file e li passa a uno a uno sed
  4. 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.


9
Mai più pipe trovare l'output su xargs senza il -print0 opzione. Il tuo comando fallirà su file con spazi ecc. Nel loro nome.
slhck

17
Inoltre, solo find -name '*.txt' -exec sed -i 's/foo/bar/g' {} + farà tutto questo con GNU find.
Daniel Andersson

6
ottengo 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.
Martin Thoma

8
Questo funziona per me se rimuovo lo spazio tra il -i e il ''
Canadian Luke

3
Qual è il significato di '' dopo il sed -i qual 'é '' ruolo?
Jas

22
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

Questo rimuove il xargs dipendenza.


4
Questo non funziona con GNU sed, quindi fallirà sulla maggior parte dei sistemi. GNU sed richiede di non inserire spazi tra -i e ''.
slhck

1
Le risposte accettate fanno meglio a spiegarlo. ma +1 per usare la sintassi corretta.
orodbhen

6

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


1
Big +1 per questa soluzione. Il resto di 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.
Josh Kupershmidt

3

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)


2

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"


Provalo quando hai file con uno o più spazi nei loro nomi. (C'è una regola empirica che dice "Se qualcosa sembra troppo bello per essere vero, probabilmente lo è". Se hai "scoperto" una soluzione che è più compatta di qualsiasi altra persona abbia postato in 3 anni e mezzo, dovresti chiederti perché potrebbe essere.)
Scott

1

Usa questo script di shell

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

0

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


0

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/'

Il fatto che tu abbia cambiato -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.
Scott
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.