Come sostituire una stringa in più file nella riga di comando di Linux


461

Devo sostituire una stringa in molti file in una cartella, con sshaccesso solo al server. Come posso fare questo?

Risposte:


602
cd /path/to/your/folder
sed -i 's/foo/bar/g' *

Le occorrenze di "foo" saranno sostituite da "bar".

Su sistemi BSD come macOS, è necessario fornire un'estensione di backup come -i '.bak'"corruzione del rischio o contenuto parziale" per ogni pagina di manuale.

cd /path/to/your/folder
sed -i '.bak' 's/foo/bar/g' *

13
Questo non sembra funzionare per me se la stringa contiene spazi bianchi o caratteri speciali. Qualche idea sul perché potrebbe essere o devo sfuggirli in qualche modo? Grazie!
Matthew Herbst,

13
Almeno per la mia versione di sed, -irichiede una stringa dopo di essa, che viene aggiunta ai vecchi nomi di file. Quindi sed -i .bak 's/foo/bar/g' *.xxsposta tutti i .xxfile sul .xx.baknome equivalente e quindi genera i .xxfile con la sostituzione foo → bar.
Anaphory,

24
Se qualcuno vuole cercare più opzioni, c'è una risposta sullo scambio di stack unix che copre più casi d'uso sito unix.stackexchange.com/a/112024/13488
Reddy

19
@MatthewHerbst per sfuggire agli spazi, usa \ like sed -i 's/foo\ with\ spaces/bar/g' *. Suppongo che tu l'abbia scoperto dopo così tanto tempo ... ma in questo modo rimane per gli altri trovare lo stesso problema.
manuelvigarcia,

5
Per qualcosa di ricorsivo potresti provare quanto segue. Si noti che non funziona se l'elenco dei file è enorme. sed -i -e 's/foo/bar/g' $(find /home/user/base/dir)
Scot

244

Simile alla risposta di Kaspar ma con la bandiera g per sostituire tutte le occorrenze su una riga.

find ./ -type f -exec sed -i 's/string1/string2/g' {} \;

Per maiuscole e minuscole globali:

find ./ -type f -exec sed -i 's/string1/string2/gI' {} \;

23
Se sei su OSX e i tuoi pattern potrebbero contenere punti e vuoi una sostituzione sul posto (nessun file di backup) dovresti usare LC_ALL=C find ./ -type f -exec sed -i '' -e 's/proc.priv/priv/g' {} \;(vedi questo post e questo )
Jonathan H

2
Questo ha sicuramente funzionato per me, in quanto consente il filtraggio con -name "* .js" '
Marcello de Sales

1
Un'opzione dettagliata sarebbe interessante, ma puoi semplicemente ripetere il grep per vedere se le modifiche sono state apportate. Nota: per i caratteri jolly, prova '-name "* .php"' e grep non funziona correttamente con ricorsione e caratteri jolly, devi aggiungere --include=*.whatevercon -r
PJ Brunet,

12
Non farlo dalla radice di un repository Git estratto. Si potrebbe corrompere accidentalmente il suo database in .git/. Assicurati di cdscendere a un livello inferiore.
erikprice

1
Ho appena fatto questo nel mio repo git e adesso git statusritorni: error: bad index file sha1 signature.... fatal: index file corrupt. Cosa dà?
Jin

165

La risposta di @ kev è buona, ma riguarda solo i file nella directory immediata. L'esempio seguente utilizza grep per trovare ricorsivamente i file. Funziona per me ogni volta.

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @

Ripartizione del comando

grep -r : --recursive , legge in modo ricorsivo tutti i file in ciascuna directory.
grep -l : --print-with-match , stampa il nome di ogni file che ha una corrispondenza, invece di stampare le linee corrispondenti.
grep -i : --ignore-case .

xargs : trasforma STDIN in argomenti, segui questa risposta .
Il comando xargs -i @ ~ contiene @ ~ : un segnaposto per l'argomento da utilizzare in una posizione specifica nel comando ~ , il segno @ è un segnaposto che può essere sostituito da qualsiasi stringa.

sed -i : modifica i file in atto, senza backup.
sed s / regexp / sostituzione / : sostituisce regexp di corrispondenza della stringa con sostituzione .
sed s / regexp / sostituzione / g : globale , effettua la sostituzione per ogni incontro anziché solo il primo incontro.


13
questo non ha funzionato per me, ma ha funzionato:grep --include={*.php,*.html,*.js} -rnl './' -e "old-word" | xargs -i@ sed -i 's/old-word/new-word/g' @
Dennis

7
@pymarco Puoi per favore spiegare questo comando? Non so perché dovessi usare xargs invece di usare solo sed dopo |, anche perché -inel comando xargs? Ho letto nel manuale che è deprecato e dovrebbe usare -Iinvece. E viene @utilizzato come delimitatore per l'inizio e la fine del modello?
Edson Horacio Junior,

2
la domanda è specifica per Linux.
pymarco,

6
Su osx questo è ok:grep -rli 'old-word' * | xargs -I@ sed -i '' 's/2.0.0/latest/g' @
Philippe Sultan,

1
Sarebbe bello se rli@
abbassi

37

Questo ha funzionato per me:

find ./ -type f -exec sed -i 's/string1/string2/' {} \;

Howerver, questo non ha fatto: sed -i 's/string1/string2/g' *. Forse "pippo" non doveva essere stringa1 e "barra" non stringa2.


1
È perché sed tratta il jolly * in modo diverso. [abc] * indica un numero arbitrario di caratteri dell'insieme {a, b, c}. [a-z0-9] * funziona in modo simile al carattere jolly *.
thepiercingarrow

8
Su OSX utilizzare:find ./ -type f -exec sed -i '' -e 's/string1/string2/' {} \;
Shaheen Ghiassy,

32

Ci sono alcune risposte standard a questo già elencato. In generale, è possibile utilizzare find per elencare in modo ricorsivo i file e quindi eseguire le operazioni con sed o perl .

Per gli usi più rapidi, potresti trovare il comando rpl molto più facile da ricordare. Ecco la sostituzione (foo -> bar), ricorsivamente su tutti i file:

rpl -R foo bar .

Probabilmente dovrai installarlo ( apt-get install rplo simile).

Tuttavia, per i lavori più difficili che coinvolgono espressioni regolari e sostituzione sostitutiva, o rinominazioni di file e ricerca e sostituzione, lo strumento più generale e potente di cui sono a conoscenza è repren , un piccolo script Python che ho scritto qualche tempo fa per alcuni compiti di rinominazione e refactoring più spinosi. I motivi che potresti preferire sono:

  • Supporta la ridenominazione dei file e la ricerca e sostituzione dei contenuti dei file.
  • Vedi le modifiche prima di impegnarti a eseguire la ricerca e sostituisci.
  • Supporta espressioni regolari con le modalità di sostituzione posteriore, parole intere, senza distinzione tra maiuscole e minuscole e preservazione delle maiuscole (sostituisci foo -> bar, Foo -> Bar, FOO -> BAR).
  • Funziona con più sostituzioni, inclusi swap (foo -> bar e bar -> foo) o set di sostituzioni non univoche (foo -> bar, f -> x).

Per usarlo, pip install repren. Controlla il README per esempi.


1
Wow, repren è eccellente! L'ho appena usato per cambiare parte di una parola all'interno di nomi di classe, metodi e variabili durante la ridenominazione dei file in modo che corrispondano a più di 1.000 file di intestazione e sorgente C ++ e ha funzionato perfettamente la prima volta con un comando. Grazie!
Bob Kocisko,

29

Per sostituire una stringa in più file è possibile utilizzare:

grep -rl string1 somedir/ | xargs sed -i 's/string1/string2/g'

Per esempio

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

Blog di origine


20

Per sostituire un percorso all'interno dei file (evitando i caratteri di escape) è possibile utilizzare il comando seguente:

sed -i 's@old_path@new_path@g'

Il segno @ indica che tutti i caratteri speciali devono essere ignorati in una stringa seguente.


2
Esattamente quello che stavo cercando in tutte le altre risposte. Vale a dire, come gestire caratteri speciali, ad esempio quando si cambia una stringa che è un percorso. Grazie. Sembrava una grande svista nelle altre risposte.
ispirato il

19

Nel caso in cui la tua stringa abbia una barra (/), puoi cambiare il delimitatore in '+'.

find . -type f -exec sed -i 's+http://example.com+https://example.com+g' {} +

Questo comando verrebbe eseguito in modo ricorsivo nella directory corrente.


Grazie! Aiutato a cambiare un sacco di riferimenti ../domain.comadomain.com
Jack Rogers

12

Le occorrenze della prima riga di "pippo" verranno sostituite con "barra". E puoi usare la seconda riga per controllare.

grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
grep 'foo' -r * | awk -F: {'print $1'} | sort -n | uniq -c

6
Perché stai collegando al tuo blog? Contiene esattamente lo stesso testo della tua risposta.
David Post

1
mi piacegrep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
Prachi,

10

Se hai un elenco di file che puoi usare

replace "old_string" "new_string" -- file_name1 file_name2 file_name3

Se hai tutti i file che puoi usare

replace "old_string" "new_string" -- *

Se si dispone di un elenco di file con estensione, è possibile utilizzare

replace "old_string" "new_string" -- *.extension

2
in realtà, "--file" dovrebbe essere semplicemente "-", almeno nella mia versione
simpleuser

4
Questa utility è distribuita all'interno dei pacchetti MySQL.
Dmitry Ginzburg,

2
Anche se vorrei apprezzare la soluzione, che funziona senza citare la linea regolare come espressione regolare.
Dmitry Ginzburg,

1
Funziona bene - e se vuoi sostituire tutte le stringhe in più file, che finiscono per esempio in ".txt", puoi semplicemente farloreplace "old_string" "new_string" -- *.txt
tsveti_iko

1
È meglio aggiungere da dove ottenere questa replaceutility. Senza queste informazioni, questa risposta è incompleta.
Sitesh,

8

"Puoi anche usare find e sed, ma trovo che questa piccola riga di perl funzioni bene.

perl -pi -w -e 's/search/replace/g;' *.php
  • -e significa eseguire la seguente riga di codice.
  • -i significa modifica sul posto
  • -w scrivere avvertimenti
  • -p loop

"(Estratto da http://www.liamdelahunty.com/tips/linux_search_and_replace_multiple_files.php )

I miei migliori risultati provengono dall'uso di perl e grep (per garantire che il file abbia l'espressione di ricerca)

perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )

8

Dato che vuoi cercare la stringa searche sostituirla con replacepiù file, questa è la mia formula a riga singola testata in battaglia :

grep -RiIl 'search' | xargs sed -i 's/search/replace/g'

Spiegazione rapida grep:

  • -R - ricerca ricorsiva
  • -i - senza distinzione tra maiuscole e minuscole
  • -I - salta i file binari (vuoi testo, giusto?)
  • -l- stampa un semplice elenco come output. Necessario per gli altri comandi

L'output grep viene quindi reindirizzato a sed (tramite xargs) che viene effettivamente utilizzato per sostituire il testo. Il -iflag modificherà direttamente il file. Rimuoverlo per una sorta di modalità "marcia a secco".


4

Ho inventato la mia soluzione prima di trovare questa domanda (e risposte). Ho cercato diverse combinazioni di "sostituire" "diversi" e "xml", perché quella era la mia applicazione, ma non ho trovato questo particolare.

Il mio problema: avevo file XML primavera con dati per casi di test, contenenti oggetti complessi. Un refactor sul codice sorgente Java ha cambiato molte classi e non si applicava ai file di dati XML. Per salvare i dati dei casi di test, avevo bisogno di cambiare tutti i nomi delle classi in tutti i file xml, distribuiti su più directory. Tutto mentre salvavo le copie di backup dei file xml originali (anche se questo non era un must, poiché il controllo della versione mi avrebbe salvato qui).

Stavo cercando una combinazione di find+ sed, perché ha funzionato per me in altre situazioni, ma non con più sostituzioni contemporaneamente.

Poi ho trovato chiedere la risposta a Ubuntu e mi ha aiutato a costruire la mia riga di comando:

find -name "*.xml" -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;

E ha funzionato perfettamente (bene, il mio caso ha avuto sei diversi sostituti). Ma tieni presente che toccherà tutti i file * .xml nella directory corrente. Per questo motivo, e se sei responsabile nei confronti di un sistema di controllo della versione, potresti voler prima filtrare e passare solo a sedquelli che hanno effettivamente le stringhe che desideri; piace:

find -name "*.xml" -exec grep -e "firstWord" -e "secondWord" -e "thirdWord" {} \; -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;

e per Windows, ho appena scoperto che esiste un modo per trovare la stringa - non ho ancora verificato come sostituirla - in un solo comando: findstr /spin /c:"quéquieresbuscar" *.xml sarà utile.
manuelvigarcia,

3
grep --include={*.php,*.html} -rnl './' -e "old" | xargs -i@ sed -i 's/old/new/g' @

3

Davvero zoppo, ma non sono riuscito a far funzionare nessuno dei comandi sed su OSX, quindi ho fatto invece questa cosa stupida:

:%s/foo/bar/g
:wn

^ - copia queste tre righe negli appunti (sì, includi la riga finale), quindi:

vi *

e tieni premuto command-v fino a quando non dice che non ci sono più file.

Dumb ... hacky ... efficace ...



2

L'editor di flusso modifica più file "inplace" quando viene richiamato con l'opzione -i, che accetta un file di backup che termina come argomento. Così

sed -i.bak 's/foo/bar/g' *

sostituisce foocon barin tutti i file in questa cartella, ma non scende nelle sottocartelle. Questo genererà comunque un nuovo .bakfile per ogni file nella tua directory. Per fare questo in modo ricorsivo per tutti i file in questa directory e tutte le sue sottodirectory, è necessario un aiuto, come find, per attraversare l'albero delle directory.

find ./ -print0 | xargs -0 sed -i.bak 's/foo/bar/g' *

findconsente ulteriori restrizioni su quali file modificare, specificando ulteriori argomenti come find ./ -name '*.php' -or -name '*.html' -print0, se necessario.


Nota: GNU sednon richiede la fine di un file, sed -i 's/foo/bar/g' *funzionerà anche; FreeBSD sedrichiede un'estensione, ma consente uno spazio in mezzo, quindi sed -i .bak s/foo/bar/g *funziona.


2

script per comando multiedito

multiedit [-n PATTERN] OLDSTRING NEWSTRING

Dalla risposta di Kaspar ho creato uno script bash per accettare gli argomenti della riga di comando e, facoltativamente, limitare i nomi dei file che corrispondono a un modello. Salva nel tuo $ PATH e rendilo eseguibile, quindi usa semplicemente il comando sopra.

Ecco la sceneggiatura:

#!/bin/bash
_help="\n
Replace OLDSTRING with NEWSTRING recursively starting from current directory\n
multiedit [-n PATTERN] OLDSTRING NEWSTRING\n

[-n PATTERN] option limits to filenames matching PATTERN\n
Note: backslash escape special characters\n
Note: enclose STRINGS with spaces in double quotes\n
Example to limit the edit to python files:\n
multiedit -n \*.py \"OLD STRING\" NEWSTRING\n"

# ensure correct number of arguments, otherwise display help...
if [ $# -lt 2 ] || [ $# -gt 4 ]; then echo -e $_help ; exit ; fi
if [ $1 == "-n" ]; then  # if -n option is given:
        # replace OLDSTRING with NEWSTRING recursively in files matching PATTERN
        find ./ -type f -name "$2" -exec sed -i "s/$3/$4/g" {} \;
else
        # replace OLDSTRING with NEWSTRING recursively in all files
        find ./ -type f -exec sed -i "s/$1/$2/" {} \;
fi

1

Se il file contiene barre rovesciate (in genere i percorsi) puoi provare qualcosa del genere:

sed -i -- 's,<path1>,<path2>,g' *

ex:

sed -i -- 's,/foo/bar,/new/foo/bar,g' *.sh (in all shell scripts available)

1

Per mantenere il mio nodo inglese personale, ho scritto uno script di utilità che aiuta a sostituire più coppie di stringhe vecchie / nuove, per tutti i file in una directory ricorsivamente.

Le coppie multiple di stringhe vecchie / nuove sono gestite in una mappa hash.

La directory può essere impostata tramite riga di comando o variabile di ambiente, la mappa è codificata nello script, ma è possibile modificare il codice da caricare da un file, se necessario.

Richiede bash 4.2, a causa di alcune nuove funzionalità.

en_standardize.sh:

#! /bin/bash
# (need bash 4.2+,)
# 
# Standardize phonetic symbol of English.
# 
# format:
#   en_standardize.sh [<dir>]
# 
# params:
# * dir
#   target dir, optional,
#   if not specified then use environment variable "$node_dir_en",
#   if both not provided, then will not execute,
# * 
# 

paramCount=$#

# figure target dir,
if [ $paramCount -ge 1 ]; then # dir specified
    echo -e "dir specified (in command):\n\t$1\n"
    targetDir=$1
elif [[ -v node_dir_en ]]; then # environable set,
    echo -e "dir specified (in environment vairable):\n\t$node_dir_en\n"
    targetDir=$node_dir_en
else # environable not set,
    echo "dir not specified, won't execute"
    exit
fi

# check whether dir exists,
if [ -d $targetDir ]; then
    cd $targetDir
else
    echo -e "invalid dir location:\n\t$targetDir\n"
    exit
fi

# initial map,
declare -A itemMap
itemMap=( ["ɪ"]="i" ["ː"]=":" ["ɜ"]="ə" ["ɒ"]="ɔ" ["ʊ"]="u" ["ɛ"]="e")

# print item maps,
echo 'maps:'
for key in "${!itemMap[@]}"; do
    echo -e "\t$key\t->\t${itemMap[$key]}"
done
echo -e '\n'

# do replace,
for key in "${!itemMap[@]}"; do
    grep -rli "$key" * | xargs -i@ sed -i "s/$key/${itemMap[$key]}/g" @
done

echo -e "\nDone."
exit

1

L'uso del comando ack sarebbe molto più veloce in questo modo:

ack '25 Essex' -l | xargs sed -i 's/The\ fox \jump/abc 321/g'

Anche se hai uno spazio bianco nel risultato della ricerca. Devi scappare.


0

Sto dando un esempio per correggere un errore shebang comune nelle fonti Python.

Puoi provare l'approccio grep / sed. Eccone uno che funziona con GNU sed e non romperà un repository git:

$ grep -rli --exclude '*.git*' '#!/usr/bin/python' . | xargs -I {} \
gsed -i '' -e 's/#!\/usr\/bin\/python/#!\/usr\/bin\/env python/' {}

Oppure puoi usare greptile :)

$ greptile -x .py -l -i -g '#!/usr/bin/env python' -r '#!/usr/bin/python' .

Ho appena testato il primo script e anche il secondo dovrebbe funzionare. Fai attenzione ai personaggi di escape, penso che dovrebbe essere più facile usare greptile nella maggior parte dei casi. Certo, puoi fare molte cose interessanti con sed, e per questo potrebbe essere preferibile padroneggiarlo usandolo con xargs.


0

L'ho trovato da un altro post (non ricordo quale) e sebbene non sia il più elegante, è semplice e come utente Linux alle prime armi non mi ha dato problemi

for i in *old_str* ; do mv -v "$i" "${i/\old_str/new_str}" ; done

se hai spazi o altri caratteri speciali usa un \

for i in *old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done

per le stringhe nelle sottodirectory usare **

for i in *\*old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done

0

Il comando seguente può essere utilizzato per cercare prima i file e sostituire i file:

find . | xargs grep 'search string' | sed 's/search string/new string/g'

Per esempio

find . | xargs grep abc | sed 's/abc/xyz/g'

0

Esiste un modo più semplice utilizzando un semplice file di script:

   # sudo chmod +x /bin/replace_string_files_present_dir

apri il file in gedit o in un editor a tua scelta, io uso gedit qui.

   # sudo gedit /bin/replace_string_files_present_dir

Quindi nell'editor incolla quanto segue nel file

   #!/bin/bash
   replace "oldstring" "newstring" -- *
   replace "oldstring1" "newstring2" -- *
   #add as many lines of replace as there are your strings to be replaced for 
   #example here i have two sets of strings to replace which are oldstring and 
   #oldstring1 so I use two replace lines.

Salva il file, chiudi gedit , quindi chiudere il terminale o semplicemente chiuderlo e quindi avviarlo per poter caricare il nuovo script aggiunto.

Passare alla directory in cui sono presenti più file che si desidera modificare. Quindi eseguire:

  #replace_string_files_present_dir

Premi Invio e questo sostituirà automaticamente oldstring e oldstring1 in tutti i file che li contengono rispettivamente con il newstring e il newstring1 corretti .

Salterà tutte le directory e i file che non contengono le vecchie stringhe.

Questo potrebbe aiutare ad eliminare il noioso lavoro di digitazione nel caso in cui tu abbia più directory con file che richiedono la sostituzione di stringhe. Tutto quello che devi fare è accedere a ciascuna di quelle directory, quindi eseguire:

#replace_string_files_present_dir

Tutto quello che devi fare è assicurarti di aver incluso o aggiunto tutte le stringhe di sostituzione come ti ho mostrato sopra:

replace "oldstring" "newstring" -- *

alla fine del file / bin / replace_string_files_present_dir .

Per aggiungere una nuova stringa di sostituzione basta aprire lo script che abbiamo creato digitando quanto segue nel terminale:

sudo gedit /bin/replace_string_files_present_dir

Non preoccuparti del numero di stringhe di sostituzione che aggiungi, non avranno alcun effetto se la stringa vecchia non viene trovata.


Di solito, quando le persone chiedono "come $ {whatever}" in bash, chiedono una versione compatta da includere in uno script o in un'istruzione di lavoro CI (diciamo a .gitlab-ci.ymlo a travis.yml). Ogni giro consistente nello scrivere uno script per eseguirlo in un secondo momento è un anti-pattern, perché dovrai quindi creare script per la creazione dello script (e io
divento

-4

$ sostituisci "Da" "A" - `trova / percorso / a / cartella -tipo f`

(sostituisci - un'utilità di sostituzione delle stringhe del sistema di database MySQL)

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.