Cómo sustituir variables de shell en archivos de texto complejos

84

Tengo varios archivos de texto en los que he introducido variables de shell ($ VAR1 o $ VAR2, por ejemplo).

Me gustaría tomar esos archivos (uno por uno) y guardarlos en archivos nuevos donde todas las variables habrían sido reemplazadas.

Para hacer esto, utilicé el siguiente script de shell (que se encuentra en StackOverflow):

while read line
do
    eval echo "$line" >> destination.txt
done < "source.txt"

Esto funciona muy bien en archivos muy básicos.

Pero en archivos más complejos, el comando "eval" hace demasiado:

  • Las líneas que comienzan con "#" se omiten

  • El análisis de archivos XML da como resultado toneladas de errores

Hay una mejor manera de hacerlo? (en el script de shell ... sé que esto se hace fácilmente con Ant, por ejemplo)

Saludos cordiales

Ben
fuente

Respuestas:

196

Mirando, resulta que en mi sistema hay un envsubstcomando que es parte del paquete gettext-base.

Entonces, esto lo hace fácil:

envsubst < "source.txt" > "destination.txt"

Nota Si desea utilizar el mismo archivo para ambos, usted tendrá que usar algo como moreutil de sponge, según lo sugerido por Johnny Utahh: envsubst < "source.txt" | sponge "source.txt". (Porque la redirección de shell vaciará el archivo antes de leerlo).

derobert
fuente
4
¡Excelente! Pero solo funciona para variables de entorno. ¿Cómo puedo hacer que funcione con las variables que están declaradas en mi script .sh?
Ben
10
@Ben: use 'exportar' (antes de llamar a envsubst) para cada variable que desee usar en envsubst
tlo
7
envsubstes parte de GNU gettext
Andy
3
@user_mda donde va la salida.
derobert
4
Advertencia: si source.txtcontiene $caracteres fuera de la expansión variable, envsubsttambién los reemplazará y no hay forma de escapar del $. La solución común (fea) es export DOLLAR="$".
Kos
60

En referencia a la respuesta 2, al hablar de envsubst, preguntaste:

¿Cómo puedo hacer que funcione con las variables que están declaradas en mi script .sh?

La respuesta es que simplemente necesita exportar sus variables antes de llamar envsubst.

También puede limitar las cadenas de variables que desea reemplazar en la entrada usando el envsubst SHELL_FORMATargumento (evitando el reemplazo involuntario de una cadena en la entrada con un valor de variable de shell común, por ejemplo $HOME).

Por ejemplo:

export VAR1='somevalue' VAR2='someothervalue'
MYVARS='$VAR1:$VAR2'

envsubst "$MYVARS" <source.txt >destination.txt

Reemplazará todas las instancias de $VAR1y $VAR2(y solo VAR1y VAR2) en source.txtcon 'somevalue'y 'someothervalue'respectivamente.

Miguel
fuente
14
Las comillas simples 'que se utilizan para establecer MYVARSson cruciales
Mark Lakata
Tenga en cuenta que export, envsubsto cualquier comando de entrelazado puede fallar cuando el texto a sustituir es grande. Referencia.
obispo
@ram eso se debe a que la omisión del SHELL_FORMATargumento (es decir "$MYVARS") hace que todas las variables exportadas sean sustituidas. Si eso es lo que necesita, no se preocupe.
Thiago Figueiro
12

Sé que este tema es antiguo, pero tengo una solución de trabajo más simple sin exportar las variables. Puede ser un delineador, pero prefiero dividir usando \el final de la línea.

var1='myVar1'\
var2=2\
var3=${var1}\
envsubst '$var1,$var3' < "source.txt" > "destination.txt"

#         ^^^^^^^^^^^    ^^^^^^^^^^     ^^^^^^^^^^^^^^^
# define which to replace   input            output

Las variables deben definirse en la misma línea que envsubstpara ser consideradas como variables de entorno.

El '$var1,$var3'es opcional para reemplazar solo los especificados. Imagínese un archivo de entrada ${VARIABLE_USED_BY_JENKINS}que no debería ser reemplazado.

inetphantom
fuente
8
  1. Defina su variable ENV
$ export MY_ENV_VAR=congratulation
  1. Cree un archivo de plantilla ( in.txt ) con el siguiente contenido
$MY_ENV_VAR

También puede usar todas las demás variables ENV definidas por su sistema como (en linux) $ TERM, $ SHELL, $ HOME ...

  1. Ejecute este comando para colocar todas las variables de env en su archivo in.txt y escribir el resultado en out.txt
$ envsubst "`printf '${%s} ' $(sh -c "env|cut -d'=' -f1")`" < in.txt > out.txt
  1. Verifique el contenido del archivo out.txt
$ cat out.txt

y debería ver "felicitaciones".

Friso
fuente
0

Si realmente solo desea usar bash (y sed), entonces revisaría cada una de sus variables de entorno (como las devuelve seten modo posix) y construiría un montón de -e 'regex'for sed a partir de eso, terminado por a -e 's/\$[a-zA-Z_][a-zA-Z0-9_]*//g', luego pasaría todo eso a sed.

Sin embargo, Perl haría un mejor trabajo, tiene acceso a las variables de entorno como una matriz y puede hacer reemplazos ejecutables para que solo coincida con cualquier variable de entorno una vez.

w00t
fuente
¿Por qué alguien rechazaría una respuesta como esta ??? -perl -pi -e 'foreach $key(sort keys %ENV){ s/\$$key/$ENV{$key}/g}' "$main_tf_file"
Yordan Georgiev
0

En realidad, debe cambiar su reada, read -rlo que hará que ignore las barras invertidas.

Además, debe escapar de las comillas y las barras invertidas. Entonces

while read -r line; do
  line="${line//\\/\\\\}"
  line="${line//\"/\\\"}"
  line="${line//\`/\\\`}"
  eval echo "\"$line\""
done > destination.txt < source.txt

Sin embargo, sigue siendo una forma terrible de hacer expansión.

w00t
fuente
De hecho, ese es el riesgo al hacer "eval" en un archivo que podría contener un código incorrecto
Ben
0

Exporte todas las variables necesarias y luego use un Perl Onliner

TEXT=$(echo "$TEXT"|perl -wpne 's#\${?(\w+)}?# $ENV{$1} // $& #ge;')

Esto reemplazará todas las variables ENV presentes en TEXT con valores reales. Las citas también se conservan :)

Govind Kailas
fuente
0

Si desea que las variables env se reemplacen en sus archivos de origen mientras mantiene todas las variables que no son env como están, puede usar el siguiente comando:

envsubst "$(printf '${%s} ' $(env | sed 's/=.*//'))" < source.txt > destination.txt

Aquí se explica la sintaxis para reemplazar solo variables específicas . El comando anterior usa un sub-shell para listar todas las variables definidas y luego pasarlo alenvsubst

Entonces, si hay una variable env definida llamada $NAME, y su source.txtarchivo se ve así:

Hello $NAME
Your balance is 123 ($USD)

El destination.txtserá:

Hello Arik
Your balance is 123 ($USD)

Observe que $NAMEse reemplaza y $USDse deja intacto

Arik
fuente
0

Llame al binario de perl, en el modo de búsqueda y reemplazo por línea (the -pi) ejecutando el código perl (the -e) entre comillas simples, que itera sobre las claves del %ENVhash especial que contiene los nombres de las variables exportadas como claves y los valores de las variables exportadas como los valores de las claves y para cada iteración simple reemplace una cadena que contenga un $<<key>>con su <<value>>.

 perl -pi -e 'foreach $key(sort keys %ENV){ s/\$$key/$ENV{$key}/g}' file

Advertencia: se requiere un manejo lógico adicional para los casos en los que dos o más vars comienzan con la misma cadena ...

Yordan Georgiev
fuente
-1

envsubstparece exactamente algo que quería usar, pero la -vopción me sorprendió un poco.

Si envsubst < template.txtbien funcionaba bien, lo mismo con la opción -vno funcionaba:

$ cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.1 (Maipo)
$ envsubst -V
envsubst (GNU gettext-runtime) 0.18.2
Copyright (C) 2003-2007 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Bruno Haible.

Como escribí, esto no estaba funcionando:

$ envsubst -v < template.txt
envsubst: missing arguments
$ cat template.txt | envsubst -v
envsubst: missing arguments

Tuve que hacer esto para que funcione:

TEXT=`cat template.txt`; envsubst -v "$TEXT"

Quizás ayude a alguien.

Betlista
fuente