Bash Templating: Como criar arquivos de configuração a partir de modelos com o Bash?


134

Estou escrevendo um script para automatizar a criação de arquivos de configuração para Apache e PHP para meu próprio servidor web. Não quero usar GUIs como CPanel ou ISPConfig.

Eu tenho alguns modelos de arquivos de configuração Apache e PHP. O script Bash precisa ler modelos, fazer substituição de variáveis ​​e gerar modelos analisados ​​em alguma pasta. Qual o melhor jeito pra fazer isso? Eu posso pensar em várias maneiras. Qual é o melhor ou pode haver algumas maneiras melhores de fazer isso? Eu quero fazer isso no Bash puro (é fácil no PHP, por exemplo)

1) Como substituir espaços reservados $ {} em um arquivo 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"

BTW, como redireciono a saída para um arquivo externo aqui? Preciso escapar de algo se as variáveis ​​contiverem, digamos, aspas?

2) Usando cat & sed para substituir cada variável pelo seu valor:

Dado template.txt:

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

Comando:

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

Parece ruim para mim devido à necessidade de escapar de muitos símbolos diferentes e com muitas variáveis ​​a linha será muito longa.

Você consegue pensar em outra solução elegante e segura?


Respostas:


61

Você pode usar isto:

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

para substituir todas as ${...}seqüências de caracteres pelas variáveis ​​de ambiente correspondentes (não esqueça de exportá-las antes de executar este script).

Para o bash puro, isso deve funcionar (assumindo que as variáveis ​​não contenham $ {...} strings):

#!/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

. Solução que não trava se o RHS faz referência a alguma variável que faz referência a si mesma:

#!/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}"

AVISO : Não conheço uma maneira de manipular corretamente as entradas com NULs no bash ou preservar a quantidade de novas linhas à direita. A última variante é apresentada como é porque os shells “amam” a entrada binária:

  1. read interpretará barras invertidas.
  2. read -r não interpretará barras invertidas, mas ainda eliminará a última linha se não terminar com uma nova linha.
  3. "$(…)"irá retirar o máximo de novas linhas de arrasto, como não estão presentes, de modo que terminam com ; echo -n ae utilização echo -n "${line:0:-1}": este descarta o último carácter (que é a) e conservas como muitas mudanças de linha de arrasto como havia na entrada (não incluindo).

3
Eu mudaria [^}]para [A-Za-Z_][A-Za-z0-9_]na versão bash para impedir que o shell fosse além da substituição estrita (por exemplo, se ele tentasse processar ${some_unused_var-$(rm -rf $HOME)}).
Chris Johnsen

2
@FractalizeR você pode querer alterar $&na solução perl "": primeiro deixa ${...}intocado se não for substituído, depois o substitui por uma string vazia.
ZyX

5
NOTA: Aparentemente, houve uma alteração do bash 3.1 para 3.2 (e acima), no qual as aspas simples em torno do regex - tratam o conteúdo do regex como um literal de string. Portanto, a regex acima deve ser ... (\ $ \ {[a-zA-Z _] [a-zA-Z_0-9] * \}) stackoverflow.com/questions/304864/…
Blue Waters

2
Para fazer o whileloop ler a última linha, mesmo que não seja finalizada por uma nova linha, use while read -r line || [[ -n $line ]]; do. Além disso, seu readcomando remove os espaços em branco iniciais e finais de cada linha; Para evitar isso, use while IFS= read -r line || [[ -n $line ]]; do
mklement0

2
Apenas para observar uma restrição para quem procura uma solução abrangente: Essas soluções úteis, de outra forma, não permitem que você proteja seletivamente as referências variáveis ​​da expansão (por exemplo, \ removendo-as).
mklement0

138

Experimentar envsubst

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF

11
Apenas para referência, envsubstnão é necessário ao usar um heredoc, pois o bash trata o heredoc como uma string literal entre aspas duplas e já interpola variáveis ​​nele. É uma ótima opção quando você deseja ler o modelo de outro arquivo. Um bom substituto para os muito mais pesados m4.
beporter

2
Fiquei agradavelmente surpreso ao saber sobre esse comando. Eu estava tentando manipular a funcionalidade do envsubst manualmente com zero sucesso. Obrigado yottatsa!
Tim Stewart

4
Nota: envsubsté um utilitário gettext GNU e, na verdade, não é tão robusto (já que gettext é destinado à localização de mensagens humanas). Mais importante, ele não reconhece substituições $ {VAR} com escape de barra invertida (portanto, você não pode ter um modelo que use substituições $ VAR em tempo de execução, como um script de shell ou arquivo conf Nginx). Veja minha resposta para uma solução que lida com escapes de barra invertida.
Stuart P. Bentley

4
@beporter Nesse caso, se você quiser passar esse modelo para o envsubst por algum motivo, você gostaria de usar <<"EOF", o que não interpola variáveis ​​(os terminadores citados são como aspas simples dos heredocs).
Stuart P. Bentley

2
Usei-o como: cat template.txt | envsubst
truthadjustr

47

envsubst era novo para mim. Fantástico.

Para o registro, usar um heredoc é uma ótima maneira de modelar um arquivo 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

1
Eu prefiro este melhor do que envsubstcoz ele salvou a minha do adicional apt-get install gettext-basena minha Dockerfile
truthadjustr

O shell como um script semelhante a um modelo, no entanto, sem nenhuma instalação de biblioteca externa nem estresse ao lidar com expressões complicadas.
千 木 郷 18/02

35

Concordo com o uso do sed: é a melhor ferramenta para pesquisar / substituir. Aqui está a minha abordagem:

$ 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

1
Isso requer arquivo temporário para a sequência de substituição, certo? Existe uma maneira de fazer isso sem arquivos temporários?
Vladislav Rastrusny

@FractalizeR: Algumas versões do sed têm uma -iopção (editar arquivos no local) que é semelhante à opção perl . Verifique a página de manual do seu sed .
Chris Johnsen

@FractalizeR Sim, o sed -i substituirá inline. Se você está familiarizado com o Tcl (outra linguagem de script), verifique este tópico: stackoverflow.com/questions/2818130/…
Hai Vu

Eu criei o replace.sed a partir de um arquivo de propriedades com o seguinte comando sed: sed -e 's / ^ / s \ / $ {/ g' -e 's / = /} \ // g' -e 's / $ / \ // g 'the.properties> replace.sed
Jaap D

O código do @hai vu cria um programa sed e passa esse programa usando o sinalizador sed's -f. Se você quisesse, poderia passar cada linha do programa sed para o sed usando os sinalizadores -e. FWIW Eu gosto da idéia de usar o sed para modelagem.
Andrew Allbright 16/03

23

Eu acho que eval funciona muito bem. Ele lida com modelos com quebras de linha, espaços em branco e todos os tipos de itens do bash. Se você tem controle total sobre os modelos, é claro:

$ 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 deve ser usado com cuidado, é claro, pois eval pode executar código arbitrário. Executar isso como raiz está praticamente fora de questão. As aspas no modelo precisam ser escapadas, caso contrário elas serão consumidas eval.

Você também pode usar aqui os documentos, se você preferir cataecho

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

O @plockc provou uma solução que evita o problema de escape das citações do bash:

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

Edit: Removida parte sobre como executar isso como root usando sudo ...

Edit: Adicionado comentário sobre como as aspas precisam ser escapadas, adicionada a solução do plockc à mistura!


Isso remove as aspas que você possui no seu modelo e não substitui as aspas simples, portanto, dependendo do formato do seu modelo, pode levar a erros sutis. Isso provavelmente é aplicável a qualquer método de modelagem baseado em Bash.
Alex B

Os modelos baseados no IMHO Bash são uma loucura, pois você precisa ser um programador de bash para entender o que o seu modelo está fazendo! Mas obrigado pelo comentário!
mogsie

@AlexB: Esta abordagem vai substituir entre aspas simples, como eles são apenas personagens literais dentro da cadeia entre aspas colocando em vez de delimitadores de cordas quando a evaled echo / catcomandos processa-los; tente eval "echo \"'\$HOME'\"".
mklement0

21

Eu tenho uma solução bash como mogsie, mas com heredoc em vez de herestring para permitir que você evite escapar aspas duplas

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

4
Esta solução suporta a expansão de parâmetros Bash no modelo. Os meus favoritos são os parâmetros necessários com ${param:?}e texto de nidificação em torno de parâmetros opcionais. Exemplo: ${DELAY:+<delay>$DELAY</delay>}expande para nada quando DELAY está indefinido e <delay> 17 </delay> quando DELAY = 17.
Eric Bolinger 31/07

2
Oh! E o delimitador EOF pode usar uma sequência dinâmica, como o PID _EOF_$$.
Eric Bolinger

1
@ mklement0 Uma solução alternativa para rastrear novas linhas é usar alguma expansão como, por exemplo, uma variável vazia $trailing_newline, ou usar $NL5e garantir que ela seja expandida como 5 novas linhas.
xebeche 04/12/19

@xebeche: Sim, colocando o que você sugere no final dentrotemplate.txt iria trabalhar, a fim de preservar a novas linhas de fuga.
mklement0

1
Uma solução elegante, mas observe que a substituição do comando removerá as novas linhas finais do arquivo de entrada, embora isso normalmente não seja um problema. Outro caso extremo: devido ao uso de eval, se template.txtcontiver EOFuma linha própria, ele encerrará prematuramente o documento aqui e, portanto, interromperá o comando. (Ponta do chapéu para @xebeche).
usar o seguinte comando

18

Editar 6 de janeiro de 2017

Eu precisava manter aspas duplas no meu arquivo de configuração para que aspas duplas com escape duplo com sed ajude:

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

Não consigo pensar em seguir novas linhas, mas as linhas vazias são mantidas.


Embora seja um tópico antigo, a IMO descobri uma solução mais elegante aqui: 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 os créditos para Grégory Pakosz .


Isso remove aspas duplas da entrada e, se houver várias linhas novas à direita no arquivo de entrada, as substituirá por uma única.
mklement0

2
Eu precisava de duas barras invertidas menos para torná-lo trabalho, ou seja, eval "echo \"$(sed 's/\"/\\"/g' $1)\""
David Bau

Infelizmente, essa abordagem não permite modelar arquivos php (eles contêm $variables).
IStranger 17/04

10

Em vez de reinventar a roda, vá com o envsubst. Pode ser usado em praticamente qualquer cenário, por exemplo, criando arquivos de configuração a partir de variáveis ​​de ambiente em contêineres do docker.

Se no mac, certifique-se de ter o homebrew , vincule-o no 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

Agora basta usá-lo:

# 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

essa sequência de invocação envsubstrealmente funciona.
1128 Alex

Para qualquer outra pessoa olhando, envsubstnão funciona em MacOS, você precisa instalá-lo usando homebrew: brew install gettext.
Matt

9

Uma versão mais longa mas mais robusta da resposta aceita:

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

Isso expande todas as instâncias $VAR ou ${VAR} para seus valores de ambiente (ou, se não estiverem definidos, a sequência vazia).

Ele escapa adequadamente as barras invertidas e aceita um $ com escape da barra invertida para inibir a substituição (ao contrário do envsubst, que, ao que parece , não faz isso ).

Portanto, se seu ambiente é:

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

e seu modelo é:

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

o resultado seria:

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

Se você quiser apenas escapar barras invertidas antes de $ (você pode escrever "C: \ Windows \ System32" em um modelo inalterado), use esta versão ligeiramente 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

8

Eu teria feito dessa maneira, provavelmente menos eficiente, mas mais fácil de ler / manter.

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

10
Você pode fazer isso sem ler linha por linha e com apenas uma chamada sed:sed -e 's/VARONE/NEWVALA/g' -e 's/VARTWO/NEWVALB/g' -e 's/VARTHR/NEWVALC/g' < $TEMPLATE > $OUTPUT
Brandon Bloom

8

Se você deseja usar os modelos Jinja2 , consulte este projeto: j2cli .

Suporta:

  • Modelos de arquivos JSON, INI, YAML e fluxos de entrada
  • Modelo de variáveis ​​de ambiente

5

Tomando a resposta do ZyX usando o bash puro, mas com a nova correspondência de regex de estilo e substituição indireta de parâmetros, ele se torna:

#!/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

5

Se o uso de Perl for uma opção e você estiver satisfeito com expansões básicas apenas em variáveis ​​de ambiente (em oposição a todas as variáveis ​​de shell ), considere a resposta robusta de Stuart P. Bentley .

Esta resposta tem como objetivo fornecer uma solução somente para o bash que, apesar do uso, evaldeve ser segura .

Os objetivos são:

  • Suporte à expansão de referências variáveis ${name}e $namevariáveis.
  • Impedir todas as outras expansões:
    • substituições de comando ( $(...)e sintaxe herdada `...`)
    • substituições aritméticas ( $((...))e sintaxe herdada $[...]).
  • Permita a supressão seletiva da expansão variável prefixando com \( \${name}).
  • Preservar caracteres especiais. na entrada, notadamente "e \instâncias.
  • Permitir entrada via argumentos ou via stdin.

FunçãoexpandVars() :

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' '`(['
}

Exemplos:

$ 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 motivos de desempenho, a função lê a entrada stdin de uma vez na memória, mas é fácil adaptar a função a uma abordagem linha a linha.
  • Também suporta expansões variáveis não básicas${HOME:0:10} , desde que não contenham comandos incorporados ou substituições aritméticas, como${HOME:0:$(echo 10)}
    • Essas substituições incorporadas realmente quebram a função (porque todas $(e `instâncias são escapadas às cegas).
    • Da mesma forma, referências a variáveis ​​malformadas, como ${HOME(fechamento ausente }) BREAK a função.
  • Devido ao manuseio do bash de seqüências de caracteres entre aspas duplas, as barras invertidas são tratadas da seguinte maneira:
    • \$name impede a expansão.
    • Um single \não seguido por $é preservado como está.
    • Se você deseja representar várias \ instâncias adjacentes , deve dobrá-las ; por exemplo:
      • \\-> \- o mesmo que apenas\
      • \\\\ -> \\
    • Caracteres A entrada não deve conter a seguinte (raramente utilizados), que são usados para fins internos: 0x1, 0x2, 0x3.
  • Existe uma preocupação amplamente hipotética de que, se o bash introduzir uma nova sintaxe de expansão, essa função poderá não impedir tais expansões - veja abaixo uma solução que não usa eval.

Se você está procurando uma solução mais restritiva que ofereça suporte apenas a${name} expansões - por exemplo, com chaves obrigatórias , ignorando $namereferências - veja esta minha resposta .


Aqui está uma versão aprimorada da evalsolução somente para bash e livre da resposta aceita :

As melhorias são:

  • Suporte para expansão de referências de ambos ${name}e $namevariáveis.
  • Suporte para \referências de variáveis ​​em escape que não devem ser expandidas.
  • Ao contrário da evalsolução baseada acima,
    • expansões não básicas são ignoradas
    • referências de variáveis ​​malformadas são ignoradas (elas não quebram o 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"

5

Aqui está outra solução pura do bash:

  • está usando heredoc, então:
    • a complexidade não aumenta devido à sintaxe adicionalmente necessária
    • modelo pode incluir código bash
      • que também permite recuar as coisas corretamente. Ver abaixo.
  • não usa eval, então:
    • sem problemas com a renderização de linhas vazias à direita
    • sem problemas com aspas no modelo

$ cat code

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

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

cat_template template | LISTING="$LISTING" bash

$ cat template (com novas linhas à direita e aspas duplas)

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

resultado

<html>
  <head>
  </head>
  <body> 
    <p>"directory listing"
      <pre>
        code
        template
      <pre>
    </p>
  </body>
</html>

4

Aqui está outra solução: gere um script bash com todas as variáveis ​​e o conteúdo do arquivo de modelo, esse script teria a seguinte aparência:

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

EOF                

Se alimentarmos esse script no bash, ele produzirá a saída desejada:

the number is 1
the word is dog

Aqui está como gerar esse script e alimentá-lo no bash:

(
    # Variables
    echo word=dog
    echo i=1

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

Discussão

  • Os parênteses abrem uma subcasca, seu objetivo é agrupar toda a saída gerada
  • Dentro do sub shell, geramos todas as declarações de variáveis
  • Também no sub shell, geramos o catcomando com HEREDOC
  • Finalmente, alimentamos a saída do sub shell para bash e produzimos a saída desejada
  • Se você deseja redirecionar essa saída para um arquivo, substitua a última linha por:

    ) | bash > output.txt

3

Esta página descreve uma resposta com awk

awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt

Isso mantém todas as aspas intactas. Ótimo!
Pepster 27/06

3

Caso perfeito para shtpl . (projeto meu, por isso não é amplamente utilizado e carece de documentação. Mas aqui está a solução que ele oferece de qualquer maneira. Você pode testá-lo.)

Basta executar:

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

O resultado é:

the number is 1
the word is dog

Diverta-se.


1
Se é uma porcaria, é diminuído de qualquer maneira. E eu estou bem com isso. Mas ok, ponto de vista, que não é claramente visível, que é realmente o meu projeto. Vai torná-lo mais visível no futuro. De qualquer forma, obrigado pelo seu comentário e seu tempo.
Zstegi 03/03

Quero acrescentar que eu realmente procurei por casos de uso ontem, onde o shtpl seria uma solução perfeita. Sim, eu estava entediado ... #
227 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 é a função bash pura ajustável ao seu gosto, usada na produção e não deve interferir em nenhuma entrada. Se quebrar - me avise.



0

Aqui está uma função bash que preserva os espaços em branco:

# 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}"
}

0

Aqui está um perlscript modificado com base em algumas das outras respostas:

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

Recursos (com base nas minhas necessidades, mas deve ser fácil de modificar):

  • Ignora expansões de parâmetro com escape (por exemplo, \ $ {VAR}).
  • Suporta expansões de parâmetro no formato $ {VAR}, mas não $ VAR.
  • Substitui $ {VAR} por uma sequência em branco se não houver envar VAR.
  • Suporta apenas az, AZ, 0-9 e caracteres sublinhados no nome (excluindo dígitos na primeira posição).

0

Veja o script python de substituição de variáveis ​​simples aqui: https://github.com/jeckep/vsubst

É muito simples de usar:

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

0

Minha humilde contribuição para esta maravilhosa pergunta.

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"

Isso funciona basicamente usando o subshell para substituir envar, exceto que ele não usa eval e escapa citações simples e duplas explicitamente. Concatena as expressões var em uma única linha sem espaços para não confundir she depois passa o modelo para echo, permitindo shmanipular as substituições var. Ele preserva novas linhas, comentários, etc ... e você pode escapar \${like_this}se não quiser que o var seja interpretado. ${missing_var}será substituído apenas pelo valor vazio.

Muitas das outras respostas aqui são muito boas, mas eu queria algo muito simples e ele não precisa lidar literalmente com todos os casos possíveis dos casos de modelos que tenho atualmente.

Aproveitar!


0

Para acompanhar a resposta do plockc nesta página, aqui está uma versão adequada para traços, para aqueles que desejam evitar basismos.

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.