É seguro avaliar $ BASH_COMMAND?

11

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_commandse 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 DEBUGinterceptação e a $BASH_COMMANDvariá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 evalainda). 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 evalcom 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.

Martin von Wittich
fonte
É isso que venho usando há anos em um invólucro sudo, nunca notei um problema.
w00t 23/05

Respostas:

7

Uma possibilidade é criar uma função de wrapper que, ao mesmo tempo, imprima o comando e execute-o, da seguinte maneira:

debug() {
    # This function prints (to stdout) its arguments and executes them
    local args=() idx=0 IFS=' ' c
    for c; do printf -v args[idx++] '%q' "$c"; done
    printf "%s\n" "${args[*]}"
    # Execute!
    "$@"
}

Para que no seu script você possa fazer:

debug echo "$ARG"

Não há necessidade de mexer na armadilha. A desvantagem é que ela adiciona algumas debugpalavras-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 DEBUGe modificar a debugfunção da seguinte maneira:

debug() {
    # This function prints (to stdout) its arguments if DEBUG is non-null
    # and executes them
    if [[ $DEBUG ]]; then
        local args=() idx=0 IFS=' ' c
        for c; do printf -v args[idx++] '%q' "$c"; done
        printf "%s\n" "${args[*]}"
    fi
    # Execute!
    "$@"
}

Em seguida, você pode chamar seu script como:

$ DEBUG=yes ./myscript

ou

$ DEBUG= ./myscript

ou apenas

$ ./myscript

dependendo se você deseja ter as informações de depuração ou não.

Eu capitalizei a DEBUGvariá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 ligue GNIOURF_DEBUGou MARTIN_VON_WITTICH_DEBUGou UNICORN_DEBUGse você gosta de unicórnios (e provavelmente também gosta de pôneis).

Nota. Na debugfunçã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 -vopção de printfpara evitar subcamadas desnecessárias.

gniourf_gniourf
fonte
1
A função funciona muito bem, mas infelizmente meu comando contém redirecionamentos e o operador de controle "excute in background" &. 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 mais eval, então eu tenho que ir para mim, o que é bom :)
Martin von Wittich
5

eval "$BASH_COMMAND" executa o comando

printf '%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 como variable=$(some_function) other_variable=$variable.

A maneira mais simples de obter um rastreio ao executar um script de shell é definir a xtraceopção de shell executando o script bash -x /path/to/scriptou invocando set -xdentro do shell. O rastreio é impresso com erro padrão.

Gilles 'SO- parar de ser mau'
fonte
1
Eu sei 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.
Martin von Wittich
2
"É impossível imprimir o valor da variável sem executar o comando" - bom exemplo, eu não havia considerado esse caso. Seria bom, se o bash tivesse uma variável adicional ao lado BASH_COMMANDque 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 :)
Martin von Wittich