sed: come sostituire la riga se trovata o aggiungere alla fine del file se non trovata?


34

Con un singolo file di input che contiene solo commenti (che iniziano con #) e VARIABLE = righe di valore, è possibile sostituire un valore per una singola variabile se trovato e, in caso contrario, aggiungere la coppia alla fine del file se non trovato?

Il mio metodo attuale funziona eliminandolo in un primo passaggio, quindi aggiungendolo alla fine del file in un secondo passaggio, ma questo metodo incasina l'ordinamento di linea (ed è anche due comandi diversi):

sed -r "/^FOOBAR=.*$/d"      -i samefile &&
sed -r "$ a\FOOBAR=newvalue" -i samefile

Esiste un modo per farlo, ad es. mantenendo l'ordine delle righe, in un'unica riga sed? Se qualche altra utility (awk, ...) fa questo, lo prenderei su sed.

Risposte:


29

In realtà è abbastanza semplice con sed: se una riga corrisponde basta copiarla nel hvecchio spazio quindi ssostituire il valore.
Sulla $riga di riga xcambia spazio e pattern spazio, quindi controlla se quest'ultimo è vuoto. Se non è vuoto, significa che la sostituzione è già stata effettuata, quindi nulla da fare. Se è vuoto, ciò significa che non è stata trovata alcuna corrispondenza, quindi sostituire lo spazio modello con il valore variabile = desiderato quindi aggiungere alla riga corrente nel buffer di mantenimento. Infine, xcambio di nuovo:

sed '/^FOOBAR=/{h;s/=.*/=newvalue/};${x;/^$/{s//FOOBAR=newvalue/;H};x}' infile

Quanto sopra è la gnu sedsintassi. Portatile:

sed '/^FOOBAR=/{
h
s/=.*/=newvalue/
}
${
x
/^$/{
s//FOOBAR=newvalue/
H
}
x
}' infile

1
Come sarebbe se newvaluefosse memorizzato in una variabile?
user1810087

sed "/^${varName}=/{h;s/=.*/=${varValue}/};\${x;/^$/{s//${varName}=${varValue}/;H};x}" ${VARFILE}con variabili, virgolette doppie per consentire la sostituzione delle variabili e, $in questo momento, l' evasione da una che non è una sostituzione, inoltre se il valore viene archiviato in $$varName:sed "/^${varName}=/{h;s/=.*/=${!varName}/};\${x;/^$/{s//${varName}=${!varName}/;H};x}" ${VARFILE}
DarkMukke,

1
Questa soluzione non è lol "semplice". Ma funziona Le pagine man di sed non sono le cose più facili da esaminare, sarebbe davvero utile se si potesse dettagliare l'effetto di ogni sezione dell'espressione :)
user2066480

18

Questo può probabilmente essere abbreviato. Non è un singolo comando sed e usa anche grep, ma questo sembra fondamentalmente quello che stai cercando. È una riga singola e modifica il file sul posto (nessun file temporaneo).

grep -q "^FOOBAR=" file && sed "s/^FOOBAR=.*/FOOBAR=newvalue/" -i file || 
    sed "$ a\FOOBAR=newvalue" -i file

13

Ecco un sedapproccio più semplice , in quanto non trovo sed hold spacefacile lavorare. Se hai dimestichezza hold space, l'uso dell'approccio don_crissti offre ulteriori opportunità per preservare qualsiasi cosa dalla linea esistente, ma di solito è molto raro.

In questo approccio, è sufficiente stampare tutto tranne la riga che si desidera eliminare e quindi alla fine, aggiungere la sostituzione.

sed -n -e '/^FOOBAR=/!p' -e '$aFOOBAR=newvalue' infile

1
Questa versione è ottima se si prevede di aggiornare un file che potrebbe avere un valore esistente ma non aggiornato.
Guillem,

3

In base alle altre risposte, se quello che vuoi fare è sostituire il valore di una variabile se quella variabile è presente nel file e aggiungerla alla fine del file se non lo è (che non è quello che fanno i tuoi sedcomandi postati ), tu potrebbe provare questo:

perl -ne '$c=1 if s/^FOOBAR=.*$/FOOBAR=newvalue/;  
             print; 
             END{print "FOBAR=newvalue" unless $c==1}' file > tmpfile && 
mv tmpfile file

1

È un po 'più facile in awk, anche se la "modifica sul posto" non è automatica:

awk -v varname="FOOBAR" -v newval="newvalue" '
    BEGIN {FS = OFS = "="}
    $1 == varname {$2 = newval; found = 1}
    {print}
    END {if (! found) {print varname, newval}}
' file > tempfile &&
mv tempfile file

1

Basta usare grepe echoper creare un record vuoto:

grep -q '^FOOBAR=' somefile || echo 'FOOBAR=VALUE' >> somefile
sed -i 's/FOOBAR=.*$/FOOBAR=VALUE/' somefile 

Ogni riga esce con il codice di errore zero.


Si perde l'ordine delle righe durante l'aggiunta di comandi aggiuntivi grepeecho
BlakBat

In effetti se si inseriscono i nuovi articoli alla fine del file, ma si continua a ordinare se si sostituisce un vecchio valore.
MUY Belgio

0

In realtà funziona con quanto segue: sed -i '/^FOOBAR=/{h;s/=.*/=newvalue/};${x;/^$/{s//FOOBAR=newvalue/;H};x}' infile Nella risposta scelta -i manca.


3
Questo è davvero un commento e non una risposta alla domanda originale. Per criticare o richiedere chiarimenti a un autore, lascia un commento sotto il suo post: puoi sempre commentare i tuoi post e una volta che avrai una reputazione sufficiente sarai in grado di commentare qualsiasi post . Si prega di leggere Perché ho bisogno di 50 reputazione per commentare? Cosa posso fare invece?
DavidPostill

-1

per fare questo con Perl e sul posto questo funziona bene per me:

grep ^FOOBAR= my.file && perl -i -ple "s/^FOOBAR=.+/FOOBAR=newvalue/g" my.file || echo FOOBAR=newvalue >> my.file

2
Questa risposta è negativa in molti modi! grepe echoe perlinvece di fare tutto quanto in perl? Inoltre, scrivendo su un file ( >> my.file) mentre stai leggendo da esso ( grep my.file)?
Vladr,
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.