Est-il sûr d'évaluer $ BASH_COMMAND?


11

Je travaille sur un script shell qui construit une commande complexe à partir de variables, par exemple comme ceci (avec une technique que j'ai apprise dans la FAQ Bash ):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

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

Ce script ajoute dynamiquement les paramètres --do-something "$SOME_ARG"et --with "$ANOTHER_ARG"à some_complex_commandsi ces variables sont définies. Jusqu'à présent, cela fonctionne bien.

Mais maintenant, je veux également pouvoir imprimer ou enregistrer la commande lorsque je l'exécute, par exemple lorsque mon script est exécuté en mode débogage. Donc, lorsque mon script s'exécute some_complex_command --do-something abc --with def, je veux également avoir cette commande dans une variable afin que je puisse par exemple la connecter au syslog.

La FAQ Bash montre une technique pour utiliser l' DEBUGinterruption et la $BASH_COMMANDvariable (par exemple à des fins de débogage) à cette fin. J'ai essayé cela avec le code suivant:

#!/bin/bash

ARG="test string"

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"

echo "Command was: ${COMMAND}"

Cela fonctionne, mais ne développe pas les variables dans la commande:

host ~ # ./test.sh
test string
Command was: echo "$ARG"

Je suppose que je dois utiliser eval pour étendre echo "$ARG"à echo test string(au moins je ne l' ai pas trouvé un moyen sans evalencore). Ce qui suit fonctionne:

eval echo "Command was: ${COMMAND}"

Il produit la sortie suivante:

host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string

Mais je ne suis pas vraiment certain de pouvoir utiliser en evaltoute sécurité comme ça. J'ai essayé en vain d'exploiter certaines choses:

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

Il semble bien gérer cela, mais je suis curieux de savoir si quelqu'un d'autre voit un problème que j'ai manqué.


C'est ce que j'utilise depuis des années dans un emballage sudo, je n'ai jamais remarqué de problème.
w00t

Réponses:


7

Une possibilité consiste à créer une fonction wrapper qui imprimera en même temps la commande et l'exécutera, comme suit:

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

Pour que dans votre script vous puissiez faire:

debug echo "$ARG"

Pas besoin de tripoter le piège. L'inconvénient est qu'il ajoute des debugmots clés partout dans votre code (mais cela devrait être bien, il est courant d'avoir de telles choses, comme des assertions, etc.).

Vous pouvez même ajouter une variable globale DEBUGet modifier la debugfonction comme suit:

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

Ensuite, vous pouvez appeler votre script comme:

$ DEBUG=yes ./myscript

ou

$ DEBUG= ./myscript

ou juste

$ ./myscript

selon que vous souhaitez ou non avoir les informations de débogage.

J'ai capitalisé la DEBUGvariable car elle doit être traitée comme une variable d'environnement. DEBUGest un nom banal et commun, donc cela pourrait entrer en conflit avec d'autres commandes. Appeler peut - être GNIOURF_DEBUGou MARTIN_VON_WITTICH_DEBUGou UNICORN_DEBUGsi vous aimez licornes (et alors vous avez probablement comme poneys aussi).

Remarque. Dans la debugfonction, j'ai soigneusement formaté chaque argument avec printf '%q'pour que la sortie soit correctement échappée et citée afin d'être réutilisable textuellement avec un copier-coller direct. Il vous montrera également exactement ce que le shell a vu, car vous pourrez comprendre chaque argument (en cas d'espaces ou d'autres symboles amusants). Cette fonction utilise également l'affectation directe avec le -vcommutateur de printfafin d'éviter les sous-coquilles inutiles.


1
La fonction fonctionne très bien, mais malheureusement ma commande contient des redirections et l'opérateur de contrôle "excute en arrière-plan" &. J'ai dû les déplacer vers la fonction pour qu'elle fonctionne - pas très bien, mais je ne pense pas qu'il y ait une meilleure façon. Mais pas plus eval, donc j'ai tout pour moi, ce qui est bien :)
Martin von Wittich

5

eval "$BASH_COMMAND" exécute la commande.

printf '%s\n' "$BASH_COMMAND" imprime la commande spécifiée exacte, plus une nouvelle ligne.

Si la commande contient des variables (c'est-à-dire s'il s'agit de quelque chose comme ça cat "$foo"), l'impression de la commande imprime le texte de la variable. Il est impossible d'imprimer la valeur de la variable sans exécuter la commande - pensez à des commandes comme variable=$(some_function) other_variable=$variable.

La manière la plus simple d'obtenir une trace lors de l'exécution d'un script shell est de définir l' xtraceoption shell en exécutant le script as bash -x /path/to/scriptou en l'appelant set -xà l'intérieur du shell. La trace est imprimée sur l'erreur standard.


1
Je sais xtrace, mais cela ne me donne pas beaucoup de contrôle. J'ai essayé "set -x; ...; set + x", mais: 1) la commande "set + x" pour désactiver xtrace est également imprimée 2) je ne peux pas préfixer la sortie par exemple avec un horodatage 3) je peux ne le connectez pas à syslog.
Martin von Wittich

2
"Il est impossible d'imprimer la valeur de la variable sans exécuter la commande" - bon exemple, je n'avais pas envisagé un tel cas. Ce serait bien si bash avait une variable supplémentaire à côté de BASH_COMMANDcelle qui contient la commande développée, car à un moment donné, il doit de toute façon faire une expansion des variables lors de son exécution :)
Martin von Wittich
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.