Quiero canalizar la salida 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?
Sed !
Dado template.txt:
El número es $ {i} La palabra es $ {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 sed
.
Aquí hay una solución de yottatsa en una pregunta similar que solo reemplaza variables como $ VAR o $ {VAR}, y es una breve descripción de una sola línea.
i=32 Word=foo envsubst < template.txt
Por supuesto, si i and Word están en su entorno, entonces es justo
envsubst < template.txt
En mi Mac, parece que se instaló como parte de gettext y de MacGPG2
Aquí hay una mejora en la solución de mogsie en una pregunta similar, mi solución no requiere que escale las comillas dobles, mogsie's sí, ¡pero su es un liner!
eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
El poder de estas dos soluciones es que solo se obtienen algunos tipos de expansiones de Shell que normalmente no se producen $ ((...)), `...` y $ (...), aunque la barra invertida es un personaje de escape aquí, pero no tiene que preocuparse de que el análisis tenga un error, y hace varias líneas muy bien.
Utilice /bin/sh
. Cree un pequeño script de Shell que establezca las variables y luego analice la plantilla utilizando el propio Shell. Me gusta (edita para manejar las nuevas líneas correctamente):
the number is ${i}
the Word is ${Word}
#!/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"
#sh script.sh
the number is 1
the Word is dog
Estaba pensando en esto nuevamente, dado el interés reciente, y creo que la herramienta en la que originalmente pensaba era m4
, el procesador de macros para autotools. Entonces, en lugar de la variable que originalmente especifiqué, usarías:
$echo 'I am a DBNAME' | m4 -DDBNAME="database name"
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
aquí está 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
Aquí hay una función Bash robusta que, a pesar de usar eval
, debería ser seguro de usar.
Todas las referencias de variables ${varName}
en el texto de entrada se expanden en función de las variables del Shell que llama.
Nada más está expandido: ninguna de las referencias de variables cuyos nombres son no encerrado en {...}
(como $varName
), ni sustituciones de comandos ($(...)
y sintaxis heredada `...`
), ni sustituciones aritméticas ($((...))
sintaxis heredada $[...]
).
Para tratar un $
como un literal, \
- escúchalo; 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 0x1
, 0x2
, 0x3
, y 0x4
caracteres de control están presentes en la entrada, debido a esos caracteres. se utilizan internamente, ya que la función procesa texto , debe ser una suposición segura.
Si está abierto a usar Perl , esa sería mi sugerencia. Aunque probablemente hay algunos sed y/o AWK expertos que probablemente saben cómo hacer esto mucho más fácil. Si tiene una asignación más compleja con más que solo dbName para sus reemplazos, podría extender esto con bastante facilidad, pero igual podría incluirlo en un script Perl estándar en ese momento.
Perl -p -e 's/\$\{dbName\}/testdb/s' yourfile | mysql
Un script corto de Perl para hacer algo un poco más complicado (manejar varias teclas):
#!/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 la secuencia de comandos anterior como reemplazar secuencia de comandos, se podría utilizar de la siguiente manera:
replace-script < yourfile | mysql
Crear rendertemplate.sh
:
#!/usr/bin/env bash
eval "echo \"$(cat $1)\""
Y template.tmpl
:
Hello, ${WORLD}
Goodbye, ${CHEESE}
Render la plantilla:
$ export WORLD=Foo
$ CHEESE=Bar ./rendertemplate.sh template.tmpl
Hello, Foo
Goodbye, Bar
archivo.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
Sugeriría usar algo como Sigil : https://github.com/gliderlabs/sigil
Se compila a un solo binario, por lo que es extremadamente fácil de instalar en los sistemas.
Entonces puedes hacer un sencillo de una sola línea 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 expresiones regulares o sed
Se puede hacer en bash sí mismo si tiene el control del formato del archivo de configuración. Solo necesita generar (".") El archivo de configuración en lugar de subhazcarlo. Eso asegura que las variables se creen en el contexto del Shell actual (y continúen existiendo) en lugar de la subshell (donde la variable desaparece cuando la subshell sale).
$ 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, puede simplemente '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 :-).
Hay muchas opciones aquí, pero pensé que tiraría la mía en el montón. Está basado en Perl, solo se dirige a variables de la forma $ {...}, toma el archivo para procesar como un argumento y genera el archivo convertido en la salida estándar:
use Env;
Env::import();
while(<>) { $_ =~ s/(\${\w+})/$1/eeg; $text .= $_; }
print "$text";
Por supuesto, no soy realmente una persona de Perl, así que fácilmente podría haber un error fatal (aunque funciona para mí).
Encontré este hilo mientras me preguntaba lo mismo. Me inspiró a esto (cuidado con las comillas traseras).
$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world
Aquí hay una forma de hacer que el Shell haga la sustitución por usted, como si el contenido del archivo se escribiera entre comillas dobles.
Usando el ejemplo de template.txt con contenidos:
The number is ${i}
The Word is ${Word}
La siguiente línea hará que el Shell interpole el contenido de template.txt y escriba el resultado en estándar.
i='1' Word='dog' sh -c 'echo "'"$(cat template.txt)"'"'
Explicación:
i
y Word
se pasan como variables de entorno asignadas a la ejecución de sh
.sh
ejecuta el contenido de la cadena que se pasa.echo "
' + "$(cat template.txt)
" + '"
'"
, "$(cat template.txt)
" se convierte en la salida de cat template.txt
.sh -c
se convierte en: echo "The number is ${i}\nThe Word is ${Word}"
,i
y Word
son las variables de entorno especificadas.