Cambia più file


194

Il seguente comando sta modificando correttamente il contenuto di 2 file.

sed -i 's/abc/xyz/g' xaa1 xab1 

Ma quello che devo fare è cambiare molti di questi file in modo dinamico e non conosco i nomi dei file. Voglio scrivere un comando che leggerà tutti i file dalla directory corrente a partire da xa*e seddovrebbe cambiare il contenuto del file.


63
Intendi sed -i 's/abc/xyz/g' xa*?
Paul R,

3
Le risposte qui non sono sufficienti. Vedi unix.stackexchange.com/questions/112023/…
Isaac

Risposte:


136

Meglio ancora:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

perché nessuno sa quanti file ci siano ed è facile infrangere i limiti della riga di comando.

Ecco cosa succede quando ci sono troppi file:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#

18
Se ci sono molti file, infrangerai il limite della riga di comando nel forcomando. Per proteggerti da ciò, dovresti usarefind ... | xargs ...
Glenn Jackman,

1
Non conosco l'implementazione, ma il modello "xa *" deve espandersi ad un certo punto. La shell esegue l'espansione in modo diverso forrispetto a quando fa per echoo grep?
Glenn Jackman,

4
vedi la risposta aggiornata. se hai bisogno di maggiori informazioni, per favore, fai una domanda ufficiale, così le persone potrebbero aiutarti.
lenik,

5
Nel comando sed, è necessario utilizzare "$i"invece di $ievitare la divisione di parole su nomi di file con spazi. Altrimenti è molto bello.
Wildcard il

4
Per quanto riguarda l'elenco, credo che la differenza sia che forfa parte della sintassi del linguaggio, nemmeno un builtin. Perché sed -i 's/old/new' *, l'espansione di *must ALL deve essere passata come arglist a sed, e sono abbastanza sicuro che ciò debba avvenire prima che il sedprocesso possa essere avviato. Usando il forloop, l'arglist completo (l'espansione di *) non viene mai passato come comando, memorizzato solo nella memoria della shell e ripetuto. Non ho alcun riferimento per questo, però, sembra probabile che sia la differenza. (Mi piacerebbe sentire da qualcuno più esperto ...)
Wildcard il

167

Sono sorpreso che nessuno abbia menzionato l'argomento -exec da trovare, che è destinato a questo tipo di caso d'uso, sebbene avvierà un processo per ciascun nome file corrispondente:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

In alternativa, si potrebbe usare xargs, che invocherà meno processi:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

O più semplicemente usa la + variante exec invece di ;in find per consentire a find di fornire più di un file per chiamata di sottoprocesso:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +

8
Ho dovuto modificare il comando in questa risposta in questo modo: find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;questa è la posizione per il comando find ./e una coppia di virgolette singole dopo -iper OSX.
shelbydz,

Il comando find funziona come è fornito da ealfonso, ./è uguale .e dopo -iha solo il parametro backupsuffix.
uhausbrand,

L' -execopzione di ricerca insieme {} +è sufficiente per risolvere il problema come indicato, e dovrebbe andare bene per la maggior parte dei requisiti. Ma xargsè una scelta migliore in generale perché consente anche l'elaborazione parallela con l' -popzione. Quando l'espansione glob è abbastanza grande da sovraccaricare la lunghezza della riga di comando, è probabile che anche tu tragga vantaggio da una velocità su una corsa sequenziale.
Amit Naidu,

78

Potresti usare grep e sed insieme. Ciò consente di cercare ricorsivamente le sottodirectory.

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)

3
Il vantaggio di questo metodo per me era che avrei potuto infilarmi in un grep -vper evitare le cartelle gitgrep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
Martin Lyne,

la migliore soluzione per un mac!
Marchese Blount,

30

Tali comandi non funzioneranno come impostazione predefinita sedfornita con Mac OS X.

Da man 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

Provato

sed -i '.bak' 's/old/new/g' logfile*

e

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

Entrambi funzionano bene.


2
@sumek Ecco una sessione terminale di esempio su OS X che mostra sed sostituendo tutte le occorrenze: GitHub Gist
funroll

Ho usato questo per sostituire due diverse righe in tutti i file di configurazione del mio sito Web con una riga di seguito. sed -i.bak "s / supercache_proxy_config / proxy_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" siti-disponibili / * Non dimenticare di eliminare i file * .bak quando hai finito per il file per l'igiene del sistema.
Josiah

19

@PaulR ha pubblicato questo post come commento, ma le persone dovrebbero vederlo come una risposta (e questa risposta funziona meglio per le mie esigenze):

sed -i 's/abc/xyz/g' xa*

Funzionerà con una moderata quantità di file, probabilmente nell'ordine delle decine, ma probabilmente non nell'ordine dei milioni .


Supponiamo di avere barre rovesciate nelle sostituzioni. Un altro esempio con i percorsi dei file sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn.
Léo Léopold Hertz

10

Un altro modo più versatile è usare find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')

1
l'output di quel comando find viene espanso, quindi questo non risolve il problema. Invece dovresti usare -exec
ealfonso l'

@erjoalgo funziona perché il comando sed può gestire più file di input. L'espansione del comando find è esattamente necessaria per farlo funzionare.
Dkinzer,

funziona fintanto che il numero di file non supera i limiti della riga di comando.
ealfonso,

Tale limite dipende solo dalle risorse di memoria disponibili per la macchina ed è esattamente uguale al limite per exec.
Dkinzer,

4
Questo semplicemente non è vero. Nel tuo comando sopra, $ (trova ...) viene espanso in un singolo comando, che potrebbe essere molto lungo se ci sono molti file corrispondenti. Se è troppo lungo (ad esempio nel mio sistema il limite è di circa 2097152 caratteri) potresti ricevere un errore: "Elenco argomenti troppo lungo" e il comando fallirà. Si prega di google questo errore per ottenere qualche informazione al riguardo.
ealfonso,

2

Sto usando findper attività simili. È abbastanza semplice: devi passarlo come argomento per sedquesto:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

In questo modo non è necessario scrivere loop complessi ed è semplice vedere quali file si intende modificare, basta eseguire findprima di eseguire sed.


1
Questo è esattamente lo stesso della risposta di @ dkinzer .
Mr. Tao,

0

puoi fare

Cerca il testo " xxxx " e lo sostituirà con " yyyy "

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'

0

Se sei in grado di eseguire uno script, ecco cosa ho fatto per una situazione simile:

Usando un dizionario / hashMap (array associativo) e variabili per il sedcomando, possiamo fare un ciclo attraverso l'array per sostituire diverse stringhe. Includere un carattere jolly nel name_patternconsentirà di sostituire sul posto nei file con un modello (questo potrebbe essere qualcosa di simile name_pattern='File*.txt') in una directory specifica ( source_dir). Tutte le modifiche vengono scritte nel logfileneldestin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

Innanzitutto, viene dichiarata la matrice di coppie, ogni coppia è una stringa di sostituzione, quindi WHAT1verrà sostituita FOR1e OTHER_string_to replacesostituita string replacednel file File.txt. Nel ciclo l'array viene letto, il primo membro della coppia viene recuperato come replace_what=$ie il secondo come replace_for=$j. Il findcomando cerca nella directory il nome del file (che può contenere un carattere jolly) e sed -isostituisce negli stessi file ciò che è stato precedentemente definito. Alla fine ho aggiunto agrep reindirizzato al file di registro per registrare le modifiche apportate ai file.

Questo ha funzionato per me GNU Bash 4.3 sed 4.2.2e basato sulla risposta di VasyaNovikov per Loop over tuples in bash .

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.