sed: ¿cómo reemplazar la línea si se encuentra o agregar al final del archivo si no se encuentra?

34

Con un solo archivo de entrada que solo contiene comentarios (comenzando con #) y VARIABLE = líneas de valor, ¿es posible reemplazar un valor para una sola variable si se encuentra y, de lo contrario, agregar el par al final del archivo si no se encuentra?

Mi método actual funciona eliminándolo en una primera pasada, luego anexándolo al final del archivo en una segunda pasada, pero este método desordena el orden de la línea (y también son dos comandos diferentes):

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

¿Hay alguna forma de hacer esto, es decir. manteniendo el orden de las líneas, en una sola línea de sed? Si alguna otra utilidad (awk, ...) hace esto, lo tomaré sobre sed.

BlakBat
fuente

Respuestas:

29

En realidad es bastante simple con sed: si una línea coincide, simplemente cópiela en el hespacio anterior y luego sreemplace el valor.
En la última $línea, xcambie el espacio de espera y el espacio del patrón, luego verifique si este último está vacío. Si no está vacío, significa que la sustitución ya se realizó, por lo que no hay nada que hacer. Si está vacío, eso significa que no se encontró coincidencia, así que reemplace el espacio del patrón con la variable = valor deseada y luego agregue a la línea actual en el búfer de retención. Finalmente, e xcambio de nuevo:

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

Lo anterior es gnu sedsintaxis. Portátil:

sed '/^FOOBAR=/{
h
s/=.*/=newvalue/
}
${
x
/^$/{
s//FOOBAR=newvalue/
H
}
x
}' infile
don_crissti
fuente
1
¿Cómo se vería esto si newvaluese almacena en una variable?
user1810087
sed "/^${varName}=/{h;s/=.*/=${varValue}/};\${x;/^$/{s//${varName}=${varValue}/;H};x}" ${VARFILE}con variables, comillas dobles para permitir la sustitución de variables y escapar de una $que no es una sustitución en este momento, adicionalmente si su valor está almacenado en $$varName:sed "/^${varName}=/{h;s/=.*/=${!varName}/};\${x;/^$/{s//${varName}=${!varName}/;H};x}" ${VARFILE}
DarkMukke
1
Esta solución no es 'simple' jajaja. Pero funciona. las páginas de manual de sed no son las cosas más fáciles de examinar, sería realmente útil si pudieras detallar el efecto de cada sección de la expresión :)
user2066480
18

Esto probablemente se puede acortar. No es un solo comando sed y también usa grep, pero esto parece ser básicamente lo que quieres. Es una sola línea y edita el archivo en el lugar (sin archivos temporales).

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

Aquí hay un sedenfoque más simple , ya que no me resulta sed hold spacefácil trabajar con él. Si se siente cómodo hold space, el uso del enfoque don_crissti brinda una oportunidad adicional para preservar cualquier cosa de la línea existente, pero esto generalmente es muy raro.

En este enfoque, solo imprime todo menos la línea que desea soltar y luego, al final, agrega el reemplazo.

sed -n -e '/^FOOBAR=/!p' -e '$aFOOBAR=newvalue' infile
haridsv
fuente
1
Esta versión es muy buena si planea actualizar un archivo que puede tener un valor existente pero desactualizado.
guillem
3

Según las otras respuestas, si lo que desea hacer es reemplazar el valor de una variable si esa variable está presente en el archivo y agregarla al final del archivo si no lo está (que no es lo que hacen sus sedcomandos publicados ), usted podría intentar esto:

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

Es un poco más fácil en awk, aunque la "edición in situ" no es automática:

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
Glenn Jackman
fuente
1

Simplemente use grepy echopara crear un registro vacío:

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

Cada línea se escapa con el código de error cero.

MUY Bélgica
fuente
Pierde el orden de las líneas al agregar comandos adicionales grepyecho
BlakBat
De hecho, si inserta los nuevos elementos al final del archivo, pero sigue ordenando si reemplaza un valor anterior.
MUY Bélgica
0

Realmente funciona con lo siguiente: sed -i '/^FOOBAR=/{h;s/=.*/=newvalue/};${x;/^$/{s//FOOBAR=newvalue/;H};x}' infile En la respuesta elegida -i falta.

Yogesh Kamat
fuente
3
Esto es realmente un comentario y no una respuesta a la pregunta original. Para criticar o solicitar una aclaración de un autor, deje un comentario debajo de su publicación; siempre puede comentar sus propias publicaciones y, una vez que tenga suficiente reputación , podrá comentar cualquier publicación . Por favor lea ¿Por qué necesito 50 reputación para comentar? ¿Qué puedo hacer en su lugar?
DavidPostill
-1

para hacer esto con perl y en el lugar, esto funciona bien para mí:

grep ^FOOBAR= my.file && perl -i -ple "s/^FOOBAR=.+/FOOBAR=newvalue/g" my.file || echo FOOBAR=newvalue >> my.file
Antweiss
fuente
2
¡Esta respuesta es mala en muchos sentidos! grepy echoy en perllugar de hacer todo perl? Además, ¿escribir en un archivo ( >> my.file) mientras lees ( grep my.file)?
vladr