Obligar a bash a expandir variables en una cadena cargada desde un archivo

88

Estoy tratando de averiguar cómo hacer que bash (¿forzar?) Expanda las variables en una cadena (que se cargó desde un archivo).

Tengo un archivo llamado "something.txt" con el contenido:

hello $FOO world

Luego corro

export FOO=42
echo $(cat something.txt)

esto devuelve:

   hello $FOO world

No expandió $ FOO a pesar de que se estableció la variable. No puedo evaluar o obtener el archivo, ya que intentará ejecutarlo (no es ejecutable como es, solo quiero la cadena con las variables interpoladas).

¿Algunas ideas?

Michael Neale
fuente
3
Tenga en cuenta las implicaciones de seguridad deeval .
Pausado hasta nuevo aviso.
¿Querías que el comentario fuera para la respuesta a continuación?
Michael Neale
Quise decir el comentario para ti. Más de una respuesta propone el uso de evaly es importante conocer las implicaciones.
Pausado hasta nuevo aviso.
@DennisWilliamson te pillé: llego un poco tarde en responder, pero es un buen consejo. Y, por supuesto, en los años intermedios hemos tenido cosas como "conmoción", ¡así que su comentario ha resistido la prueba del tiempo! sombrero de consejos
Michael Neale
¿Responde esto a tu pregunta? Bash expandir variable en una variable
LoganMzz

Respuestas:

111

Me topé con lo que creo que es LA respuesta a esta pregunta: el envsubst comando.

envsubst < something.txt

En caso de que aún no esté disponible en su distribución, está en el

Paquete GNU gettext .

@Rockallite: escribí un pequeño script de envoltura para solucionar el problema '\ $'.

(Por cierto, hay una "característica" de envsubst, explicada en https://unix.stackexchange.com/a/294400/7088 para expandir solo algunas de las variables en la entrada, pero estoy de acuerdo en que escapar de las excepciones es mucho más conveniente.)

Aquí está mi guión:

#! /bin/bash
      ## -*-Shell-Script-*-
CmdName=${0##*/}
Usage="usage: $CmdName runs envsubst, but allows '\$' to  keep variables from
    being expanded.
  With option   -sl   '\$' keeps the back-slash.
  Default is to replace  '\$' with '$'
"

if [[ $1 = -h ]]  ;then echo -e >&2  "$Usage" ; exit 1 ;fi
if [[ $1 = -sl ]] ;then  sl='\'  ; shift ;fi

sed 's/\\\$/\${EnVsUbDolR}/g' |  EnVsUbDolR=$sl\$  envsubst  "$@"
LenW
fuente
5
+1. Esta es la única respuesta que está garantizada para no realizar sustituciones de comandos, eliminación de citas, procesamiento de argumentos, etc.
Josh Kelley
6
Sin embargo, solo funciona para vars de shell completamente exportadas (en bash). por ejemplo, S = '$ a $ b'; a = conjunto; exportar b = exportado; echo $ S | envsubst; produce: "exportado"
gaoithe
1
gracias - Creo que después de todos estos años, debería aceptar esta respuesta.
Michael Neale
1
Sin embargo, no maneja el $escape del signo de dólar ( ), por ejemplo, echo '\$USER' | envsubstgenerará el nombre de usuario actual con una barra inclinada hacia atrás, no $USER.
Rockallite
3
parece tener esto en un mac con necesidades brew link --force gettext
caseras
25

Muchas de las respuestas usando evalyecho funcionan, pero se rompen en varias cosas, como múltiples líneas, intentar escapar de los metacaracteres de shell, escapes dentro de la plantilla que no están destinados a ser expandidos por bash, etc.

Tuve el mismo problema y escribí esta función de shell, que por lo que puedo decir, maneja todo correctamente. Esto aún eliminará solo las nuevas líneas finales de la plantilla, debido a las reglas de sustitución de comandos de bash, pero nunca he encontrado que eso sea un problema siempre que todo lo demás permanezca intacto.

apply_shell_expansion() {
    declare file="$1"
    declare data=$(< "$file")
    declare delimiter="__apply_shell_expansion_delimiter__"
    declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter"
    eval "$command"
}

Por ejemplo, puede usarlo así con un parameters.cfgque es realmente un script de shell que solo establece variables, y template.txtque es una plantilla que usa esas variables:

. parameters.cfg
printf "%s\n" "$(apply_shell_expansion template.txt)" > result.txt

En la práctica, lo uso como una especie de sistema de plantilla ligero.

wjl
fuente
4
Este es uno de los mejores trucos que encontré hasta ahora :). Según su respuesta, es muy fácil construir una función que expanda una variable con una cadena que contenga referencias a otras variables; simplemente reemplace $ data con $ 1 en su función y ¡aquí vamos! Creo que esta es la mejor respuesta a la pregunta original, por cierto.
galaxia
Incluso esto tiene evaldificultades habituales . Intente agregar ; $(eval date)al final de cualquier línea dentro del archivo de entrada y ejecutará el datecomando.
anubhava
1
@anubhava No estoy seguro de lo que quieres decir; ese es realmente el comportamiento de expansión deseado en este caso. En otras palabras, sí, se supone que puede ejecutar código de shell arbitrario con esto.
wjl
22

puedes probar

echo $(eval echo $(cat something.txt))
Pizza
fuente
9
Úselo echo -e "$(eval "echo -e \"`<something.txt`\"")"si necesita mantener nuevas líneas.
pevik
Funciona como un encanto
kyb
1
Pero solo si tu template.txtes texto. Esta operación es peligrosa porque ejecuta (evalúa) comandos si lo son.
kyb
2
pero esta comilla doble eliminada
Thomas Decaux
Échale la culpa a la subcapa.
ingyhere
8

No desea imprimir cada línea, desea evaluarla para que Bash pueda realizar sustituciones de variables.

FOO=42
while read; do
    eval echo "$REPLY"
done < something.txt

Consulte help evalo el manual de Bash para obtener más información.

Todd A. Jacobs
fuente
2
evales peligroso ; no solo se $varnameexpande (como lo haría con envsubst), sino $(rm -rf ~)también.
Charles Duffy
4

Otro enfoque (que parece asqueroso, pero lo estoy poniendo aquí de todos modos):

Escriba el contenido de something.txt en un archivo temporal, con una declaración de eco envuelto alrededor de él:

something=$(cat something.txt)

echo "echo \"" > temp.out
echo "$something" >> temp.out
echo "\"" >> temp.out

luego vuelva a generarlo en una variable:

RESULT=$(source temp.out)

y $ RESULT lo tendrá todo expandido. ¡Pero parece tan malo!

Michael Neale
fuente
1
icky pero es más sencillo, sencillo y funciona.
kyb
3

Si solo desea que se amplíen las referencias de variables (un objetivo que tenía para mí), puede hacer lo siguiente.

contents="$(cat something.txt)"
echo $(eval echo \"$contents\")

(Las comillas de escape alrededor de $ contenidos son clave aquí)

SS781
fuente
He probado este, sin embargo, $ () elimina los finales de línea en mi caso, lo que rompe la cadena resultante
moz
Cambié la línea de evaluación a eval "echo \"$$contents\""y conservó el espacio en blanco
moz
1

Siguiente solución:

  • permite la sustitución de variables definidas

  • deja los marcadores de posición de variables sin cambios que no están definidos. Esto es especialmente útil durante las implementaciones automatizadas.

  • admite el reemplazo de variables en los siguientes formatos:

    ${var_NAME}

    $var_NAME

  • informa qué variables no están definidas en el entorno y devuelve el código de error para tales casos



    TARGET_FILE=someFile.txt;
    ERR_CNT=0;

    for VARNAME in $(grep -P -o -e '\$[\{]?(\w+)*[\}]?' ${TARGET_FILE} | sort -u); do     
      VAR_VALUE=${!VARNAME};
      VARNAME2=$(echo $VARNAME| sed -e 's|^\${||g' -e 's|}$||g' -e 's|^\$||g' );
      VAR_VALUE2=${!VARNAME2};

      if [ "xxx" = "xxx$VAR_VALUE2" ]; then
         echo "$VARNAME is undefined ";
         ERR_CNT=$((ERR_CNT+1));
      else
         echo "replacing $VARNAME with $VAR_VALUE2" ;
         sed -i "s|$VARNAME|$VAR_VALUE2|g" ${TARGET_FILE}; 
      fi      
    done

    if [ ${ERR_CNT} -gt 0 ]; then
        echo "Found $ERR_CNT undefined environment variables";
        exit 1 
    fi
aprodan
fuente
1
  1. Si algo.txt tiene solo una línea, un bashmétodo (una versión más corta de la respuesta "asquerosa" de Michael Neale ), usando la sustitución de proceso y comando :

    FOO=42 . <(echo -e echo $(<something.txt))
    

    Salida:

    hello 42 world
    

    Tenga en cuenta que exportno es necesario.

  2. Si algo.txt tiene una o más líneas, un método de valoración de GNU :sed e

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    
agc
fuente
1
Encontré la sedevaluación que mejor se ajusta a mi propósito. Necesitaba producir scripts a partir de plantillas, que tendrían valores llenados de variables exportadas en otro archivo de configuración. Maneja correctamente el escape para la expansión de comandos, por ejemplo, poner \$(date)la plantilla para obtener $(date)la salida. ¡Gracias!
gadamiak
0
$ eval echo $(cat something.txt)
hello 42 world
$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.
ClearCrescendo
fuente
0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)
Božský Boch
fuente
1
Gracias por este fragmento de código, que puede proporcionar ayuda inmediata y limitada. Una explicación adecuada mejoraría enormemente su valor a largo plazo al mostrar por qué es una buena solución al problema y lo haría más útil para futuros lectores con otras preguntas similares. Por favor, editar su respuesta a añadir un poco de explicación, incluyendo los supuestos realizados.
Capricornio
-1

envsubst es una gran solución (consulte la respuesta de LenW) si el contenido que está sustituyendo tiene una longitud "razonable".

En mi caso, necesitaba sustituir el contenido de un archivo para reemplazar el nombre de la variable. envsubstrequiere que el contenido se exporte como variables de entorno y bash tiene un problema al exportar variables de entorno que son más de un megabyte aproximadamente.

solución awk

Usando la solución de cuonglm de una pregunta diferente:

needle="doc1_base64" # The "variable name" in the file. (A $ is not needed.)
needle_file="doc1_base64.txt" # Will be substituted for the needle 
haystack=$requestfile1 # File containing the needle
out=$requestfile2
awk "BEGIN{getline l < \"${needle_file}\"}/${needle}/{gsub(\"${needle}\",l)}1" $haystack > $out

Esta solución funciona incluso para archivos grandes.

Larry K
fuente
-3

Los siguientes trabajos: bash -c "echo \"$(cat something.txt)"\"

Remolino
fuente
Esto no solo es ineficiente, sino también extremadamente peligroso; no solo ejecuta expansiones seguras como $foo, sino también inseguras como $(rm -rf ~).
Charles Duffy