Bash Templating: ¿Cómo construir archivos de configuración a partir de plantillas con Bash?

134

Estoy escribiendo un script para automatizar la creación de archivos de configuración para Apache y PHP para mi propio servidor web. No quiero usar ninguna GUI como CPanel o ISPConfig.

Tengo algunas plantillas de archivos de configuración de Apache y PHP. La secuencia de comandos Bash necesita leer plantillas, realizar sustituciones variables y generar plantillas analizadas en alguna carpeta. ¿Cuál es la mejor manera de hacer eso? Se me ocurren varias maneras. ¿Cuál es el mejor o puede haber algunas formas mejores de hacerlo? Quiero hacer eso en puro Bash (es fácil en PHP, por ejemplo)

1) ¿Cómo reemplazar los marcadores de posición $ {} en un archivo de texto?

template.txt:

the number is ${i}
the word is ${word}

script.sh:

#!/bin/sh

#set variables
i=1
word="dog"
#read in template one line at the time, and replace variables
#(more natural (and efficient) way, thanks to Jonathan Leffler)
while read line
do
    eval echo "$line"
done < "./template.txt"

Por cierto, ¿cómo redirecciono la salida a un archivo externo aquí? ¿Necesito escapar de algo si las variables contienen, por ejemplo, comillas?

2) Usando cat & sed para reemplazar cada variable con su valor:

Dado template.txt:

The number is ${i}
The word is ${word}

Mando:

cat template.txt | sed -e "s/\${i}/1/" | sed -e "s/\${word}/dog/"

Me parece mal por la necesidad de escapar de muchos símbolos diferentes y con muchas variables la línea será demasiado larga.

¿Se te ocurre alguna otra solución elegante y segura?

Vladislav Rastrusny
fuente

Respuestas:

61

Puedes usar esto:

perl -p -i -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : $&/eg' < template.txt

para reemplazar todas las ${...}cadenas con las variables de entorno correspondientes (no olvide exportarlas antes de ejecutar este script).

Para bash puro, esto debería funcionar (suponiendo que las variables no contengan cadenas $ {...}):

#!/bin/bash
while read -r line ; do
    while [[ "$line" =~ (\$\{[a-zA-Z_][a-zA-Z_0-9]*\}) ]] ; do
        LHS=${BASH_REMATCH[1]}
        RHS="$(eval echo "\"$LHS\"")"
        line=${line//$LHS/$RHS}
    done
    echo "$line"
done

. Solución que no se bloquea si RHS hace referencia a alguna variable que se hace referencia a sí misma:

#!/bin/bash
line="$(cat; echo -n a)"
end_offset=${#line}
while [[ "${line:0:$end_offset}" =~ (.*)(\$\{([a-zA-Z_][a-zA-Z_0-9]*)\})(.*) ]] ; do
    PRE="${BASH_REMATCH[1]}"
    POST="${BASH_REMATCH[4]}${line:$end_offset:${#line}}"
    VARNAME="${BASH_REMATCH[3]}"
    eval 'VARVAL="$'$VARNAME'"'
    line="$PRE$VARVAL$POST"
    end_offset=${#PRE}
done
echo -n "${line:0:-1}"

ADVERTENCIA : no conozco una forma de manejar correctamente la entrada con NUL en bash o preservar la cantidad de líneas nuevas finales. La última variante se presenta tal cual es porque los shells "aman" la entrada binaria:

  1. read interpretará barras invertidas.
  2. read -r no interpretará barras inclinadas invertidas, pero aún caerá la última línea si no termina con una nueva línea.
  3. "$(…)"eliminará tantas líneas nuevas finales como haya presentes, así que termino con ; echo -n ay uso echo -n "${line:0:-1}": esto elimina el último carácter (que es a) y conserva tantas líneas nuevas finales como había en la entrada (incluyendo no).
ZyX
fuente
3
Cambiaría [^}]a [A-Za-Z_][A-Za-z0-9_]la versión bash para evitar que el shell vaya más allá de la sustitución estricta (por ejemplo, si trató de procesar ${some_unused_var-$(rm -rf $HOME)}).
Chris Johnsen
2
@FractalizeR es posible que desee cambiar $&en la solución perl a "": primero deja ${...}intacta si no puede sustituir, la segunda la reemplaza con una cadena vacía.
ZyX
55
NOTA: Aparentemente, hubo un cambio de bash 3.1 a 3.2 (y superior) en el que las comillas simples alrededor de la expresión regular tratan el contenido de la expresión regular como un literal de cadena. Entonces, la expresión regular anterior debería ser ... (\ $ \ {[a-zA-Z _] [a-zA-Z_0-9] * \}) stackoverflow.com/questions/304864/…
Blue Waters
2
Para hacer que el whileciclo lea la última línea, incluso si no está terminada por una nueva línea, use while read -r line || [[ -n $line ]]; do. Además, su readcomando elimina los espacios en blanco iniciales y finales de cada línea; para evitar eso, usewhile IFS= read -r line || [[ -n $line ]]; do
mklement0
2
Solo para tener en cuenta una restricción para aquellos que buscan una solución integral: estas soluciones útiles de otra manera no le permiten proteger selectivamente las referencias variables de la expansión (como al \ escaparlas).
mklement0
138

Tratar envsubst

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF
yottatsa
fuente
11
Solo como referencia, envsubstno se requiere cuando se usa un heredoc ya que bash trata el heredoc como una cadena literal entre comillas dobles y ya interpola variables en él. Sin embargo, es una gran opción cuando desea leer la plantilla desde otro archivo. Un buen reemplazo para los mucho más engorrosos m4.
beporter
2
Me sorprendió gratamente conocer este comando. Estaba tratando de improvisar la funcionalidad de envsubst manualmente con cero éxito. Gracias yottatsa!
Tim Stewart
44
Nota: envsubstes una utilidad gettext de GNU, y en realidad no es tan robusta (ya que gettext está destinado a localizar mensajes humanos). Lo más importante es que no reconoce las sustituciones $ {VAR} con barra invertida (por lo que no puede tener una plantilla que use sustituciones $ VAR en tiempo de ejecución, como un script de shell o un archivo Nginx conf). Vea mi respuesta para una solución que maneja los escapes de barra invertida.
Stuart P. Bentley
44
@beporter En este caso, si quisiera pasar esta plantilla a envsubst por alguna razón, querría usar <<"EOF", que no interpola variables (los terminadores entre comillas son como las comillas simples de heredocs).
Stuart P. Bentley
2
Lo usé como: cat template.txt | envsubst
truthadjustr
47

envsubst era nuevo para mí. Fantástico.

Para el registro, el uso de un heredoc es una excelente manera de crear una plantilla para un archivo conf.

STATUS_URI="/hows-it-goin";  MONITOR_IP="10.10.2.15";

cat >/etc/apache2/conf.d/mod_status.conf <<EOF
<Location ${STATUS_URI}>
    SetHandler server-status
    Order deny,allow
    Deny from all
    Allow from ${MONITOR_IP}
</Location>
EOF
Dan Garthwaite
fuente
1
prefiero esto mejor que envsubstporque me salvó del adicional apt-get install gettext-baseen mi Dockerfile
truthadjustr
Sin embargo, el shell como un script similar a una plantilla sin ninguna instalación de biblioteca externa ni estrés por hacer frente a expresiones difíciles.
千 木 郷
35

Estoy de acuerdo con el uso de sed: es la mejor herramienta para buscar / reemplazar. Aquí está mi enfoque:

$ cat template.txt
the number is ${i}
the dog's name is ${name}

$ cat replace.sed
s/${i}/5/
s/${name}/Fido/

$ sed -f replace.sed template.txt > out.txt

$ cat out.txt
the number is 5
the dog's name is Fido
Hai Vu
fuente
1
Esto requiere un archivo temporal para la cadena de sustitución, ¿verdad? ¿Hay alguna manera de hacerlo sin archivos temporales?
Vladislav Rastrusny
@FractalizeR: Algunas versiones de sed tienen una -iopción (editar archivos en su lugar) que es similar a la opción perl . Verifique la página de manual de su sed .
Chris Johnsen
@FractalizeR Sí, sed -i reemplazará en línea. Si se siente cómodo con Tcl (otro lenguaje de secuencias de comandos), consulte este hilo: stackoverflow.com/questions/2818130/…
Hai Vu
Creé el replace.sed de un archivo de propiedades con el siguiente comando sed: sed -e 's / ^ / s \ / $ {/ g' -e 's / = /} \ // g' -e 's / $ / \ // g 'the.properties> replace.sed
Jaap D
El código de @hai vu crea un programa sed y pasa ese programa al usar la bandera -f de sed. Si lo desea, en su lugar, puede pasar cada línea del programa sed a sed utilizando las banderas -e. FWIW Me gusta la idea de usar sed para crear plantillas.
Andrew Allbright
23

Creo que eval funciona muy bien. Maneja plantillas con saltos de línea, espacios en blanco y todo tipo de bash. Si tiene control total sobre las plantillas en sí, por supuesto:

$ cat template.txt
variable1 = ${variable1}
variable2 = $variable2
my-ip = \"$(curl -s ifconfig.me)\"

$ echo $variable1
AAA
$ echo $variable2
BBB
$ eval "echo \"$(<template.txt)\"" 2> /dev/null
variable1 = AAA
variable2 = BBB
my-ip = "11.22.33.44"

Este método debe usarse con cuidado, por supuesto, ya que eval puede ejecutar código arbitrario. Ejecutar esto como root está prácticamente fuera de discusión. Las citas en la plantilla deben escaparse, de lo contrario se las comerán eval.

También puede usar aquí los documentos, si lo prefiere cataecho

$ eval "cat <<< \"$(<template.txt)\"" 2> /dev/null

@plockc provocó una solución que evita el problema de escape de la cita de bash:

$ eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null

Editar: Se eliminó la parte de ejecutar esto como root usando sudo ...

Editar: ¡Se agregó un comentario sobre cómo se deben escapar las citas, se agregó la solución de plockc a la mezcla!

mogsie
fuente
Esto elimina las citas que tiene en su plantilla y no sustituirá dentro de comillas simples, por lo que, dependiendo del formato de su plantilla, puede provocar errores sutiles. Sin embargo, esto probablemente sea aplicable a cualquier método de plantilla basado en Bash.
Alex B
En mi humilde opinión, las plantillas basadas en Bash son una locura, ¡ya que debes ser un programador de bash para comprender lo que está haciendo tu plantilla! ¡Pero, gracias por el comentario!
mogsie
@AlexB: Este enfoque va a sustituir entre comillas simples, ya que son caracteres literales solo dentro de la cadena entre comillas dobles en lugar de encerrar delimitadores de cadenas cuando la evaled echo / catcomandos de procesos de ellas; tratar eval "echo \"'\$HOME'\"".
mklement0
21

Tengo una solución bash como mogsie pero con heredoc en lugar de herejías para permitirle evitar las comillas dobles

eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
plockc
fuente
44
Esta solución admite la expansión de parámetros Bash en la plantilla. Mis favoritos son parámetros obligatorios con ${param:?}y texto anidado alrededor de parámetros opcionales. Ejemplo: se ${DELAY:+<delay>$DELAY</delay>}expande a nada cuando DELAY no está definido y <delay> 17 </delay> cuando DELAY = 17.
Eric Bolinger
2
Oh! Y el delimitador EOF puede usar una cadena dinámica, como el PID _EOF_$$.
Eric Bolinger
1
@ mklement0 Una solución alternativa para seguir las nuevas líneas es usar alguna expansión como, por ejemplo, una variable vacía $trailing_newline, o usar $NL5y asegurarse de que se expanda como 5 nuevas líneas.
xebeche
@xebeche: Sí, colocar lo que sugiere al final dentrotemplate.txt funcionaría para preservar las nuevas líneas finales.
mklement0
1
Una solución elegante, pero tenga en cuenta que la sustitución del comando eliminará cualquier nueva línea final del archivo de entrada, aunque eso normalmente no será un problema. Otro caso extremo: debido al uso de eval, si template.txtcontiene EOFuna línea propia, terminará prematuramente el documento aquí y, por lo tanto, romperá el comando. (Punta del sombrero para @xebeche).
mklement0
18

Editar 6 de enero de 2017

Necesitaba mantener comillas dobles en mi archivo de configuración, por lo que las comillas dobles de escape doble con sed ayudan:

render_template() {
  eval "echo \"$(sed 's/\"/\\\\"/g' $1)\""
}

No puedo pensar en seguir líneas nuevas, pero se mantienen líneas vacías en el medio.


Aunque es un tema antiguo, en mi opinión, encontré una solución más elegante aquí: http://pempek.net/articles/2013/07/08/bash-sh-as-template-engine/

#!/bin/sh

# render a template configuration file
# expand variables + preserve formatting
render_template() {
  eval "echo \"$(cat $1)\""
}

user="Gregory"
render_template /path/to/template.txt > path/to/configuration_file

Todos los créditos a Grégory Pakosz .

CKK
fuente
Esto elimina las comillas dobles de la entrada y, si hay varias líneas nuevas en el archivo de entrada, las reemplaza con una sola.
mklement0
2
Necesitaba dos barras invertidas menos para que funcionara, es decir, eval "echo \"$(sed 's/\"/\\"/g' $1)\""
David Bau
Desafortunadamente, este enfoque no le permite crear plantillas de archivos php (que contienen $variables).
IStranger
10

En lugar de reinventar la rueda, vaya con envsubst Se puede usar en casi cualquier escenario, por ejemplo, creando archivos de configuración a partir de variables de entorno en contenedores acoplables.

Si en Mac, asegúrese de tener homebrew y luego vincúlelo desde gettext:

brew install gettext
brew link --force gettext

./template.cfg

# We put env variables into placeholders here
this_variable_1 = ${SOME_VARIABLE_1}
this_variable_2 = ${SOME_VARIABLE_2}

./.env:

SOME_VARIABLE_1=value_1
SOME_VARIABLE_2=value_2

./configure.sh

#!/bin/bash
cat template.cfg | envsubst > whatever.cfg

Ahora solo úsalo:

# make script executable
chmod +x ./configure.sh
# source your variables
. .env
# export your variables
# In practice you may not have to manually export variables 
# if your solution depends on tools that utilise .env file 
# automatically like pipenv etc. 
export SOME_VARIABLE_1 SOME_VARIABLE_2
# Create your config file
./configure.sh
smentek
fuente
esta secuencia de invocación de envsubstrealmente funciona.
Alex
Para una persona que quiere, envsubstno funciona en MacOS, que había necesidad de instalarlo usando homebrew: brew install gettext.
Matt
9

Una versión más larga pero más robusta de la respuesta aceptada:

perl -pe 's;(\\*)(\$([a-zA-Z_][a-zA-Z_0-9]*)|\$\{([a-zA-Z_][a-zA-Z_0-9]*)\})?;substr($1,0,int(length($1)/2)).($2&&length($1)%2?$2:$ENV{$3||$4});eg' template.txt

Esto expande todas las instancias de $VAR o ${VAR} a sus valores de entorno (o, si están definidos, la cadena vacía).

Se escapa correctamente de las barras diagonales inversas y acepta un $ escapado de barra invertida para inhibir la sustitución (a diferencia de envsubst, que, al parecer, no hace esto ).

Entonces, si su entorno es:

FOO=bar
BAZ=kenny
TARGET=backslashes
NOPE=engi

y tu plantilla es:

Two ${TARGET} walk into a \\$FOO. \\\\
\\\$FOO says, "Delete C:\\Windows\\System32, it's a virus."
$BAZ replies, "\${NOPE}s."

el resultado sería:

Two backslashes walk into a \bar. \\
\$FOO says, "Delete C:\Windows\System32, it's a virus."
kenny replies, "${NOPE}s."

Si solo desea escapar de las barras invertidas antes de $ (puede escribir "C: \ Windows \ System32" en una plantilla sin cambios), use esta versión ligeramente modificada:

perl -pe 's;(\\*)(\$([a-zA-Z_][a-zA-Z_0-9]*)|\$\{([a-zA-Z_][a-zA-Z_0-9]*)\});substr($1,0,int(length($1)/2)).(length($1)%2?$2:$ENV{$3||$4});eg' template.txt
Stuart P. Bentley
fuente
8

Lo habría hecho de esta manera, probablemente menos eficiente, pero más fácil de leer / mantener.

TEMPLATE='/path/to/template.file'
OUTPUT='/path/to/output.file'

while read LINE; do
  echo $LINE |
  sed 's/VARONE/NEWVALA/g' |
  sed 's/VARTWO/NEWVALB/g' |
  sed 's/VARTHR/NEWVALC/g' >> $OUTPUT
done < $TEMPLATE
Craig552uk
fuente
10
Puede hacerlo sin leer línea por línea y con una sola invocación de sed:sed -e 's/VARONE/NEWVALA/g' -e 's/VARTWO/NEWVALB/g' -e 's/VARTHR/NEWVALC/g' < $TEMPLATE > $OUTPUT
Brandon Bloom
8

Si desea usar plantillas Jinja2 , vea este proyecto: j2cli .

Soporta:

  • Plantillas de archivos JSON, INI, YAML y flujos de entrada
  • Plantillas de variables de entorno
Kolypto
fuente
5

Tomando la respuesta de ZyX usando bash puro pero con un nuevo estilo de coincidencia de expresiones regulares y sustitución indirecta de parámetros se convierte en:

#!/bin/bash
regex='\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}'
while read line; do
    while [[ "$line" =~ $regex ]]; do
        param="${BASH_REMATCH[1]}"
        line=${line//${BASH_REMATCH[0]}/${!param}}
    done
    echo $line
done
cuales
fuente
5

Si usar Perl es una opción y está contento con basar las expansiones solo en variables de entorno (a diferencia de todas las variables de shell ), considere la respuesta robusta de Stuart P. Bentley .

Esta respuesta tiene como objetivo proporcionar una solución solo para bash que, a pesar del uso de eval, debería ser segura de usar .

Los objetivos son:

  • Admite la expansión de ambas referencias ${name}y $namevariables.
  • Prevenir todas las demás expansiones:
    • sustituciones de comandos ( $(...)y sintaxis heredada `...`)
    • sustituciones aritméticas ( $((...))y sintaxis heredada $[...]).
  • Permitir la supresión selectiva de la expansión variable prefijando con \( \${name}).
  • Conservar caracteres especiales. en la entrada, especialmente "e \instancias.
  • Permitir entrada ya sea a través de argumentos o vía stdin.

FunciónexpandVars() :

expandVars() {
  local txtToEval=$* txtToEvalEscaped
  # If no arguments were passed, process stdin input.
  (( $# == 0 )) && IFS= read -r -d '' txtToEval
  # Disable command substitutions and arithmetic expansions to prevent execution
  # of arbitrary commands.
  # Note that selectively allowing $((...)) or $[...] to enable arithmetic
  # expressions is NOT safe, because command substitutions could be embedded in them.
  # If you fully trust or control the input, you can remove the `tr` calls below
  IFS= read -r -d '' txtToEvalEscaped < <(printf %s "$txtToEval" | tr '`([' '\1\2\3')
  # Pass the string to `eval`, escaping embedded double quotes first.
  # `printf %s` ensures that the string is printed without interpretation
  # (after processing by by bash).
  # The `tr` command reconverts the previously escaped chars. back to their
  # literal original.
  eval printf %s "\"${txtToEvalEscaped//\"/\\\"}\"" | tr '\1\2\3' '`(['
}

Ejemplos:

$ expandVars '\$HOME="$HOME"; `date` and $(ls)'
$HOME="/home/jdoe"; `date` and $(ls)  # only $HOME was expanded

$ printf '\$SHELL=${SHELL}, but "$(( 1 \ 2 ))" will not expand' | expandVars
$SHELL=/bin/bash, but "$(( 1 \ 2 ))" will not expand # only ${SHELL} was expanded
  • Por razones de rendimiento, la función lee la entrada stdin de una vez en la memoria, pero es fácil adaptar la función a un enfoque línea por línea.
  • También admite expansiones de variables no básicas como ${HOME:0:10}, siempre que no contengan comandos incrustados o sustituciones aritméticas, como${HOME:0:$(echo 10)}
    • Tales sustituciones incrustadas en realidad ROMPEN la función (porque todos $(y las `instancias se escapan a ciegas).
    • Del mismo modo, las referencias de variables mal formadas como ${HOME(falta de cierre }) ROMPEN la función.
  • Debido al manejo de bash de cadenas entre comillas dobles, las barras invertidas se manejan de la siguiente manera:
    • \$name Previene la expansión.
    • Un único \no seguido por $se conserva tal cual.
    • Si desea representar varias \ instancias adyacentes , debe duplicarlas ; p.ej:
      • \\-> \- lo mismo que solo\
      • \\\\ -> \\
    • Caracteres La entrada no debe contener los siguientes (raramente usados), que se utilizan para fines internos: 0x1, 0x2, 0x3.
  • Existe una preocupación en gran medida hipotética de que si bash introduce una nueva sintaxis de expansión, esta función podría no evitar tales expansiones; consulte a continuación una solución que no se usa eval.

Si está buscando una solución más restrictiva que solo sea compatible con ${name}expansiones , es decir, con llaves obligatorias , ignorando $namereferencias, vea esta respuesta mía.


Aquí hay una versión mejorada de la evalsolución libre de bash de la respuesta aceptada :

Las mejoras son:

  • Soporte para expansión de referencias tanto variables ${name}como $namevariables.
  • Apoyo para \ referencias variables de escape que no deberían expandirse.
  • A diferencia del eval solución basada anteriormente,
    • las expansiones no básicas se ignoran
    • las referencias de variables mal formadas se ignoran (no rompen el script)
 IFS= read -d '' -r lines # read all input from stdin at once
 end_offset=${#lines}
 while [[ "${lines:0:end_offset}" =~ (.*)\$(\{([a-zA-Z_][a-zA-Z_0-9]*)\}|([a-zA-Z_][a-zA-Z_0-9]*))(.*) ]] ; do
      pre=${BASH_REMATCH[1]} # everything before the var. reference
      post=${BASH_REMATCH[5]}${lines:end_offset} # everything after
      # extract the var. name; it's in the 3rd capture group, if the name is enclosed in {...}, and the 4th otherwise
      [[ -n ${BASH_REMATCH[3]} ]] && varName=${BASH_REMATCH[3]} || varName=${BASH_REMATCH[4]}
      # Is the var ref. escaped, i.e., prefixed with an odd number of backslashes?
      if [[ $pre =~ \\+$ ]] && (( ${#BASH_REMATCH} % 2 )); then
           : # no change to $lines, leave escaped var. ref. untouched
      else # replace the variable reference with the variable's value using indirect expansion
           lines=${pre}${!varName}${post}
      fi
      end_offset=${#pre}
 done
 printf %s "$lines"
mklement0
fuente
5

Aquí hay otra solución de bash puro:

  • está usando heredoc, entonces:
    • la complejidad no aumenta debido a la sintaxis adicional requerida
    • la plantilla puede incluir código bash
      • eso también te permite sangrar cosas correctamente. Vea abajo.
  • no usa eval, entonces:
    • sin problemas con la representación de líneas vacías finales
    • sin problemas con las comillas en la plantilla

$ cat code

#!/bin/bash
LISTING=$( ls )

cat_template() {
  echo "cat << EOT"
  cat "$1"
  echo EOT
}

cat_template template | LISTING="$LISTING" bash

$ cat template (con líneas finales y comillas dobles)

<html>
  <head>
  </head>
  <body> 
    <p>"directory listing"
      <pre>
$( echo "$LISTING" | sed 's/^/        /' )
      <pre>
    </p>
  </body>
</html>

salida

<html>
  <head>
  </head>
  <body> 
    <p>"directory listing"
      <pre>
        code
        template
      <pre>
    </p>
  </body>
</html>
Tomáš Pospíšek
fuente
4

Aquí hay otra solución: generar un script bash con todas las variables y el contenido del archivo de plantilla, ese script se vería así:

word=dog           
i=1                
cat << EOF         
the number is ${i} 
the word is ${word}

EOF                

Si introducimos este script en bash, produciría el resultado deseado:

the number is 1
the word is dog

Aquí se explica cómo generar ese script y alimentar ese script en bash:

(
    # Variables
    echo word=dog
    echo i=1

    # add the template
    echo "cat << EOF"
    cat template.txt
    echo EOF
) | bash

Discusión

  • Los paréntesis abren un sub shell, su propósito es agrupar todos los resultados generados
  • Dentro del sub shell, generamos todas las declaraciones de variables
  • También en el sub shell, generamos el catcomando con HEREDOC
  • Finalmente, alimentamos la salida del subconjunto a bash y producimos la salida deseada
  • Si desea redirigir esta salida a un archivo, reemplace la última línea con:

    ) | bash > output.txt
Hai Vu
fuente
3

Esta página describe una respuesta con awk

awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt
Matt Brown
fuente
Esto mantiene todas las citas intactas. ¡Excelente!
Pepster
3

Estuche perfecto para shtpl . (proyecto mío, por lo que no se usa mucho y carece de documentación. Pero aquí está la solución que ofrece de todos modos. Puede que desee probarlo).

Solo ejecuta:

$ i=1 word=dog sh -c "$( shtpl template.txt )"

El resultado es:

the number is 1
the word is dog

Que te diviertas.

zstegi
fuente
1
Si es una mierda, de todos modos es rechazado. Y estoy de acuerdo con eso. Pero bueno, punto tomado, que no es claramente visible, que en realidad es mi proyecto. Voy a hacerlo más visible en el futuro. Gracias de todos modos por tu comentario y tu tiempo.
zstegi
Quiero agregar, que realmente busqué casos de uso ayer, donde shtpl sería una solución perfecta. Sí, estaba aburrida ...
zstegi
3
# Usage: template your_file.conf.template > your_file.conf
template() {
        local IFS line
        while IFS=$'\n\r' read -r line ; do
                line=${line//\\/\\\\}         # escape backslashes
                line=${line//\"/\\\"}         # escape "
                line=${line//\`/\\\`}         # escape `
                line=${line//\$/\\\$}         # escape $
                line=${line//\\\${/\${}       # de-escape ${         - allows variable substitution: ${var} ${var:-default_value} etc
                # to allow arithmetic expansion or command substitution uncomment one of following lines:
#               line=${line//\\\$\(/\$\(}     # de-escape $( and $(( - allows $(( 1 + 2 )) or $( command ) - UNSECURE
#               line=${line//\\\$\(\(/\$\(\(} # de-escape $((        - allows $(( 1 + 2 ))
                eval "echo \"${line}\"";
        done < "$1"
}

Esta es la función de bash puro ajustable a su gusto, utilizada en producción y no debe interrumpirse en ninguna entrada. Si se rompe, avíseme.

ttt
fuente
0

Aquí hay una función bash que conserva los espacios en blanco:

# Render a file in bash, i.e. expand environment variables. Preserves whitespace.
function render_file () {
    while IFS='' read line; do
        eval echo \""${line}"\"
    done < "${1}"
}
Igor Katson
fuente
0

Aquí hay una perlsecuencia de comandos modificada basada en algunas de las otras respuestas:

perl -pe 's/([^\\]|^)\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}/$1.$ENV{$2}/eg' -i template

Características (basadas en mis necesidades, pero deberían ser fáciles de modificar):

  • Se omiten las expansiones de parámetros escapados (por ejemplo, \ $ {VAR}).
  • Admite expansiones de parámetros de la forma $ {VAR}, pero no $ VAR.
  • Reemplaza $ {VAR} con una cadena en blanco si no hay VAR envar.
  • Solo admite az, AZ, 0-9 y caracteres de subrayado en el nombre (excluyendo dígitos en la primera posición).
Kevin
fuente
0

Mira el script de Python de sustitución de variables simples aquí: https://github.com/jeckep/vsubst

Es muy facíl de usar:

python subst.py --props secure.properties --src_path ./templates --dst_path ./dist
jeckep
fuente
0

Mi humilde contribución a esta maravillosa pregunta.

tpl() {
    local file=$(cat - | \
                 sed -e 's/\"/\\"/g' \
                     -e "s/'/\\'/g")
    local vars=$(echo ${@} | tr ' ' ';')
    echo "$(sh -c "${vars};echo \"$file\"")"
}

cat tpl.txt | tpl "one=fish" "two=fish"

Básicamente, esto funciona al usar la subshell para hacer el reemplazo de envar, excepto que no usa eval y escapa explícitamente a comillas simples y dobles. Concatena las expresiones var en una sola línea sin espacios para no confundir shy luego pasa la plantilla a echo, permitiendo shmanejar los reemplazos var. Conserva nuevas líneas, comentarios, etc. y puede escapar \${like_this}si no desea que se interprete la var.${missing_var}solo se reemplazará con un valor vacío.

Muchas de las otras respuestas aquí son muy buenas, pero quería algo muy simple y no necesita manejar literalmente todos los casos posibles para los casos de plantillas que tengo actualmente.

¡Disfrutar!

Peter M. Elias
fuente
0

Para dar seguimiento a la respuesta de plockc en esta página, aquí hay una versión adecuada para el tablero, para aquellos de ustedes que buscan evitar los bashismos.

eval "cat <<EOF >outputfile
$( cat template.in )
EOF
" 2> /dev/null
bgStack15
fuente