Trova e sostituisci all'interno di un file di testo da un comando Bash


561

Qual è il modo più semplice per trovare e sostituire una determinata stringa di input, ad esempio abc, e sostituirla con un'altra stringa, ad esempio XYZnel file /tmp/file.txt?

Sto scrivendo un'app e utilizzo IronPython per eseguire comandi tramite SSH, ma non conosco bene Unix e non so cosa cercare.

Ho sentito che Bash, oltre ad essere un'interfaccia a riga di comando, può essere un linguaggio di scripting molto potente. Quindi, se questo è vero, presumo che tu possa compiere azioni come queste.

Posso farlo con bash e qual è lo script più semplice (una riga) per raggiungere il mio obiettivo?

Risposte:


930

Il modo più semplice è usare sed (o perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Che invocherà sed per fare una modifica sul posto grazie -iall'opzione. Questo può essere chiamato da bash.

Se vuoi davvero usare solo bash, allora può funzionare:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

Questo scorre su ogni riga, effettuando una sostituzione e scrivendo in un file temporaneo (non voglio ostruire l'input). La mossa alla fine si sposta temporaneamente sul nome originale.


3
Solo che invocare mv è praticamente "non Bash" come usare sed. Ho quasi detto lo stesso dell'eco, ma è una shell incorporata.
magro

5
L'argomento -i per sed non esiste per Solaris (e vorrei pensare ad altre implementazioni), quindi tienilo a mente. Ho appena trascorso alcuni minuti a capirlo ...
Panky,

2
Nota per sé: sull'espressione regolare di sed: s/..../..../ - Substitutee /g - Global
checksum

89
Nota per gli utenti Mac che ottengono un invalid command code Cerrore ... Per le sostituzioni sul posto, BSD sedrichiede un'estensione del file dopo il -iflag perché salva un file di backup con l'estensione specificata. Ad esempio: sed -i '.bak' 's/find/replace/' /file.txt è possibile saltare il backup utilizzando una stringa vuota in questo modo:sed -i '' 's/find/replace/' /file.txt
Austin

8
Suggerimento: se si desidera utilizzare il repalce senza distinzione tra maiuscole e minuscoles/abc/XYZ/gi
Boris D. Teoharov il

166

La manipolazione dei file non viene normalmente eseguita da Bash, ma dai programmi invocati da Bash, ad esempio:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

La -ibandiera gli dice di fare una sostituzione sul posto.

Per man perlrunulteriori dettagli, vedere come eseguire il backup del file originale.


37
Il purista in me dice che non puoi essere sicuro che Perl sarà disponibile sul sistema. Ma oggi è molto raro. Forse sto mostrando la mia età.
magro

3
Puoi mostrare un esempio più complesso. Qualcosa come sostituire "chdir / blah" con "chdir / blah2". Ho provato perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, ma continuo a ricevere un errore con Non avere spazio tra pattern e la seguente parola è deprecata alla riga -e 1. Ineguagliata (in regex; contrassegnata da <- HERE in m / (chdir) () (<- QUI ?: \\ / at -e line 1.
CMCDragonkai

@CMCDragonkai Controlla questa risposta: stackoverflow.com/a/12061491/2730528
Alfonso Santiago

69

Sono stato sorpreso quando mi sono imbattuto in questo ...

Esiste un replacecomando fornito con il "mysql-server"pacchetto, quindi se lo hai installato provalo:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Vedi man replacedi più su questo.


12
Qui sono possibili due cose: a) replaceè un utile strumento indipendente e la gente di MySQL dovrebbe rilasciarlo separatamente e dipendere da esso b) replacerichiede un po 'di MySQL o_O Ad ogni modo, installare mysql-server per ottenere la sostituzione sarebbe la cosa sbagliata da fare :)
Philip Whitehouse,

funziona solo per mac? nel mio Ubuntu centos quel comando non esiste
paul

1
Questo perché non hai mysql-serverinstallato il pacchetto. Come sottolineato da @rayro, ne replacefa parte.
Phius

2
"Avviso: sostituire è obsoleto e verrà rimosso in una versione futura."
Steven Vachon,

1
Fare attenzione a non eseguire il comando REPLACE su Windows! Su Windows il comando REPLACE serve per una rapida replica dei file. Non pertinente per questa discussione.
Maor,

53

Questo è un vecchio post, ma per chiunque voglia usare le variabili come @centurian ha detto che le virgolette singole significano che nulla verrà espanso.

Un modo semplice per ottenere le variabili è quello di eseguire la concatenazione di stringhe poiché questa operazione viene eseguita dalla giustapposizione in bash, dovrebbe funzionare quanto segue:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt

39

Bash, come altre shell, è solo uno strumento per coordinare altri comandi. In genere si tenta di utilizzare i comandi UNIX standard, ma si può ovviamente usare Bash per invocare qualsiasi cosa, inclusi i propri programmi compilati, altri script shell, script Python e Perl ecc.

In questo caso, ci sono un paio di modi per farlo.

Se vuoi leggere un file e scriverlo su un altro file, eseguendo la ricerca / sostituzione mentre procedi, usa sed:

sed 's/abc/XYZ/g' <infile >outfile

Se si desidera modificare il file in atto (come se si aprisse il file in un editor, modificandolo, quindi salvandolo) fornire le istruzioni all'editor di riga 'ex'

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex è come vi senza la modalità a schermo intero. Puoi dare gli stessi comandi che avresti al prompt ':' di vi.


33

Ho trovato questo thread tra gli altri e sono d'accordo che contiene le risposte più complete, quindi sto aggiungendo anche il mio:

  1. sede edsono così utili ... a mano. Guarda questo codice da @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Quando la mia restrizione è di usarlo in uno script di shell, nessuna variabile può essere usata al posto di "abc" o "XYZ". Il BashFAQ sembra essere d'accordo con quello che ho capito almeno. Quindi, non posso usare:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt
    

    ma cosa possiamo fare? Come, @Johnny ha detto di usare un while read...ma, sfortunatamente non è la fine della storia. Quanto segue ha funzionato bene con me:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
    
  3. Come si può vedere se nullglobè impostato allora, si comporta in modo strano quando c'è una stringa che contiene un *come in:

    <VirtualHost *:80>
     ServerName www.example.com
    

    che diventa

    <VirtualHost ServerName www.example.com

    non c'è parentesi angolare finale e Apache2 non può nemmeno caricare.

  4. Questo tipo di analisi dovrebbe essere più lento della ricerca con un colpo e sostituire ma, come hai già visto, ci sono quattro variabili per quattro diversi modelli di ricerca che lavorano su un ciclo di analisi.

La soluzione più adatta che mi viene in mente con le ipotesi date del problema.


12
Nel tuo (2) puoi farlo sed -e "s/$x/$y/"e funzionerà. Non le doppie virgolette. Può diventare seriamente confuso se le stringhe nelle variabili stesse contengono caratteri con significato speciale. Ad esempio se x = "/" o x = "\". Quando si verificano questi problemi, probabilmente significa che si dovrebbe smettere di provare a utilizzare la shell per questo lavoro.
magro

Ciao magro, vedo che sei anche contrario all'utilizzo del Perl. Qual è la tua soluzione? Perché in effetti voglio cambiare dinamicamente un percorso in un file, il che significa che ho molto / nella stringa!
Mahdi,

20

Puoi usare sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

Utilizzare -iper "ignora maiuscole / minuscole" se non si è sicuri che il testo sia abco ABCo AbC, ecc.

Puoi usare finde sedse non conosci il tuo nome file:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Trova e sostituisci in tutti i file Python:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;

12

È inoltre possibile utilizzare il edcomando per eseguire la ricerca nel file e sostituire:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

Vedi di più in " Modifica dei file tramite script coned ".


3
Questa soluzione è indipendente dalle incompatibilità GNU / FreeBSD (Mac OSX) (a differenza sed -i <pattern> <filename>). Molto bella!
Peterino,

11

Se il file su cui stai lavorando non è così grande e archiviarlo temporaneamente in una variabile non è un problema, puoi utilizzare la sostituzione della stringa di Bash su tutto il file in una volta - non c'è bisogno di superarlo riga per riga:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

L'intero contenuto del file verrà trattato come una stringa lunga, comprese le interruzioni di riga.

XYZ può essere una variabile $replacement, ad esempio , e uno dei vantaggi di non usare sed qui è che non è necessario preoccuparsi che la ricerca o la sostituzione della stringa possa contenere il carattere delimitatore del modello sed (di solito, ma non necessariamente, /). Uno svantaggio è non poter usare espressioni regolari o nessuna delle operazioni più sofisticate di sed.


Qualche consiglio per usarlo con i caratteri di tabulazione? Per qualche ragione il mio script non trova nulla con le schede dopo essere passato da sed con un sacco di escape a questo metodo.
Brian Hannay,

2
Se vuoi inserire una scheda nella stringa che stai sostituendo, puoi farlo con la sintassi "virgolette singole" di Bash, quindi una scheda è rappresentata da $ '\ t' e puoi fare $ echo 'tab' $ '\ t''separated'> testfile; $ file_contents = $ (<testfile); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff il


5

Prova il seguente comando shell:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'

4
Questa è un'eccellente risposta a "come posso accidentalmente copiare tutti i file in tutte le sottodirectory" ma non sembra essere quello che viene richiesto qui.
Tripleee,

Questa sintassi non funzionerà con la versione BSD di sed, usa sed -i''invece.
Kenorb,

5

Per modificare il testo nel file in modo non interattivo, è necessario un editor di testo sul posto come vim.

Ecco un semplice esempio su come utilizzarlo dalla riga di comando:

vim -esnc '%s/foo/bar/g|:wq' file.txt

Ciò equivale a risposta @slim di ex redattore che è fondamentalmente la stessa cosa.

Ecco alcuni exesempi pratici.

Sostituzione del testo foocon barnel file:

ex -s +%s/foo/bar/ge -cwq file.txt

Rimozione di spazi bianchi finali per più file:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Risoluzione dei problemi (quando il terminale è bloccato):

  • Aggiungi -V1param per mostrare messaggi dettagliati.
  • Uscita forzata da: -cwq!.

Guarda anche:


Volevo fare le sostituzioni in modo interattivo. Quindi ho provato "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Ma il terminale è bloccato ora. Come possiamo effettuare le sostituzioni in modo interattivo senza che la shell bash si comporti in modo strano.
Vineeshvs

Per eseguire il debug, aggiungere -V1, per forzare l'uscita, utilizzare wq!.
Kenorb,

2

Puoi usare Python anche all'interno dello script bash. Non ho avuto molto successo con alcune delle risposte migliori qui e ho scoperto che funzionava senza la necessità di loop:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()

1

Puoi usare il comando rpl. Ad esempio, vuoi cambiare il nome di dominio nell'intero progetto php.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Questo non è un chiaro motivo di causa, ma è molto rapido e utile. :)

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.