Agregar una línea a un archivo solo si aún no existe

157

Necesito agregar la siguiente línea al final de un archivo de configuración:

include "/configs/projectname.conf"

a un archivo llamado lighttpd.conf

Estoy buscando usar sedpara hacer esto, pero no puedo entender cómo.

¿Cómo lo insertaría solo si la línea aún no existe?

Benjamin Dell
fuente
Si está intentando editar un archivo ini, la herramienta crudinipodría ser una buena opción (pero aún no para lighthttpd)
rubo77

Respuestas:

292

Solo mantenlo simple :)

grep + echo debería ser suficiente:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar

Editar: incorporaron @cerin y @ thijs-wouters sugerencias .

drAlberT
fuente
2
Agregaría el interruptor -q a grep para suprimir la salida: grep -vq ...
Dave Kirby el
1
FYI, usando -v y && no parece hacer el truco, mientras que || sin -v funciona.
bPizzi
3
Esto solo funciona para líneas muy simples. De lo contrario, grep interpreta la mayoría de los caracteres no alfa de su línea como patrones, lo que hace que no detecte la línea en el archivo.
Cerin
2
Hermosa solución Esto también funciona para desencadenar expresiones más complicadas, por supuesto. El mío usa el echopara activar un catheredoc multilínea en un archivo de configuración.
Eric L.
2
Agregue -spara ignorar errores cuando el archivo no exista, creando un nuevo archivo solo con esa línea.
Frank
78

Esta sería una solución limpia, legible y reutilizable usando grepy echopara agregar una línea a un archivo solo si aún no existe:

LINE='include "/configs/projectname.conf"'
FILE='lighttpd.conf'
grep -qF -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE"

Si necesita unir toda la línea, use grep -xqF

Agregue -spara ignorar errores cuando el archivo no exista, creando un nuevo archivo solo con esa línea.

rubo77
fuente
55
Si se trata de un archivo donde necesita permisos de root:sudo grep -qF "$LINE" "$FILE" || echo "$LINE" | sudo tee -a "$FILE"
nebffa
En realidad, esto no funciona cuando la línea comienza con a -.
hyperknot
@zsero: buen punto! He añadido --en el comando grep, por lo que no interpretará un $ línea de salida con un guión como opciones más.
rubo77
13

Aquí hay una sedversión:

sed -e '\|include "/configs/projectname.conf"|h; ${x;s/incl//;{g;t};a\' -e 'include "/configs/projectname.conf"' -e '}' file

Si su cadena está en una variable:

string='include "/configs/projectname.conf"'
sed -e "\|$string|h; \${x;s|$string||;{g;t};a\\" -e "$string" -e "}" file
Pausado hasta nuevo aviso.
fuente
Para un comando que reemplaza una cadena parcial con la entrada completa / actualizada del archivo de configuración, o agrega la línea según sea necesario:sed -i -e '\|session.*pam_mkhomedir.so|h; ${x;s/mkhomedir//;{g;tF};a\' -e 'session\trequired\tpam_mkhomedir.so umask=0022' -e '};:F;s/.*mkhomedir.*/session\trequired\tpam_mkhomedir.so umask=0022/g;' /etc/pam.d/common-session
bgStack15
Bien, me ayudó mucho +1. ¿podría por favor, explicar su expresión sed?
Rahul
Me encantaría ver una explicación anotada de esta solución. Lo he usado seddurante años, pero principalmente solo el scomando. Los intercambios holdy el patternespacio están más allá de mí.
norte
11

Si escribe en un archivo protegido, las respuestas de @drAlberT y @ rubo77 podrían no funcionar para usted, ya que uno no puede sudo >>. Una solución similarmente simple , entonces, sería usar tee --append(o, en MacOS, tee -a):

LINE='include "/configs/projectname.conf"'
FILE=lighttpd.conf
grep -qF "$LINE" "$FILE"  || echo "$LINE" | sudo tee --append "$FILE"
hamx0r
fuente
6

Si, algún día, alguien más tiene que lidiar con este código como "código heredado", entonces esa persona se lo agradecerá si escribe un código menos exotérico, como

grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
  echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi
Marcelo Ventura
fuente
1
Lo sentimos, si no conoces Shell, entonces ve y administra Windows. Las soluciones usando && y || son una sintaxis de shell normal y cualquier administrador competente debe entenderla. No hay nada esotérico allí en absoluto.
Graham Nicholls
3
Gracias por tu comentario Graham! Sí, es una sintaxis de shell normal. Y sí, cualquier administrador competente debería entenderlo. Sin embargo, funciona como tal basado en un efecto secundario del algoritmo de evaluación de expresión dentro de shell. Así que puedes llamarlo todo lo que quieras, excepto directo, que era mi punto original.
Marcelo Ventura
6

otra solución sed es agregarla siempre en la última línea y eliminar una preexistente.

sed -e '$a\' -e '<your-entry>' -e "/<your-entry-properly-escaped>/d"

"escapado correctamente" significa poner una expresión regular que coincida con su entrada, es decir, escapar de todos los controles de expresión regular de su entrada real, es decir, poner una barra diagonal inversa delante de ^ $ / *? + ().

esto podría fallar en la última línea de su archivo o si no hay una nueva línea colgante, no estoy seguro, pero eso podría solucionarse mediante una ingeniosa ramificación ...

Robin479
fuente
1
Bonita línea, corta y fácil de leer. Funciona muy bien para actualizar etc / hosts:sed -i.bak -e '$a\' -e "$NEW_IP\t\t$HOST.domain\t$HOST" -e "/.*$HOST/d" /etc/hosts
Noam Manos
Una versión más pequeña:sed -i -e '/<entry>/d; $a <entry>'
Jérôme Pouiller
@Jezz Creo que esto fallará, si la entrada ya está al final del archivo, porque el dcomando reiniciará el programa sed y, por lo tanto, omitirá el append, si la eliminación se encuentra en la última línea.
Robin479
@ Robin479 Damned, append tiene que ser ejecutado antes de delete: sed -i -e '$a<entry>' -e '/<entry>/d'. Es casi lo mismo que su respuesta original.
Jérôme Pouiller
3

usar awk

awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file file
ghostdog74
fuente
¿Es 'archivo de archivo' o simplemente 'archivo'?
webdevguy
Es file fileporque hace dos pases sobre el mismo archivo. NR==FNRes cierto en el primer pase, pero no en el segundo. Este es un idioma awk común.
tripleee
1

Las respuestas con grep son incorrectas. Debe agregar una opción -x para que coincida con la línea completa; de lo contrario, las líneas similares #text to addseguirán coincidiendo cuando busque agregar exactamente text to add.

Entonces la solución correcta es algo como:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
Thijs Wouters
fuente
1

Usando sed: se insertará al final de la línea. También puede pasar variables como siempre, por supuesto.

grep -qxF "port=9033" $light.conf
if [ $? -ne 0 ]; then
  sed -i "$ a port=9033" $light.conf
else
    echo "port=9033 already added"
fi

Usando oneliner sed

grep -qxF "port=9033" $lightconf || sed -i "$ a port=9033" $lightconf

Usar echo puede no funcionar bajo root, pero funcionará así. Pero no le permitirá automatizar las cosas si está buscando hacerlo, ya que podría solicitar una contraseña.

Tuve un problema cuando intentaba editar desde la raíz para un usuario en particular. Solo agregar el $usernameantes fue una solución para mí.

grep -qxF "port=9033" light.conf
if [ $? -ne 0 ]; then
  sudo -u $user_name echo "port=9033" >> light.conf
else
    echo "already there"    
fi
Rakib Fiha
fuente
¡Bienvenido a stackoverflow! Lea la pregunta detenidamente antes de publicar una respuesta: el OP preguntó cómo insertar usando sed
Markoorn
-1

Necesitaba editar un archivo con permisos de escritura restringidos tan necesarios sudo. trabajando desde la respuesta de ghostdog74 y usando un archivo temporal:

awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file > /tmp/file
sudo mv /tmp/file file
webdevguy
fuente
Esto no parece correcto, perderá las otras líneas del archivo cuando falta la línea de configuración.
tripleee