Estou trabalhando em um script de shell que constrói um comando complexo a partir de variáveis, por exemplo, assim (com uma técnica que aprendi nas Perguntas frequentes do Bash ):
#!/bin/bash
SOME_ARG="abc"
ANOTHER_ARG="def"
some_complex_command \
${SOME_ARG:+--do-something "$SOME_ARG"} \
${ANOTHER_ARG:+--with "$ANOTHER_ARG"}
Este script adiciona dinamicamente os parâmetros --do-something "$SOME_ARG"
e --with "$ANOTHER_ARG"
para some_complex_command
se estas variáveis são definidas. Até agora, isso está funcionando bem.
Mas agora também quero poder imprimir ou registrar o comando enquanto o estiver executando, por exemplo, quando meu script for executado no modo de depuração. Portanto, quando meu script é executado some_complex_command --do-something abc --with def
, eu também quero ter esse comando dentro de uma variável para poder, por exemplo, registrá-lo no syslog.
As Perguntas frequentes sobre o Bash demonstram uma técnica para usar a DEBUG
interceptação e a $BASH_COMMAND
variável (por exemplo, para fins de depuração) para esse fim. Eu tentei isso com o seguinte código:
#!/bin/bash
ARG="test string"
trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"
echo "Command was: ${COMMAND}"
Isso funciona, mas não expande as variáveis no comando:
host ~ # ./test.sh
test string
Command was: echo "$ARG"
Eu acho que eu tenho que usar eval para expandir echo "$ARG"
a echo test string
(pelo menos eu não encontrei uma maneira sem eval
ainda). O seguinte funciona:
eval echo "Command was: ${COMMAND}"
Produz a seguinte saída:
host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string
Mas não tenho certeza se posso usar eval
com segurança assim. Tentei, sem sucesso, explorar algumas coisas:
#!/bin/bash
ARG="test string; touch /x"
DANGER='$(touch /y; cat /etc/shadow)'
trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG" $DANGER
echo "Command was: ${COMMAND}"
eval echo "Command was: ${COMMAND}"
Parece lidar bem com isso, mas estou curioso para saber se alguém viu um problema que eu perdi.
Respostas:
Uma possibilidade é criar uma função de wrapper que, ao mesmo tempo, imprima o comando e execute-o, da seguinte maneira:
Para que no seu script você possa fazer:
Não há necessidade de mexer na armadilha. A desvantagem é que ela adiciona algumas
debug
palavras-chave em todo o seu código (mas isso deve ser bom, é comum ter essas coisas, como declarações, etc.).Você pode até adicionar uma variável global
DEBUG
e modificar adebug
função da seguinte maneira:Em seguida, você pode chamar seu script como:
ou
ou apenas
dependendo se você deseja ter as informações de depuração ou não.
Eu capitalizei a
DEBUG
variável porque ela deveria ser tratada como uma variável de ambiente.DEBUG
é um nome trivial e comum, portanto, isso pode colidir com outros comandos. Talvez ligueGNIOURF_DEBUG
ouMARTIN_VON_WITTICH_DEBUG
ouUNICORN_DEBUG
se você gosta de unicórnios (e provavelmente também gosta de pôneis).Nota. Na
debug
função, formatei cuidadosamente cada argumento com,printf '%q'
para que a saída seja corretamente escapada e citada, de modo a ser literalmente reutilizável com uma cópia e colagem direta. Também mostrará exatamente o que o shell viu, pois você poderá descobrir cada argumento (no caso de espaços ou outros símbolos engraçados). Esta função também utiliza atribuição direta com a-v
opção deprintf
para evitar subcamadas desnecessárias.fonte
&
. Eu tive que movê-los para a função para fazê-la funcionar - não muito agradável, mas acho que não há uma maneira melhor. Mas não maiseval
, então eu tenho que ir para mim, o que é bom :)eval "$BASH_COMMAND"
executa o comandoprintf '%s\n' "$BASH_COMMAND"
imprime o comando exato especificado, além de uma nova linha.Se o comando contiver variáveis (por exemplo, se for algo parecido
cat "$foo"
), a impressão do comando imprimirá o texto da variável. É impossível imprimir o valor da variável sem executar o comando - pense em comandos comovariable=$(some_function) other_variable=$variable
.A maneira mais simples de obter um rastreio ao executar um script de shell é definir a
xtrace
opção de shell executando o scriptbash -x /path/to/script
ou invocandoset -x
dentro do shell. O rastreio é impresso com erro padrão.fonte
xtrace
, mas isso não me dá muito controle. Eu tentei "set -x; ...; set + x", mas: 1) o comando "set + x" para desativar o xtrace também é impresso 2) Não consigo prefixar a saída, por exemplo, com um carimbo de data / hora 3) Posso registre-o no syslog.BASH_COMMAND
que contenha o comando expandido, porque, em algum momento, ele terá que fazer uma expansão variável no comando de qualquer maneira quando o executar :)