Passando o valor do pipeline como parâmetro para xargs para uso pelo eco eval

3

Eu tenho um arquivo de texto que estou usando como modelo, parece assim:

Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS

Meu script bash define duas variáveis, HOSTNAME e HOSTADDRESS , lê o arquivo de modelo e, em seguida, faz um eval para expandir $HOSTNAME e $HOSTADDRESS :

HOSTNAME="SH_SQL_0089"
HOSTADDRESS="172.16.3.44"
TEMPLATE='cat template.txt'
MESSAGE='eval echo $TEMPLATE'

O valor resultante de MESSAGE é:

Hostname     : SH_SQL_0089
Host Address : 172.16.3.44

É possível reduzir as últimas duas linhas para algo como:

MESSAGE=$(cat template.txt | eval echo ????)

Eu tentei usar xargs :

MESSAGE=$( cat template.txt | xargs -i bash -c "eval echo {}" )

Mas apenas substitui $HOSTNAME e retira os retornos de carro.

O motivo pelo qual quero fazer isso é que preciso adicionar uma terceira etapa de processamento e canalizar a saída eval 'd para ela. Eu sinto que estou tão perto.

    
por Kev 21.06.2011 / 03:19

4 respostas

2

Primeiro, xargs não pode funcionar aqui porque as substituições seriam executadas em um subprocesso (o bash processa esse xargs lança). Mas apenas variáveis de ambiente são passadas para subprocessos, não para variáveis de shell. Evidentemente, HOSTNAME já estava no ambiente do seu script quando começou, mas HOSTADDRESS não está. Neste ponto, você pode ser tentado a exportar todas as variáveis, mas mesmo assim xargs não é uma boa solução porque sofre de numerosos problemas de cotação - se o seu modelo contém \"' ou se você deseja manter o espaço em branco, torrada.

Agora, olhando para o seu código atual: MESSAGE='eval echo $TEMPLATE' é uma maneira complexa de escrever eval MESSAGE=$TEMPLATE , exceto para problemas de cotação. E você tem citando problemas; por exemplo, você notará que todo o seu espaço em branco foi recolhido. Você já ouviu falar de Bobby Tables , não é? As regras de expansão do shell são bastante complexas, mas há algumas regras que manterão você sensato:

  • Sempre aspas duplas em torno de substituições de variáveis "$foo" e substituições de comandos "$(bar)" . Você pode violar esta regra se entender por que precisa omitir as aspas.
  • Use $(…) em vez de '…' para substituições de comandos. As citações dentro do formulário backquote são arcanas e não portáveis, enquanto que a cotação dentro de $(…) funciona normalmente.
  • Use eval somente se pressionado na mira da arma. Se fizer isso, tenha muito cuidado e torne-o o mais simples possível.

Então, o que pode dar errado com eval MESSAGE="$TEMPLATE" ? Faz o shell avaliar

MESSAGE=Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS

Opa, precisamos de cotações em torno da peça que deve ser o valor de MESSAGE . As citações precisam passar para eval , então elas precisam passar literalmente pelo primeiro estágio da expansão do shell: eval MESSAGE="\"$TEMPLATE\"" .

MESSAGE="Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS"

Melhor, mas agora você tem exatamente o padrão de injeção do Bobby Tables - e se o modelo contiver uma cotação? Então você precisa escapar das cotações. Os quatro caracteres que têm um significado especial entre aspas duplas são \"$' e você deseja que $ retenha seu significado, portanto, adicione uma barra invertida antes dos outros três.

TEMPLATE=$(sed -e 's/[\"']/\&/g' <template.txt)
eval MESSAGE="\"$TEMPLATE\""

Agora o shell avaliará

MESSAGE="Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS
Name         : Bobby \"drop\" O'Tables"

e tudo está bem.

Observe que a citação correta aqui é para proteger contra problemas de análise acidental; quem controla o modelo ainda tem acesso ao shell (com $(hello) ).

Se você quisesse o modelo incluído no seu script de shell, isso seria feito naturalmente com um heredoc .

MESSAGE=$(cat <<EOF)
Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS
EOF

Mas com um template externo você precisaria fazer duas etapas de avaliação, portanto use eval , e a análise de bash é bastante bugs quando se trata de coisas funky como heredocs dentro de eval . Há certamente uma maneira que funciona, pelo menos com o bash 4, mas eu não recomendo arriscar.

    
por 21.06.2011 / 10:58
2

Você quer que o shell aplique substituições de variáveis, o que significa que o texto do modelo deve ser lido pelo próprio shell como parte de um comando, mas o modelo também é multi-line texto por isso é um pouco complicado para entrar no processamento orientado por linha normalmente feito pelo UNIX comandos.

Uma abordagem seria usar bash aqui-documentos (todos os comandos mostrados conforme digitado no prompt do shell):

  1. crie o arquivo de modelo:

    $ cat template.txt
    Hi, $NAME
    
    Welcome to $PLACE
    
  2. exportar as variáveis que devem ser substituídas no modelo texto:

    $ export NAME=Bob
    $ export PLACE='unix&linux'
    
  3. crie uma variável shell que contenha uma única "nova linha" \n personagem; no bash a maneira mais fácil é simplesmente abrir uma string, digite uma nova linha e fechá-lo:

    $ newline='
    > '
    
  4. finalmente, chame bash para fazer a substituição:

    $ bash -c "cat <<__EOF__${newline}$(cat template.txt)${newline}__EOF__"
    Hi, Bob
    
    Welcome to unix&linux
    

Por que isso funciona? Vamos desconstruí-lo do fundo: você está pedindo bash para executar um comando (pela opção -c ), mas a substituição de variável ${newline} e a expansão de comando $(cat ...) ocorre no shell pai antes que bash -c ... seja realmente executado. Então, o resultado é que bash -c vê um comando string que incorpora novas linhas e todo o texto do modelo. É como se você digitou o seguinte em um prompt bash:

cat <<__EOF__
...contents of template.txt...
__EOF__

Observe também que as variáveis de substituição precisam ser exportadas, uma vez que as variáveis são atribuídas no shell pai, mas é o "filho" bash que está fazendo a substituição.

Nota , no entanto, que esta abordagem pode facilmente levar a citações: já que o texto do template é interpretado pelo shell bash, backtics são expandidos, o que pode resultar em comandos sendo executados em seu sistema.

    
por 21.06.2011 / 11:16
0

Você pode até (dobrar) eval uma sequência de aspas simples incorporada para fins de experiência:

export HOSTNAME HOSTADDRESS
(
NL='
'
DELIM=$'7'
esc="'\''"
export IFS=""
HOSTNAME="SH_SQL_00'8'9"
HOSTADDRESS='172.16.3.44 Bobby "drop" O'\''Tables ${PWD} $(ls)'
printf '%s\n' 'Hostname : $HOSTNAME' 'Host Address : $HOSTADDRESS' > template.txt
TEMPLATE="$(<template.txt)"
TEMPLATE="${TEMPLATE//\'/${esc}}"       # escape every single quote: ' --> '\'' (which later will become ''\''' by '${TEMPLATE}')
TEMPLATE="${TEMPLATE//${NL}/${DELIM}}"  # replace every newline char with a delim char
evalstr="echo '${TEMPLATE}'"
#printf '\n%s\n\n' "${evalstr}" | LC_ALL=C vis -fotc
set -xv
eval eval "${evalstr}"
) | LC_ALL=C tr '7' '\n' | nl
    
por 21.06.2011 / 15:14
-3
MESSAGE=$(eval $(cat template.txt))
    
por 21.06.2011 / 09:18

Tags