sed modifica file in atto


300

Sto cercando di scoprire se è possibile modificare un file in un singolo comando sed senza trasmettere manualmente il contenuto modificato in un nuovo file e quindi rinominare il nuovo file con il nome del file originale. Ho provato l' -iopzione ma il mio sistema Solaris ha detto che -iè un'opzione illegale. C'è un modo diverso?


7
-iè un'opzione in gnu sed, ma non è in sed standard. Tuttavia, trasmette il contenuto a un nuovo file e quindi rinomina il file in modo che non sia quello desiderato.
William Pursell,

2
in realtà, è quello che voglio, voglio solo non essere esposto a dover svolgere il banale compito di rinominare il nuovo file con il nome originale
anfibio

3
Quindi è necessario riaffermare la domanda.
William Pursell,

3
@amphibient: ti dispiacerebbe prefissare il titolo della tua domanda con la parola "Solaris"? Il valore della tua domanda si sta perdendo. Si prega di vedere i commenti sotto la mia risposta. Grazie.
Steve,

1
@Steve: ho rimosso di nuovo il prefisso Solaris dal titolo perché non è assolutamente esclusivo di Solaris.
triplo il

Risposte:


477

L' -iopzione esegue lo streaming del contenuto modificato in un nuovo file e quindi lo rinomina dietro le quinte, comunque.

Esempio:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

e

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

su macOS .


3
almeno lo fa per me, quindi non devo
anfibio

2
Quindi potresti provare a compilare la GNU sed sul tuo sistema.
Choroba,

3
esempio:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin,

2
Questo è meglio della soluzione perl. In qualche modo non crea alcun file .swap .... grazie!
matematica

3
@maths: Sì, ma forse da qualche altra parte o per un tempo più breve?
Choroba,

72

Su un sistema in cui sednon è possibile modificare i file in atto, penso che la soluzione migliore sarebbe quella di utilizzare perl:

perl -pi -e 's/foo/bar/g' file.txt

Sebbene ciò crei un file temporaneo, sostituisce l'originale perché è stato fornito un suffisso / estensione vuoto sul posto.


14
Non solo crea un file temporaneo, ma interrompe anche i collegamenti reali (ad esempio, invece di modificare il contenuto del file, sostituisce il file con un nuovo file con lo stesso nome.) A volte questo è il comportamento desiderato, a volte lo è accettabile, ma quando ci sono più collegamenti a un file è quasi sempre un comportamento sbagliato.
William Pursell,

3
@DwightSpencer: credo che tutti i sistemi senza Perl siano rotti. Un singolo comando supera una catena di comandi quando si desidera disporre di autorizzazioni elevate e deve essere utilizzato sudo. Nella mia mente, la domanda è su come evitare la creazione e la ridenominazione manuale di un file temporaneo senza dover installare l'ultimo GNU o BSD sed. Usa solo Perl.
Steve,

4
@PetrPeller: Davvero? Se si legge attentamente la domanda, si dovrebbe capire che il PO sta cercando di evitare qualcosa di simile, sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt. Solo perché la modifica sul posto effettua la ridenominazione utilizzando un file temporaneo, non significa che deve eseguire una ridenominazione manuale quando l'opzione non è disponibile. Ci sono altri strumenti là fuori che possono fare quello che vuole, e Perl è la scelta ovvia. In che modo questa non è una risposta alla domanda originale?
Steve

2
@a_arias: hai ragione; lui ha fatto. Ma non può ottenere ciò che vuole usando Solaris sed. La domanda era in realtà una molto buona, ma il suo valore è stato diminuito da persone che o non sanno leggere pagine man o non può essere preso la briga di realtà domande leggere qui su SO.
Steve,

4
@EdwardGarson: IIRC, Solaris viene fornito con Perl ma non Python o Ruby. E come ho detto prima, l'OP non può ottenere il risultato desiderato usando Solaris sed.
Steve,

56

Nota che su OS X potresti ricevere strani errori come "codice comando non valido" o altri strani errori quando esegui questo comando. Per risolvere questo problema, provare

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

Questo perché nella versione OSX di sed, l' -iopzione prevede un extensionargomento, pertanto il comando viene effettivamente analizzato come extensionargomento e il percorso del file viene interpretato come codice del comando. Fonte: https://stackoverflow.com/a/19457213


1
Questa è un'altra stranezza di OS X. Fortunatamente, un brew install gnu-sedinsieme a un PATHti permetterà di usare la versione GNU di sed. Grazie per averlo menzionato; un grattacapo definito.
Doug

23

Quanto segue funziona bene sul mio mac

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

Stiamo sostituendo foo con la barra nel file di esempio. Il backup del file originale verrà salvato in sample.bak

Per la modifica in linea senza backup, utilizzare il comando seguente

sed -i'' 's/foo/bar/g' sample

In che modo prevenire la conservazione dei file di backup?
Petr Peller,

@PetrPeller, Puoi usare il comando citato per lo stesso ... sed -i '' 's / foo / bar / g' sample
minhas23

18

Una cosa da notare, sednon è possibile scrivere file da solo, poiché l'unico scopo di sed è agire come un editor sullo "stream" (ovvero pipeline di stdin, stdout, stderr e altri >&nbuffer, socket e simili). Con questo in mente puoi usare un altro comando teeper riscrivere l'output nel file. Un'altra opzione è quella di creare una patch dal piping del contenuto in diff.

Metodo a T

sed '/regex/' <file> | tee <file>

Metodo di patch

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

AGGIORNARE:

Inoltre, nota che la patch farà cambiare il file dalla riga 1 dell'output diff:

La patch non ha bisogno di sapere a quale file accedere come si trova nella prima riga dell'output da diff:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar

2
/dev/stdin 2014-03-15, ha risposto 7 gennaio - sei un viaggiatore nel tempo?
Adrian Frühwirth,

Su Windows, usando msysgit, /dev/stdinnon esiste, quindi devi sostituire /dev/stdincon '-', un singolo trattino senza virgolette, quindi i seguenti comandi dovrebbero funzionare: $ sed 's/oo/u/' fubar | diff -p fubar -e$ sed 's/oo/u/' fubar | diff -p fubar - | patch
Jim Raden,

@ AdrianFrühwirth Ho una scatola blu della polizia da qualche parte e per qualche ragione mi chiamano il Dottore al lavoro. Ma onestamente, sembra che ci sia davvero una brutta deriva dell'orologio sul sistema in quel momento.
Dwight Spencer,

Queste soluzioni mi sembrano basarsi su un comportamento di buffering particolare. Sospetto che per file più grandi questi possano rompersi, mentre sed sta leggendo, il file potrebbe iniziare a cambiare sotto il comando patch, o peggio ancora con il comando tee che troncerà il file. In realtà non sono sicuro che anche se patchnon si tronca. Penso che salvare l'output in un altro file e poi catin originale sarebbe più sicuro.
Akostadinov,

vera risposta sul posto da @ william-pursell
akostadinov

11

Non è possibile fare ciò che vuoi sed. Anche le versioni sedche supportano l' -iopzione per la modifica di un file in atto fanno esattamente ciò che hai esplicitamente dichiarato che non vuoi: scrivono in un file temporaneo e quindi rinominano il file. Ma forse puoi semplicemente usare ed. Ad esempio, per modificare tutte le occorrenze di fooin barnel file file.txt, è possibile eseguire:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

La sintassi è simile sed, ma certamente non esattamente la stessa.

Ma forse dovresti considerare perché non vuoi usare un file temporaneo rinominato. Anche se non hai un -isupporto sed, puoi facilmente scrivere una sceneggiatura per fare il lavoro per te. Invece di sed -i 's/foo/bar/g' file, potresti fare inline file sed 's/foo/bar/g'. Una tale sceneggiatura è banale da scrivere. Per esempio:

#!/bin/sh -e
IN=$1
shift
trap 'rm -f $tmp' 0
tmp=$( mktemp )
<$IN "$@" >$tmp && cat $tmp > $IN  # preserve hard links

dovrebbe essere adeguato per la maggior parte degli usi.


1
ad esempio, come chiameresti questo script e come lo chiameresti?
anfibio,

2
Lo chiamerei inlinee lo invocherei come descritto sopra:inline inputfile sed 's/foo/bar/g'
William Pursell,

1
wow, edunica risposta che fa davvero sul posto. La prima volta che ne abbia mai bisogno ed. Grazie. Una piccola ottimizzazione è usare echo -e ",s/foo/bar/g\012 w"o echo $',s/foo/bar/g\012 w'dove la shell gli consente di evitare trchiamate extra .
Akostadinov

2
ednon esegue davvero le modifiche sul posto. Da straceuscita, GNU edcrea un file temporaneo, scrive le modifiche al file temporaneo, quindi riscrive l'intero file originale, conservando hard link. Da trussuscita, Solaris 11 edutilizza anche un file temporaneo, ma rinomina il file temporaneo al nome del file originale al momento del salvataggio con il wcomando, distruggendo qualsiasi hard link.
Andrew Henle,

@AndrewHenle È scoraggiante. Quello edfornito con gli attuali macos (Mojave, qualunque cosa significhi quel gergo di marketing) conserva ancora i collegamenti. YMMV
William Pursell,

10

Puoi usare vi

vi -c '%s/foo/bar/g' my.txt -c 'wq'

9

sed supporta l'editing sul posto. Da man sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

Esempio :

Supponiamo che tu abbia un file hello.txtcon il testo:

hello world!

Se si desidera conservare un backup del vecchio file, utilizzare:

sed -i.bak 's/hello/bonjour' hello.txt

Finirai con due file: hello.txtcon il contenuto:

bonjour world!

e hello.txt.bak con il vecchio contenuto.

Se non si desidera conservare una copia, non passare il parametro di estensione.


3
L'OP menziona specificamente che la sua piattaforma non supporta questa opzione (popolare ma) non standard.
triplo il

3

Non hai specificato quale shell stai usando, ma con zsh puoi usare il =( )costrutto per raggiungere questo obiettivo. Qualcosa sulla falsariga di:

cp =(sed ... file; sync) file

=( )è simile >( )ma crea un file temporaneo che viene automaticamente eliminato al cptermine.


3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

Dovrebbe preservare tutti gli hardlink, poiché l'output è indirizzato indietro per sovrascrivere il contenuto del file originale ed evita qualsiasi necessità di una versione speciale di sed.


3

Se si sta sostituendo la stessa quantità di caratteri e dopo aver letto attentamente la modifica "sul posto" dei file ...

È inoltre possibile utilizzare l'operatore di reindirizzamento <>per aprire il file per leggere e scrivere:

sed 's/foo/bar/g' file 1<> file

Guardalo dal vivo:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

Dal manuale di riferimento di Bash → 3.6.10 Apertura dei descrittori di file per la lettura e la scrittura :

L'operatore di reindirizzamento

[n]<>word

causa l'apertura del file il cui nome è l'espansione della parola sia per la lettura che per la scrittura sul descrittore di file n, o sul descrittore di file 0 se n non è specificato. Se il file non esiste, viene creato.


Questo è danneggiato dal file. Non sono sicuro del motivo dietro. paste.fedoraproject.org/462017
Nehal J Wani,

Vedi le righe [43-44], [88-89], [135-136]
Nehal J Wani,

2
Questo post sul blog spiega perché questa risposta non dovrebbe essere utilizzata. backreference.org/2011/01/29/in-place-editing-of-files
Nehal J Wani

@NehalJWani Avrò una lunga lettura dell'articolo, grazie per l'heads-up! Quello che non ho menzionato nella risposta è che funziona se cambia la stessa quantità di caratteri.
fedorqui "SO smettere di danneggiare",

Detto questo, mi dispiace se la mia risposta ha causato danni ai tuoi file. Lo aggiornerò con le correzioni (o lo cancellerò se necessario) dopo aver letto i collegamenti che hai fornito.
fedorqui "SO smettere di danneggiare",

2

Come disse Moneypenny in Skyfall: "A volte i vecchi modi sono i migliori". Kincade disse qualcosa di simile più tardi.

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

Buon editing sul posto. Aggiunto '\ nw \ n' per scrivere il file. Ci scusiamo per la richiesta di risposta posticipata.


Puoi spiegare cosa fa questo?
Matt Montag,

1
Invoca ed (1), l'editor di testo con alcuni caratteri scritti su stdin. Come persona di età inferiore ai 30 anni, penso che sia giusto per me dire che ed (1) è per i dinosauri come lo sono i dinosauri per noi. Ad ogni modo, invece di aprire ed e scrivere quel materiale, jlettvin sta eseguendo il piping dei personaggi nell'uso |. @MattMontag
Ari Sweedler

Se stai chiedendo cosa fanno i comandi, ce ne sono due, terminati da un \n. [range] s / <old> / <new> / <flag> è la forma del comando sostitutivo - un paradigma ancora visto in molti altri editor di testo (ok, vim, un diretto discendente di ed) Comunque, la gbandiera sta per "globale" e significa "Ogni istanza su ogni riga che guardi". La gamma 3,5esamina le righe 3-5. ,5guarda StartOfFile-5, 3,guarda le righe 3-EndOfFile e ,guarda StartOfFile-EndOfFile. Un comando di sostituzione globale su ogni riga che sostituisce "false" con "true". Quindi, wviene inserito il comando di scrittura,.
Ari Sweedler,

1

Esempi molto buoni. Ho avuto la sfida di modificare sul posto molti file e l'opzione -i sembra essere l'unica soluzione ragionevole usandola all'interno del comando find. Ecco lo script per aggiungere "versione:" davanti alla prima riga di ogni file:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
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.