Como substituir espaços reservados ${} em um arquivo de texto?
-
03-07-2019 - |
Pergunta
Eu quero canalizar a saída de um arquivo de "modelo" para o MySQL, o arquivo tendo variáveis como ${dbName}
intercalado.Qual é o utilitário de linha de comando para substituir essas instâncias e despejar a saída na saída padrão?
Solução
Sed!
Dado template.txt:
The number is ${i} The word is ${word}
só nos resta dizer:
sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.txt
Obrigado ao Jonathan Leffler pela dica para passar em múltiplos -e
argumentos para o mesmo sed
invocação.
Outras dicas
Atualizar
Aqui está uma solução de Yottatsa em uma pergunta semelhante que substitui apenas variáveis como $VAR ou ${VAR} e é uma breve linha
i=32 word=foo envsubst < template.txt
Claro se eu e palavra estão em seu ambiente, então é só
envsubst < template.txt
No meu Mac parece que foi instalado como parte do obter texto e de MacGPG2
Resposta Antiga
Aqui está uma melhoria para a solução de mogsie em uma pergunta semelhante, minha solução não exige que você escale aspas duplas, como a de mogsie, mas a dele é de uma só linha!
eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
O poder dessas duas soluções é que você obtém apenas alguns tipos de expansões de shell que não ocorrem normalmente $((...)), `...` e $(...), embora a barra invertida é um caractere de escape aqui, mas você não precisa se preocupar se a análise tem um bug e ela executa várias linhas perfeitamente.
Usar /bin/sh
.Crie um pequeno script de shell que defina as variáveis e, em seguida, analise o modelo usando o próprio shell.Assim (edite para lidar com novas linhas corretamente):
Arquivo modelo.txt:
the number is ${i}
the word is ${word}
Arquivo 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"
Saída:
#sh script.sh
the number is 1
the word is dog
Estava pensando nisso novamente, dado o interesse recente, e acho que a ferramenta em que estava pensando originalmente era m4
, o processador macro para ferramentas automáticas.Então, em vez da variável que especifiquei originalmente, você usaria:
$echo 'I am a DBNAME' | m4 -DDBNAME="database name"
modelo.txt
Variable 1 value: ${var1}
Variable 2 value: ${var2}
dados.sh
#!/usr/bin/env bash
declare var1="value 1"
declare var2="value 2"
analisador.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
arquivo_parsed.txt
Variable 1 value: value 1
Variable 2 value: value 2
aqui está minha solução com perl baseada na resposta anterior, substitui variáveis de ambiente:
perl -p -e 's/\$\{(\w+)\}/(exists $ENV{$1}?$ENV{$1}:"missing variable $1")/eg' < infile > outfile
Aqui está um função Bash robusta que - apesar de usar eval
- deve ser seguro de usar.
Todos ${varName}
referências de variáveis no texto de entrada são expandidas com base nas variáveis do shell de chamada.
Nada mais é expandido:nem referências de variáveis cujos nomes são não encerrado em {...}
(como $varName
), nem substituições de comando ($(...)
e sintaxe herdada `...`
), nem substituições aritméticas ($((...))
e sintaxe herdada $[...]
).
Para tratar um $
como literal, \
-escapar disso;por exemplo.:\${HOME}
Observe que a entrada só é aceita via stdin.
Exemplo:
$ expandVarsStrict <<<'$HOME is "${HOME}"; `date` and \$(ls)' # only ${HOME} is expanded
$HOME is "/Users/jdoe"; `date` and $(ls)
Código fonte da função:
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
}
A função assume que não 0x1
, 0x2
, 0x3
, e 0x4
caracteres de controle estão presentes na entrada, porque esses caracteres.são usados internamente - já que a função processa texto, essa deve ser uma suposição segura.
Se você estiver aberto a usar Perl, essa seria minha sugestão.Embora existam provavelmente alguns sed e/ou AWK especialistas que provavelmente sabem como fazer isso com muito mais facilidade.Se você tiver um mapeamento mais complexo com mais do que apenas dbName para suas substituições, poderá estendê-lo facilmente, mas também poderá colocá-lo em um script Perl padrão nesse ponto.
perl -p -e 's/\$\{dbName\}/testdb/s' yourfile | mysql
Um pequeno script Perl para fazer algo um pouco mais complicado (lidar com múltiplas chaves):
#!/usr/bin/env perl
my %replace = ( 'dbName' => 'testdb', 'somethingElse' => 'fooBar' );
undef $/;
my $buf = <STDIN>;
$buf =~ s/\$\{$_\}/$replace{$_}/g for keys %replace;
print $buf;
Se você nomear o script acima como script de substituição, ele poderá ser usado da seguinte maneira:
replace-script < yourfile | mysql
Criar rendertemplate.sh
:
#!/usr/bin/env bash
eval "echo \"$(cat $1)\""
E template.tmpl
:
Hello, ${WORLD}
Goodbye, ${CHEESE}
Renderize o modelo:
$ export WORLD=Foo
$ CHEESE=Bar ./rendertemplate.sh template.tmpl
Hello, Foo
Goodbye, Bar
arquivo.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
Eu sugeriria usar algo como Sigilo: https://github.com/gliderlabs/sigil
Ele é compilado em um único binário, por isso é extremamente fácil de instalar nos sistemas.
Então você pode fazer uma linha simples como a seguinte:
cat my-file.conf.template | sigil -p $(env) > my-file.conf
Isto é muito mais seguro do que eval
e mais fácil do que usar regex ou sed
Encontrei este tópico enquanto me perguntava a mesma coisa.Isso me inspirou nisso (cuidado com os crases)
$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world
Muitas opções aqui, mas resolvi jogar a minha na pilha.É baseado em Perl, tem como alvo apenas variáveis no formato ${...}, pega o arquivo para processar como um argumento e gera o arquivo convertido em stdout:
use Env;
Env::import();
while(<>) { $_ =~ s/(\${\w+})/$1/eeg; $text .= $_; }
print "$text";
É claro que não sou realmente uma pessoa perl, então pode facilmente haver uma falha fatal (embora funcione para mim).
Isso pode ser feito no próprio bash se você tiver controle do formato do arquivo de configuração.Você só precisa fornecer (".") o arquivo de configuração em vez de subdividi-lo.Isso garante que as variáveis sejam criadas no contexto do shell atual (e continuem a existir) em vez do subshell (onde a variável desaparece quando o subshell sai).
$ 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
Se o seu arquivo de configuração não puder ser um script de shell, você pode simplesmente 'compilá-lo' antes de executá-lo (a compilação depende do seu 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
No seu caso específico, você poderia 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
Em seguida, canalize a saída do go.bash para o MySQL e pronto, espero que você não destrua seu banco de dados :-).
Aqui está uma maneira de fazer com que o shell faça a substituição para você, como se o conteúdo do arquivo fosse digitado entre aspas duplas.
Usando o exemplo de template.txt com conteúdo:
The number is ${i}
The word is ${word}
A linha a seguir fará com que o shell interpole o conteúdo de template.txt e grave o resultado na saída padrão.
i='1' word='dog' sh -c 'echo "'"$(cat template.txt)"'"'
Explicação:
i
eword
são passados como variáveis de ambiente com escopo para a execução desh
.sh
executa o conteúdo da string que é passada.- Strings escritas uma ao lado da outra tornam-se uma string, essa string é:
- '
echo "
' + "$(cat template.txt)
" + '"
'
- '
- Como a substituição é entre
"
, "$(cat template.txt)
"torna-se a saída decat template.txt
. - Então o comando executado por
sh -c
torna-se:echo "The number is ${i}\nThe word is ${word}"
,- onde
i
eword
são as variáveis de ambiente especificadas.