Ist es sicher, $ BASH_COMMAND zu bewerten?


11

Ich arbeite an einem Shell-Skript, das einen komplexen Befehl aus Variablen erstellt, z. B. wie folgt (mit einer Technik, die ich aus den Bash-FAQ gelernt habe ):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

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

Dieses Skript fügt die Parameter dynamisch hinzu --do-something "$SOME_ARG"und gibt --with "$ANOTHER_ARG"an, some_complex_commandob diese Variablen definiert sind. Bisher funktioniert das gut.

Jetzt möchte ich den Befehl aber auch drucken oder protokollieren können, wenn ich ihn ausführe, beispielsweise wenn mein Skript in einem Debug-Modus ausgeführt wird. Wenn mein Skript ausgeführt wird some_complex_command --do-something abc --with def, möchte ich diesen Befehl auch in einer Variablen haben, damit ich ihn z. B. im Syslog protokollieren kann.

In den Bash-FAQ wird eine Technik demonstriert, mit der der DEBUGTrap und die $BASH_COMMANDVariable (z. B. für Debugging-Zwecke) für diesen Zweck verwendet werden. Ich habe das mit folgendem Code versucht:

#!/bin/bash

ARG="test string"

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

echo "Command was: ${COMMAND}"

Dies funktioniert, erweitert jedoch nicht die Variablen im Befehl:

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

Ich denke , ich habe eval verwenden zu erweitern , echo "$ARG"um echo test string(zumindest habe ich nicht einen Weg ohne gefunden evalnoch) nicht . Folgendes funktioniert:

eval echo "Command was: ${COMMAND}"

Es wird die folgende Ausgabe erzeugt:

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

Aber ich bin mir nicht sicher, ob ich so evalsicher arbeiten kann . Ich habe erfolglos versucht, einige Dinge auszunutzen:

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

Es scheint gut damit umzugehen, aber ich bin neugierig, ob jemand anderes ein Problem sieht, das ich verpasst habe.


Dies ist, was ich seit Jahren in einem Sudo-Wrapper verwende, ich habe nie ein Problem bemerkt.
w00t

Antworten:


7

Eine Möglichkeit besteht darin, eine Wrapper-Funktion zu erstellen, die gleichzeitig den Befehl druckt und wie folgt ausführt:

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

Damit Sie in Ihrem Skript Folgendes tun können:

debug echo "$ARG"

Sie müssen nicht an der Falle herumspielen. Der Nachteil ist, dass einige debugSchlüsselwörter im gesamten Code hinzugefügt werden (aber das sollte in Ordnung sein, es ist üblich, solche Dinge wie Asserts usw. zu haben).

Sie können sogar eine globale Variable hinzufügen DEBUGund die debugFunktion folgendermaßen ändern :

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

Dann können Sie Ihr Skript wie folgt aufrufen:

$ DEBUG=yes ./myscript

oder

$ DEBUG= ./myscript

oder nur

$ ./myscript

abhängig davon, ob Sie die Debug-Informationen haben möchten oder nicht.

Ich habe die DEBUGVariable groß geschrieben , weil sie als Umgebungsvariable behandelt werden sollte. DEBUGist ein trivialer und gebräuchlicher Name, daher kann dies mit anderen Befehlen kollidieren. Nennen Sie es vielleicht GNIOURF_DEBUGoder MARTIN_VON_WITTICH_DEBUGoder UNICORN_DEBUGwenn Sie Einhörner mögen (und dann mögen Sie wahrscheinlich auch Ponys).

Hinweis. In der debugFunktion habe ich jedes Argument sorgfältig mit formatiert, printf '%q'damit die Ausgabe korrekt maskiert und in Anführungszeichen gesetzt wird, damit sie wörtlich mit direktem Kopieren und Einfügen wiederverwendet werden kann. Es zeigt Ihnen auch genau, was die Shell gesehen hat, da Sie jedes Argument herausfinden können (bei Leerzeichen oder anderen lustigen Symbolen). Diese Funktion verwendet auch die direkte Zuordnung mit dem -vSchalter von printf, um unnötige Unterschalen zu vermeiden.


1
Die Funktion funktioniert hervorragend, aber leider enthält mein Befehl Umleitungen und den Steueroperator "Im Hintergrund ausführen" &. Ich musste diese auf die Funktion verschieben, damit sie funktioniert - nicht sehr schön, aber ich glaube nicht, dass es einen besseren Weg gibt. Aber nicht mehr eval, also habe ich das für mich, was schön ist :)
Martin von Wittich

5

eval "$BASH_COMMAND" führt den Befehl aus.

printf '%s\n' "$BASH_COMMAND" Gibt den genau angegebenen Befehl sowie eine neue Zeile aus.

Wenn der Befehl Variablen enthält (dh wenn es sich um etwas Ähnliches handelt cat "$foo"), wird beim Ausdrucken des Befehls der Variablentext ausgedruckt. Es ist unmöglich, den Wert der Variablen zu drucken, ohne den Befehl auszuführen - denken Sie an Befehle wie variable=$(some_function) other_variable=$variable.

Der einfachste Weg, einen Trace von der Ausführung eines Shell-Skripts zu erhalten, besteht darin, die xtraceShell-Option festzulegen, indem Sie das Skript als ausführen bash -x /path/to/scriptoder set -xinnerhalb der Shell aufrufen . Die Ablaufverfolgung wird auf Standardfehler gedruckt.


1
Ich weiß davon xtrace, aber das gibt mir nicht viel Kontrolle. Ich habe "set -x; ...; set + x" versucht, aber: 1) Der Befehl "set + x" zum Deaktivieren von xtrace wird ebenfalls gedruckt. 2) Ich kann der Ausgabe kein Präfix voranstellen, z. B. mit einem Zeitstempel. 3) Ich kann Nicht in Syslog protokollieren.
Martin von Wittich

2
"Es ist unmöglich, den Wert der Variablen zu drucken, ohne den Befehl auszuführen" - ein gutes Beispiel, ich hatte einen solchen Fall nicht in Betracht gezogen. Es wäre jedoch schön, wenn bash eine zusätzliche Variable daneben hätte BASH_COMMAND, die den erweiterten Befehl enthält, da der Befehl irgendwann ohnehin eine Variablenerweiterung durchführen muss, wenn er ausgeführt wird :)
Martin von Wittich
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.