bash: é seguro eval $ BASH_COMMAND?

10

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 com o FAQ do Bash ):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

some_complex_command \
  ${SOME_ARG:+--do-something "$SOME_ARG"} \
  ${ANOTHER_ARG:+--with "$ANOTHER_ARG"}

Esse script adiciona dinamicamente os parâmetros --do-something "$SOME_ARG" e --with "$ANOTHER_ARG" a some_complex_command se essas variáveis forem definidas. Até agora isso está funcionando bem.

Mas agora eu também quero poder imprimir ou registrar o comando quando eu o estiver executando, por exemplo, quando meu script for executado em um modo de depuração. Então, quando meu script executar some_complex_command --do-something abc --with def , também quero ter esse comando dentro de uma variável para que eu possa, por exemplo, registre-o no syslog.

A FAQ do Bash demonstra uma técnica para usar a DEBUG trap e a variável $BASH_COMMAND (por exemplo, para fins de depuração) para essa finalidade. 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 tenho que usar eval para expandir echo "$ARG" para echo test string (pelo menos eu não encontrei um caminho 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. Eu 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 que lida bem com isso, mas estou curioso para saber se alguém viu um problema que eu perdi.

    
por Martin von Wittich 20.05.2014 / 17:21

2 respostas

6

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

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 com a armadilha. A desvantagem é que ele adiciona algumas debug de palavras-chave em todo o seu código (mas isso deve ser bom, é comum ter essas coisas, como assertes, etc.).

Você pode até mesmo adicionar uma variável global DEBUG e modificar a função debug assim:

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!
    "$@"
}

Depois, você pode chamar seu script como:

$ DEBUG=yes ./myscript

ou

$ DEBUG= ./myscript

ou apenas

$ ./myscript

dependendo se você quer ter a informação de depuração ou não.

Eu capitalizei a variável DEBUG porque ela deve ser tratada como uma variável de ambiente. DEBUG é um nome comum e trivial, portanto, isso pode colidir com outros comandos. Talvez chamá-lo de GNIOURF_DEBUG ou MARTIN_VON_WITTICH_DEBUG ou UNICORN_DEBUG se você gosta de unicórnios (e provavelmente também gosta de pôneis).

Nota. Na função debug , eu cuidadosamente formatei cada argumento com printf '%q' para que a saída fosse corretamente excluída e citada de modo a ser reutilizável textualmente com uma cópia direta e cole . Ele também mostrará exatamente o que a casca viu, pois você poderá descobrir cada argumento (no caso de espaços ou outros símbolos engraçados). Essa função também usa a atribuição direta com a opção -v de printf para evitar subconsultas desnecessárias.

    
por 20.05.2014 / 18:10
3

eval "$BASH_COMMAND" executa o comando.

printf '%s\n' "$BASH_COMMAND" imprime o comando especificado, além de uma nova linha.

Se o comando contiver variáveis (ou seja, se for algo como 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 de execução de um script de shell é definir a opção xtrace shell executando o script como bash -x /path/to/script ou invocando set -x dentro do shell. O traço é impresso para erro padrão.

    
por 21.05.2014 / 09:23

Tags