La sostituzione delle stringhe nei file in base a determinati criteri di ricerca è un'operazione molto comune. Come posso
- sostituisci la stringa
foo
conbar
in tutti i file nella directory corrente? - Fare lo stesso ricorsivamente per le sottodirectory?
- sostituire solo se il nome del file corrisponde a un'altra stringa?
- sostituire solo se la stringa si trova in un determinato contesto?
- sostituire se la stringa si trova su un certo numero di riga?
- sostituisci più stringhe con la stessa sostituzione
- sostituisci più stringhe con sostituzioni diverse
Risposta accettata:
1. Sostituzione di tutte le occorrenze di una stringa con un'altra in tutti i file nella directory corrente:
Questi sono per i casi in cui sai che la directory contenga solo file regolari e che desideri elaborare tutti i file non nascosti. In caso contrario, utilizzare gli approcci in 2.
Tutti i sed
le soluzioni in questa risposta presuppongono GNU sed
. Se utilizzi FreeBSD o macOS, sostituisci -i
con -i ''
. Si noti inoltre che l'uso di -i
passare con qualsiasi versione di sed
ha alcune implicazioni sulla sicurezza del filesystem ed è sconsigliabile in qualsiasi script che intendi distribuire in alcun modo.
-
Non ricorsivo, solo file in questa directory:
sed -i -- 's/foo/bar/g' * perl -i -pe 's/foo/bar/g' ./*
(il perl
uno fallirà per i nomi di file che terminano con |
o spazio)).
-
File ricorsivi e regolari (compresi quelli nascosti ) in questa e in tutte le sottodirectory
find . -type f -exec sed -i 's/foo/bar/g' {} +
Se stai usando zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(potrebbe fallire se l'elenco è troppo grande, vedi
zargs
per aggirare).Bash non può verificare direttamente la presenza di file regolari, è necessario un ciclo (le parentesi graffe evitano di impostare le opzioni a livello globale):
( shopt -s globstar dotglob; for file in **; do if [[ -f $file ]] && [[ -w $file ]]; then sed -i -- 's/foo/bar/g' "$file" fi done )
I file vengono selezionati quando sono file effettivi (-f) e sono scrivibili (-w).
2. Sostituisci solo se il nome del file corrisponde a un'altra stringa / ha un'estensione specifica / è di un certo tipo ecc:
-
Non ricorsivo, solo file in questa directory:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
-
File ricorsivi e regolari in questa e in tutte le sottodirectory
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
Se stai usando bash (le parentesi graffe evitano di impostare le opzioni a livello globale):
( shopt -s globstar dotglob sed -i -- 's/foo/bar/g' **baz* sed -i -- 's/foo/bar/g' **.baz )
Se stai usando zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.) sed -i -- 's/foo/bar/g' **/*.baz(D.)
Il --
serve a dire a sed
che non verranno più forniti flag nella riga di comando. Questo è utile per proteggere dai nomi di file che iniziano con -
.
-
Se un file è di un certo tipo, ad esempio, eseguibile (vedi
man find
per ulteriori opzioni):find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Sostituisci solo se la stringa si trova in un determinato contesto
-
Sostituisci
foo
conbar
solo se esiste unbaz
più avanti sulla stessa riga:sed -i 's/foo(.*baz)/bar1/' file
In sed
, utilizzando ( )
salva tutto ciò che è tra parentesi e puoi quindi accedervi con 1
. Esistono molte varianti di questo tema, per saperne di più su tali espressioni regolari, vedere qui.
-
Sostituisci
foo
conbar
solo sefoo
si trova nella colonna 3d (campo) del file di input (assumendo campi separati da spazi):gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(richiede gawk
4.1.0 o successivo).
-
Per un campo diverso usa semplicemente
$N
doveN
è il numero del campo di interesse. Per un separatore di campo diverso (:
in questo esempio) usa:gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Un'altra soluzione che utilizza perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
NOTA:sia il awk
e perl
le soluzioni influiranno sulla spaziatura nel file (rimuovere gli spazi vuoti iniziali e finali e convertire le sequenze di spazi vuoti in uno spazio in quelle righe che corrispondono). Per un campo diverso, usa $F[N-1]
dove N
è il numero di campo desiderato e per un separatore di campo diverso utilizzare (il $"=":"
imposta il separatore del campo di output su :
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
-
Sostituisci
foo
conbar
solo sulla 4a riga:sed -i '4s/foo/bar/g' file gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file perl -i -pe 's/foo/bar/g if $.==4' file
4. Operazioni multiple di sostituzione:sostituisci con stringhe diverse
-
Puoi combinare
sed
comandi:sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Tieni presente che l'ordine è importante (sed 's/foo/bar/g; s/bar/baz/g'
sostituirà foo
con baz
).
-
o comandi Perl
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
-
Se hai un numero elevato di pattern, è più facile salvare i tuoi pattern e le loro sostituzioni in un
sed
file di script:#! /usr/bin/sed -f s/foo/bar/g s/baz/zab/g
-
Oppure, se hai troppe coppie di pattern perché quanto sopra sia fattibile, puoi leggere le coppie di pattern da un file (due pattern separati da spazi, $pattern e $replacement, per riga):
while read -r pattern replacement; do sed -i "s/$pattern/$replacement/" file done < patterns.txt
-
Sarà piuttosto lento per lunghi elenchi di pattern e file di dati di grandi dimensioni, quindi potresti voler leggere i pattern e creare un
sed
script da loro invece. Quanto segue presuppone uno <spazio> delimitatore separa un elenco di MATCH<spazio>REPLACE coppie che si verificano una per riga nel filepatterns.txt
:sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt | sed -f- ./editfile >outfile
Il formato sopra è in gran parte arbitrario e, ad esempio, non consente uno <spazio> in una delle PARTITA o SOSTITUIRE . Il metodo è però molto generale:in pratica, se riesci a creare un flusso di output che assomigli a un sed
script, quindi puoi ottenere quel flusso come sed
script specificando sed
's file di script come -
stdin.
-
Puoi combinare e concatenare più script in modo simile:
SOME_PIPELINE | sed -e'#some expression script' -f./script_file -f- -e'#more inline expressions' ./actual_edit_file >./outfile
Un POSIX sed
concatenerà tutti gli script in uno nell'ordine in cui appaiono sulla riga di comando. Nessuno di questi deve terminare con un n
ewline.
-
grep
può funzionare allo stesso modo:sed -e'#generate a pattern list' <in | grep -f- ./grepped_file
-
Quando si lavora con stringhe fisse come pattern, è buona norma evitare i metacaratteri dell'espressione regolare . Puoi farlo abbastanza facilmente:
sed 's/[]$&^*./[]/\&/g s| *([^ ]*) *([^ ]*).*|s/1/2/g| ' <patterns.txt | sed -f- ./editfile >outfile
5. Operazioni multiple di sostituzione:sostituisci più pattern con la stessa stringa
-
Sostituisci uno qualsiasi di
foo
,bar
obaz
confoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
-
o
perl -i -pe 's/foo|bar|baz/foobar/g' file