È sicuro valutare $ BASH_COMMAND?


11

Sto lavorando a uno script di shell che costruisce un comando complesso da variabili, ad esempio in questo modo (con una tecnica che ho imparato dalle FAQ di Bash ):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

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

Questo script aggiunge in modo dinamico i parametri --do-something "$SOME_ARG"e --with "$ANOTHER_ARG"per some_complex_commandse vengono definite queste variabili. Finora funziona bene.

Ma ora voglio anche essere in grado di stampare o registrare il comando quando lo eseguo, ad esempio quando il mio script viene eseguito in modalità debug. Quindi, quando il mio script viene eseguito some_complex_command --do-something abc --with def, voglio anche avere questo comando all'interno di una variabile in modo da poterlo ad esempio accedere al syslog.

Le FAQ di Bash mostrano una tecnica per utilizzare la DEBUGtrap e la $BASH_COMMANDvariabile (ad esempio per scopi di debug) a questo scopo. L'ho provato con il seguente codice:

#!/bin/bash

ARG="test string"

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

echo "Command was: ${COMMAND}"

Funziona, ma non espande le variabili nel comando:

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

Credo che devo usare eval per espandere echo "$ARG"a echo test string(almeno io non ho trovato un modo senza evalancora). Il seguente funziona:

eval echo "Command was: ${COMMAND}"

Produce il seguente output:

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

Ma non sono davvero sicuro di poter usare in evalsicurezza in questo modo. Ho tentato senza successo di sfruttare alcune cose:

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

Sembra gestirlo bene, ma sono curioso di sapere se qualcun altro vede un problema che mi è sfuggito.


Questo è quello che uso da anni in un wrapper sudo, non ho mai notato un problema.
wt

Risposte:


7

Una possibilità è quella di creare una funzione wrapper che stamperà contemporaneamente il comando ed eseguirlo, come segue:

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

In modo che nella tua sceneggiatura puoi fare:

debug echo "$ARG"

Non c'è bisogno di giocherellare con la trappola. Lo svantaggio è che aggiunge alcune debugparole chiave in tutto il codice (ma dovrebbe andare bene, è comune avere cose del genere, come asserzioni, ecc.).

Puoi anche aggiungere una variabile globale DEBUGe modificare la debugfunzione in questo modo:

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

Quindi puoi chiamare il tuo script come:

$ DEBUG=yes ./myscript

o

$ DEBUG= ./myscript

o solo

$ ./myscript

a seconda che tu abbia o meno le informazioni di debug.

Ho capitalizzato la DEBUGvariabile perché dovrebbe essere trattata come una variabile d'ambiente. DEBUGè un nome banale e comune, quindi potrebbe scontrarsi con altri comandi. Magari chiamalo GNIOURF_DEBUGo MARTIN_VON_WITTICH_DEBUGo UNICORN_DEBUGse ti piacciono gli unicorni (e quindi probabilmente ti piacciono anche i pony).

Nota. Nella debugfunzione, ho accuratamente formattato ogni argomento in printf '%q'modo che l'output sia correttamente evitato e citato in modo da essere riutilizzabile alla lettera con una copia e incolla diretta. Ti mostrerà anche esattamente ciò che la shell ha visto mentre sarai in grado di capire ogni argomento (in caso di spazi o altri simboli divertenti). Questa funzione utilizza anche l'assegnazione diretta con l' -vinterruttore di printfmodo da evitare sottotitoli non necessari.


1
La funzione funziona benissimo, ma purtroppo il mio comando contiene reindirizzamenti e l'operatore di controllo "Excute in background" &. Ho dovuto spostare quelli nella funzione per farlo funzionare - non molto bello, ma non credo che ci sia un modo migliore. Ma non di più eval, quindi ce l'ho per me, il che è carino :)
Martin von Wittich,

5

eval "$BASH_COMMAND" esegue il comando.

printf '%s\n' "$BASH_COMMAND" stampa l'esatto comando specificato, più una nuova riga.

Se il comando contiene variabili (ovvero se è qualcosa di simile cat "$foo"), la stampa del comando stampa il testo della variabile. È impossibile stampare il valore della variabile senza eseguire il comando - pensa a comandi come variable=$(some_function) other_variable=$variable.

Il modo più semplice per ottenere una traccia dall'esecuzione di uno script shell è impostare l' xtraceopzione shell eseguendo lo script come bash -x /path/to/scripto invocando set -xall'interno della shell. La traccia viene stampata sull'errore standard.


1
Lo so xtrace, ma questo non mi dà molto controllo. Ho provato "set -x; ...; set + x", ma: 1) viene stampato anche il comando "set + x" per disabilitare xtrace 2) Non riesco a precedere l'output ad es. Con un timestamp 3) Posso non registrarlo su syslog.
Martin von Wittich,

2
"È impossibile stampare il valore della variabile senza eseguire il comando" - buon esempio, non avevo considerato un caso del genere. Sarebbe bello se Bash avesse accanto una variabile aggiuntiva BASH_COMMANDche contiene il comando espanso, perché ad un certo punto deve comunque eseguire l'espansione variabile sul comando quando lo sta eseguendo :)
Martin von Wittich
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.