bash - sustituir - ¿Cómo reemplazar ${} marcadores de posición en un archivo de texto?
sed reemplazar/ (14)
Quiero canalizar el resultado de un archivo de "plantilla" en MySQL, el archivo tiene variables como ${dbName}
intercaladas. ¿Cuál es la utilidad de línea de comandos para reemplazar estas instancias y volcar la salida a la salida estándar?
Actualizar
Aquí hay una solución de yottatsa sobre una pregunta similar que solo hace el reemplazo de variables como $ VAR o $ {VAR}, y es un breve resumen
i=32 word=foo envsubst < template.txt
Por supuesto, si yo y la palabra están en su entorno, entonces es solo
envsubst < template.txt
En mi Mac, parece que se instaló como parte de gettext y de MacGPG2
Vieja respuesta
Aquí hay una mejora de la solución de mogsie en una pregunta similar, mi solución no requiere que escales las comillas dobles, la de Mogsie, ¡pero la suya es un trazador!
eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
El poder de estas dos soluciones es que solo se obtienen unos pocos tipos de expansiones de shell que normalmente no ocurren $ ((...)), `...` y $ (...), aunque la barra invertida es una Escape carácter aquí, pero no tienes que preocuparte de que el análisis tiene un error, y hace varias líneas muy bien.
Aquí hay una función Bash robusta que, a pesar de usar eval
, debe ser segura de usar.
Todas las referencias de variable ${varName}
en el texto de entrada se expanden en función de las variables del intérprete de llamada.
Nada más se expande: ni las referencias de variables cuyos nombres no están encerrados en {...}
(como $varName
), ni las sustituciones de comandos ( $(...)
y la sintaxis heredada `...`
), ni las sustituciones aritméticas ( $((...))
y la sintaxis heredada $[...]
).
Para tratar un $
como un literal, /
-capelo; por ejemplo: /${HOME}
Tenga en cuenta que la entrada solo se acepta a través de stdin .
Ejemplo:
$ expandVarsStrict <<<''$HOME is "${HOME}"; `date` and /$(ls)'' # only ${HOME} is expanded
$HOME is "/Users/jdoe"; `date` and $(ls)
Código fuente de la función:
expandVarsStrict(){
local line lineEscaped
while IFS= read -r line || [[ -n $line ]]; do # the `||` clause ensures that the last line is read even if it doesn''t end with /n
# Escape ALL chars. that could trigger an expansion..
IFS= read -r -d '''' lineEscaped < <(printf %s "$line" | tr ''`([$'' ''/1/2/3/4'')
# ... then selectively reenable ${ references
lineEscaped=${lineEscaped//$''/4''{//${}
# Finally, escape embedded double quotes to preserve them.
lineEscaped=${lineEscaped///"////"}
eval "printf ''%s/n'' /"$lineEscaped/"" | tr ''/1/2/3/4'' ''`([$''
done
}
La función asume que no hay caracteres de control 0x1
, 0x2
, 0x3
y 0x4
en la entrada, porque esos caracteres. se usan internamente, ya que la función procesa texto , eso debería ser una suposición segura.
Crear rendertemplate.sh
:
#!/usr/bin/env bash
eval "echo /"$(cat $1)/""
Y template.tmpl
:
Hello, ${WORLD}
Goodbye, ${CHEESE}
Renderizar la plantilla:
$ export WORLD=Foo
$ CHEESE=Bar ./rendertemplate.sh template.tmpl
Hello, Foo
Goodbye, Bar
Encontré este hilo mientras me preguntaba lo mismo. Me inspiró a esto (cuidado con los apoyos)
$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world
Estaba pensando en esto de nuevo, dado el reciente interés, y creo que la herramienta en la que originalmente estaba pensando era m4
, el macroprocesador para autotools. Entonces, en lugar de la variable que originalmente especifiqué, usarías:
$echo ''I am a DBNAME'' | m4 -DDBNAME="database name"
Muchas opciones aquí, pero pensé que lanzaría la mía en el montón. Está basado en Perl, solo las variables de destino del formulario $ {...}, toman el archivo para procesarlo como un argumento y emite el archivo convertido en stdout:
use Env;
Env::import();
while(<>) { $_ =~ s/(/${/w+})/$1/eeg; $text .= $_; }
print "$text";
Por supuesto que no soy realmente una persona perl, por lo que fácilmente podría haber un defecto fatal (aunque a mí me funciona).
Se puede hacer en bash si tienes el control del formato del archivo de configuración. Solo necesita obtener (".") El archivo de configuración en lugar de subshell. Eso asegura que las variables se crean en el contexto del shell actual (y continúan existiendo) en lugar de la subcadena (donde la variable desaparece cuando sale el subshell).
$ cat config.data
export parm_jdbc=jdbc:db2://box7.co.uk:5000/INSTA
export parm_user=pax
export parm_pwd=never_you_mind
$ cat go.bash
. config.data
echo "JDBC string is " $parm_jdbc
echo "Username is " $parm_user
echo "Password is " $parm_pwd
$ bash go.bash
JDBC string is jdbc:db2://box7.co.uk:5000/INSTA
Username is pax
Password is never_you_mind
Si su archivo de configuración no puede ser un script de shell, simplemente puede ''compilarlo'' antes de ejecutarlo (la compilación depende de su formato de entrada).
$ cat config.data
parm_jdbc=jdbc:db2://box7.co.uk:5000/INSTA # JDBC URL
parm_user=pax # user name
parm_pwd=never_you_mind # password
$ cat go.bash
cat config.data
| sed ''s/#.*$//''
| sed ''s/[ /t]*$//''
| sed ''s/^[ /t]*//''
| grep -v ''^$''
| sed ''s/^/export ''
>config.data-compiled
. config.data-compiled
echo "JDBC string is " $parm_jdbc
echo "Username is " $parm_user
echo "Password is " $parm_pwd
$ bash go.bash
JDBC string is jdbc:db2://box7.co.uk:5000/INSTA
Username is pax
Password is never_you_mind
En su caso específico, podría usar algo como:
$ cat config.data
export p_p1=val1
export p_p2=val2
$ cat go.bash
. ./config.data
echo "select * from dbtable where p1 = ''$p_p1'' and p2 like ''$p_p2%'' order by p1"
$ bash go.bash
select * from dbtable where p1 = ''val1'' and p2 like ''val2%'' order by p1
Luego canalice la salida de go.bash a MySQL y listo, con suerte no destruirá su base de datos :-).
Si está abierto a usar Perl , esa sería mi sugerencia. Aunque es probable que haya algunos expertos sed y / o AWK que probablemente saben cómo hacer esto mucho más fácil. Si tiene un mapeo más complejo con algo más que solo dbName para sus reemplazos, podría ampliarlo con bastante facilidad, pero podría colocarlo en un script de Perl estándar en ese punto.
perl -p -e ''s//$/{dbName/}/testdb/s'' yourfile | mysql
Un script corto de Perl para hacer algo un poco más complicado (manejar múltiples claves):
#!/usr/bin/env perl
my %replace = ( ''dbName'' => ''testdb'', ''somethingElse'' => ''fooBar'' );
undef $/;
my $buf = <STDIN>;
$buf =~ s//$/{$_/}/$replace{$_}/g for keys %replace;
print $buf;
Si nombra el script anterior como replace-script, podría usarse de la siguiente manera:
replace-script < yourfile | mysql
Sugeriría usar algo como Sigil : https://github.com/gliderlabs/sigil
Se compila en un solo binario, por lo que es extremadamente fácil de instalar en los sistemas.
Entonces puedes hacer un simple delineador como el siguiente:
cat my-file.conf.template | sigil -p $(env) > my-file.conf
Esto es mucho más seguro que eval
y más fácil que usar regex o sed
Usa /bin/sh
. Cree un pequeño script de shell que establezca las variables y luego analice la plantilla utilizando el propio shell. Al igual que (edición para manejar nuevas líneas correctamente):
Archivo template.txt:
the number is ${i}
the word is ${word}
Archivo 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"
Salida:
#sh script.sh
the number is 1
the word is dog
esta es mi solución con Perl basada en la respuesta anterior, reemplaza las variables de entorno:
perl -p -e ''s//$/{(/w+)/}/(exists $ENV{$1}?$ENV{$1}:"missing variable $1")/eg'' < infile > outfile
file.tpl:
The following bash function should only replace ${var1} syntax and ignore
other shell special chars such as `backticks` or $var2 or "double quotes".
If I have missed anything - let me know.
script.sh:
template(){
# usage: template file.tpl
while read -r line ; do
line=${line///"////"}
line=${line///`////`}
line=${line///$////$}
line=${line/////${//${}
eval "echo /"$line/"";
done < ${1}
}
var1="*replaced*"
var2="*not replaced*"
template file.tpl > result.txt
template.txt
Variable 1 value: ${var1}
Variable 2 value: ${var2}
data.sh
#!/usr/bin/env bash
declare var1="value 1"
declare var2="value 2"
parser.sh
#!/usr/bin/env bash
# args
declare file_data=$1
declare file_input=$2
declare file_output=$3
source $file_data
eval "echo /"$(< $file_input)/"" > $file_output
./parser.sh data.sh template.txt parsed_file.txt
parsed_file.txt
Variable 1 value: value 1
Variable 2 value: value 2
Sed !
Dado template.txt:
The number is ${i} The word is ${word}
solo tenemos que decir:
sed -e "s//${i}/1/" -e "s//${word}/dog/" template.txt
Gracias a Jonathan Leffler por la sugerencia de pasar múltiples argumentos -e
a la misma invocación de sed
.