Wofür eignet sich die Variable $ BASH_COMMAND?

25

Laut Bash-HandbuchBASH_COMMAND enthält die Umgebungsvariable

Der Befehl, der gerade ausgeführt wird oder gerade ausgeführt wird, es sei denn, die Shell führt einen Befehl als Ergebnis eines Traps aus. In diesem Fall handelt es sich um den Befehl, der zum Zeitpunkt des Traps ausgeführt wird.

Wenn ich den Fall der Trap-Ecke beiseite lasse, bedeutet dies, dass die Variable BASH_COMMANDdiesen Befehl enthält, wenn ich einen Befehl ausführe . Es ist nicht absolut klar, ob diese Variable nach der Befehlsausführung nicht gesetzt ist (dh nur verfügbar ist, während der Befehl ausgeführt wird, aber nicht danach), obwohl man argumentieren könnte, dass es sich um "den Befehl handelt, der gerade ausgeführt wird oder gerade ausgeführt wird". , es ist nicht der Befehl , der gerade ausgeführt wurde.

Aber lasst uns prüfen:

$ set | grep BASH_COMMAND=
$ 

Leeren. Ich hätte erwartet, zu sehen BASH_COMMAND='set | grep BASH_COMMAND='oder vielleicht nur BASH_COMMAND='set', aber leer hat mich überrascht.

Versuchen wir etwas anderes:

$ echo $BASH_COMMAND
echo $BASH_COMMAND
$ 

Na das macht Sinn. Ich führe den Befehl aus echo $BASH_COMMANDund so BASH_COMMANDenthält die Variable die Zeichenfolge echo $BASH_COMMAND. Warum hat es diesmal funktioniert, aber nicht vorher?

Lass uns das setDing nochmal machen:

$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Also warte. Es wurde gesetzt, als ich diesen echoBefehl ausführte , und es wurde danach nicht aufgehoben. Aber als ich setnochmal ausführte , BASH_COMMAND wurde nicht auf den setBefehl gesetzt. Egal wie oft ich den setBefehl hier ausführe , das Ergebnis bleibt gleich. Ist die Variable also beim Ausführen gesetzt echo, aber nicht beim Ausführen set? Wir werden sehen.

$ echo Hello AskUbuntu
Hello AskUbuntu
$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Was? Also wurde die Variable gesetzt, als ich ausgeführt habe echo $BASH_COMMAND, aber nicht, als ich ausgeführt habe echo Hello AskUbuntu? Wo ist jetzt der Unterschied? Wird die Variable nur gesetzt, wenn der aktuelle Befehl selbst die Shell zwingt, die Variable auszuwerten? Versuchen wir etwas anderes. Vielleicht ein externes Kommando, diesmal kein eingebauter Bash.

$ /bin/echo $BASH_COMMAND
/bin/echo $BASH_COMMAND
$ set | grep BASH_COMMAND=
BASH_COMMAND='/bin/echo $BASH_COMMAND'
$

Hmm, ok ... wieder wurde die Variable gesetzt. Ist meine derzeitige Vermutung also richtig? Wird die Variable nur gesetzt, wenn sie ausgewertet werden muss? Warum? Warum? Aus Performancegründen? Versuchen wir es noch einmal. Wir werden versuchen, nach $BASH_COMMANDin einer Datei zu suchen , und da $BASH_COMMANDsollte dann ein grepBefehl enthalten sein , grepsollte nach diesem grepBefehl gesucht werden (dh nach sich selbst). Machen wir also eine entsprechende Datei:

$ echo -e "1 foo\n2 grep\n3 bar\n4 grep \$BASH_COMMAND tmp" > tmp
$ grep $BASH_COMMAND tmp
grep: $BASH_COMMAND: No such file or directory
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
$ set | grep BASH_COMMAND=
BASH_COMMAND='grep --color=auto $BASH_COMMAND tmp'
$

Ok, interessant. Der Befehl grep $BASH_COMMAND tmpwurde erweitert auf grep grep $BASH_COMMAND tmp tmp(die Variable wird natürlich nur einmal erweitert), und so habe ich grepeinmal in einer Datei, $BASH_COMMANDdie nicht existiert, und zweimal in der Datei nachgegriffen tmp.

F1: Ist meine derzeitige Annahme richtig, dass:

  • BASH_COMMANDwird nur gesetzt, wenn ein Befehl versucht, ihn tatsächlich auszuwerten; und
  • Sie wird nach der Ausführung eines Befehls nicht deaktiviert, auch wenn die Beschreibung uns zu der Annahme verleitet.

Frage 2: Wenn ja, warum? Performance? Wenn nein, wie kann sonst das Verhalten in der obigen Befehlssequenz erklärt werden?

F3: Gibt es ein Szenario, in dem diese Variable tatsächlich sinnvoll verwendet werden könnte? Eigentlich habe ich versucht, $PROMPT_COMMANDden ausgeführten Befehl damit zu analysieren (und davon abhängig einige Dinge zu tun), aber ich kann nicht, weil ich, sobald $PROMPT_COMMANDich einen Befehl ausführe $BASH_COMMAND, der die Variable , die Variable, ansieht Ruft Sätze auf diesen Befehl ab. Auch wenn ich das MYVARIABLE=$BASH_COMMANDgleich am Anfang mache $PROMPT_COMMAND, dann MYVARIABLEenthält der String MYVARIABLE=$BASH_COMMAND, weil eine Zuweisung auch ein Befehl ist. (Bei dieser Frage geht es nicht darum, wie ich den aktuellen Befehl innerhalb einer $PROMPT_COMMANDAusführung erhalten kann. Ich weiß, dass es auch andere Möglichkeiten gibt.)

Es ist ein bisschen wie mit Heisenbergs Unschärferelation. Nur indem ich die Variable beobachte, ändere ich sie.

Malte Skoruppa
quelle
5
Schön, aber wahrscheinlich besser für unix.stackexchange.com geeignet ... da gibt es echte bashÜber-Gurus.
Rmano
Nun, ist es möglich, es dorthin zu bewegen? Modifikationen?
Malte Skoruppa
Ich bin mir wirklich nicht sicher, wie ich es machen soll - ich nehme an, es ist ein Mod-Privileg. Auf der anderen Seite halte ich es nicht für sinnvoll, zu markieren - mal sehen, ob jemand auf die Schließfahne reagiert.
Rmano
1
Für den dritten Punkt ist dies das, wonach Sie suchen. Vielleicht können Sie auf der Grundlage dieses Artikels auch die Antwort für die ersten beiden Punkte finden.
Radu Rădeanu
2
+1 hat mich verdammt verwirrt, aber es hat Spaß gemacht zu lesen!
Joe

Antworten:

15

Beantwortung der dritten Frage: Natürlich kann es sinnvoll eingesetzt werden, um im Bash-Handbuch eindeutige Hinweise zu geben - in einer Falle, zB:

$ trap 'echo ‘$BASH_COMMAND’ failed with error code $?' ERR
$ fgfdjsa
fgfdjsa: command not found
fgfdjsa failed with error code 127
$ cat /etc/fgfdjsa
cat: /etc/fgfdjsa: No such file or directory
cat /etc/fgfdjsa failed with error code 1
Dmitry Alexandrov
quelle
Ah, in der Tat schön. Würden Sie also sagen, dass eine Falle der einzige Kontext ist, in dem diese Variable Sinn macht? Es klang wie ein Eckfall im Handbuch: "Der Befehl wird ausgeführt (...), es sei denn, die Shell führt einen Befehl als Ergebnis einer Falle aus, ..."
Malte Skoruppa
Nachdem Sie die Artikel gelesen haben, die in der Antwort von @DocSalvager verlinkt sind, ist klar, dass $BASH_COMMANDsie auch in anderen Kontexten als einer Falle sinnvoll verwendet werden können. Die von Ihnen beschriebene Verwendung ist jedoch wahrscheinlich die typischste und direkteste. Ihre und die von DocSalvager gestellte Antwort beantwortet mein Q3 am besten, und dies war für mich die wichtigste. Für Q1 und Q2 sind, wie durch @zwets angegeben, diese Dinge undefiniert. Es kann sein, dass $BASH_COMMANDfür die Leistung nur ein Wert angegeben wird, auf den zugegriffen wird, oder dass einige seltsame interne Programmbedingungen zu diesem Verhalten führen. Wer weiß?
Malte Skoruppa
1
Auf Nebenbei entdeckte ich , dass Sie kann zwingen $BASH_COMMANDimmer so eingestellt werden, durch Erzwingen Auswertung der $BASH_COMMANDvor jedem Befehl. Dazu können wir den DEBUG-Trap verwenden, der vor jedem einzelnen Befehl (all of em) ausgeführt wird. Wenn wir ausgeben, zum Beispiel trap 'echo "$BASH_COMMAND" > /dev/null' DEBUG, dann $BASH_COMMANDwird immer vor jedem Befehl ausgewertet werden (und den Wert dieser zugewiesen $COMMAND). Also, wenn ich ausführe, set | grep BASH_COMMAND=während diese Falle aktiv ist ... was weißt du? Jetzt ist die Ausgabe BASH_COMMAND=setwie erwartet :)
Malte Skoruppa
6

Nachdem Q3 beantwortet wurde (meiner Meinung nach richtig: BASH_COMMANDnützlich in Fallen und kaum anderswo), versuchen wir es mit Q1 und Q2.

Die Antwort auf Q1 lautet: Die Richtigkeit Ihrer Annahme ist nicht zu entscheiden. Die Wahrheit von keinem der Aufzählungspunkte kann festgestellt werden, da sie nach nicht spezifiziertem Verhalten fragen. Gemäß seiner Spezifikation wird der Wert von BASH_COMMANDfür die Dauer der Ausführung dieses Befehls auf den Text eines Befehls festgelegt. Die Spezifikation gibt nicht an, wie hoch ihr Wert in einer anderen Situation sein muss, dh wenn kein Befehl ausgeführt wird. Es könnte irgendeinen Wert haben oder gar keinen.

Die Antwort auf Q2 "Wenn nein, wie sonst kann das Verhalten in der obigen Befehlssequenz erklärt werden?" dann folgt logisch (wenn auch etwas pedantisch): es wird dadurch erklärt, dass der Wert von BASH_COMMANDundefiniert ist. Da sein Wert undefiniert ist, kann er einen beliebigen Wert haben, und genau das zeigt die Sequenz.

Nachsatz

Es gibt einen Punkt, an dem Sie meiner Meinung nach tatsächlich eine Schwäche in der Spezifikation haben. Hier sagst du:

Auch wenn MYVARIABLE = $ BASH_COMMAND direkt am Anfang von $ PROMPT_COMMAND ausgeführt wird, enthält MYVARIABLE die Zeichenfolge MYVARIABLE = $ BASH_COMMAND, da eine Zuweisung auch ein Befehl ist .

Die Art und Weise, wie ich die Bash-Manpage lese, ist nicht wahr. In diesem Abschnitt wird SIMPLE COMMAND EXPANSIONerläutert, wie zuerst die Variablenzuweisungen in der Befehlszeile aufgehoben werden und dann

Wenn sich kein Befehlsname ergibt [mit anderen Worten, es gab nur Variablenzuweisungen], wirken sich die Variablenzuweisungen auf die aktuelle Shell-Umgebung aus.

Dies legt für mich nahe, dass Variablenzuweisungen keine Befehle sind (und daher nicht angezeigt werden dürfen BASH_COMMAND), wie dies auch in anderen Programmiersprachen der Fall ist. Dies würde dann auch erklären, warum es keine Zeile BASH_COMMAND=setin der Ausgabe von gibt set, die im setWesentlichen ein syntaktischer 'Marker' für die Variablenzuweisung ist.

OTOH, im letzten Absatz dieses Abschnitts heißt es

Wenn nach der Erweiterung noch ein Befehlsname vorhanden ist, wird die Ausführung wie unten beschrieben fortgesetzt. Andernfalls wird der Befehl beendet .

... was etwas anderes vorschlägt, und Variablenzuweisungen sind ebenfalls Befehle.

zwets
quelle
1
Nur weil eine Vermutung nicht aufgestellt werden kann, heißt das nicht, dass sie falsch ist. Einstein nahm an, dass die Lichtgeschwindigkeit für jeden Beobachter (bei jeder Geschwindigkeit) dieselbe ist wie eine grundlegende Hypothese für die Relativitätstheorie. das kann nicht festgestellt werden, aber das heißt nicht, dass es falsch ist. Die Eigenschaften "kann festgelegt werden" und "ist korrekt" sind orthogonal. Bestenfalls könnte man sagen "Wir wissen nicht, ob es richtig ist, da es nicht festgestellt werden kann". Außerdem wird angegeben, dass es sich um den aktuellen Befehl während der Ausführung handelt. Also, wenn ich tippe, setsollte ich BASH_COMMAND='set'irgendwo in dieser Ausgabe sehen, welche
Malte Skoruppa
... ist aber nicht der Fall. Auch wenn dies während der Ausführung des setBefehls geschieht . Daher sollte es laut Spezifikation funktionieren. Befolgen die Implementierungen die Spezifikationen nicht genau oder verstehe ich sie falsch? Die Antwort auf diese Frage zu finden, ist eine Art Zweck von Q1. +1 für Ihren Nachtrag :)
Malte Skoruppa
@MalteSkoruppa Gute Punkte. Korrigierte die Antwort entsprechend und fügte eine Anmerkung über hinzu set.
zwets
1
Es ist möglich, dass es sich im Bash-Interpreter setnicht um einen "Befehl" handelt. So wie =es kein "Befehl" ist. Die Verarbeitung des Texts, der diesen Tokens folgt / diese umgibt, kann eine völlig andere Logik haben als die Verarbeitung eines "Befehls".
DocSalvager
Gute Punkte von euch beiden. :) Es kann durchaus sein, dass setdas nicht als "Befehl" zählt. Ich hätte nicht gedacht, dass sich $BASH_COMMANDdas unter vielen Umständen als so unterbestimmt herausstellen würde. Ich denke , zumindest wir festgestellt haben , dass der Mann Seite auf jeden Fall könnte klarer darüber sein;)
Malte Skoruppa
2

Eine erfinderische Verwendung für $ BASH_COMMAND

Kürzlich wurde diese eindrucksvolle Verwendung von $ BASH_COMMAND bei der Implementierung einer makrofähigen Funktionalität festgestellt .

Dies ist der Kerntrick des Alias ​​und ersetzt die Verwendung der DEBUG-Falle. Wenn Sie den Teil im vorherigen Beitrag über die DEBUG-Falle lesen, erkennen Sie die Variable $ BASH_COMMAND. In diesem Beitrag habe ich gesagt, dass es vor jedem Aufruf der DEBUG-Falle auf den Text des Befehls gesetzt wurde. Nun, es stellt sich heraus, dass es gesetzt ist, bevor jeder Befehl, DEBUG-Trap oder Nein ausgeführt wird (z. B. 'echo “this command = $ BASH_COMMAND”' ausführen, um zu sehen, wovon ich spreche). Durch Zuweisen einer Variablen (nur für diese Zeile) erfassen wir BASH_COMMAND im äußersten Bereich des Befehls, der den gesamten Befehl enthält.

Der vorherige Artikel des Autors bietet auch einige gute Hintergrundinformationen beim Implementieren einer Technik unter Verwendung eines DEBUG trap. Das trapist in der verbesserten Version beseitigt.

DocSalvager
quelle
+1 Endlich habe ich diese beiden Artikel gelesen. Wow! Was für eine Lektüre! Sehr aufschlussreich. Dieser Typ ist ein Bash- Guru ! Ein dickes Lob! Dies beantwortet auch meinen Kommentar zu Dmitry's Antwort, ob er $BASH_COMMANDin einem anderen Kontext als a sinnvoll verwendet werden kann trap. Und was für ein Nutzen das ist! Verwenden Sie es, um Makrobefehle in bash zu implementieren - wer hätte das für möglich gehalten? Danke für den Hinweis.
Malte Skoruppa
0

Eine anscheinend übliche Verwendung für das Debuggen von trap ... besteht darin, die Titel in der Fensterliste (^ A ") zu verbessern, wenn Sie" screen "verwenden.

Ich habe versucht, den "Bildschirm" und die "Fensterliste" benutzerfreundlicher zu machen, also habe ich angefangen, Artikel zu finden, die auf "trap ... debug" verweisen.

Ich fand heraus, dass die Methode, die PROMPT_COMMAND zum Senden der "Null-Titelsequenz" verwendet, nicht so gut funktionierte, weshalb ich auf die Debug-Methode trap ... zurückgegriffen habe.

So machen Sie es:

1 Weisen Sie "screen" an, nach der Escape-Sequenz zu suchen (aktivieren), indem Sie "shelltitle '$ | bash:'" in "$ HOME / .screenrc" einfügen.

2 Deaktivieren Sie die Weitergabe der Debug-Trap in Sub-Shells. Dies ist wichtig, denn wenn es aktiviert ist, wird alles durcheinander gebracht. Verwenden Sie dazu: "set + o functrace".

3 Senden Sie die Titel-Escape-Sequenz für "screen", um Folgendes zu interpretieren: trap 'printf "\ ek $ (Datum +% Y% m% d% H% M% S) $ (whoami) @ $ (Hostname): $ (pwd) $ {BASH_COMMAND} \ e \ "'" DEBUG "

Es ist nicht perfekt, aber es hilft, und Sie können diese Methode verwenden, um buchstäblich alles, was Sie möchten, in den Titel einzufügen

Siehe folgende "Fensterliste" (5 Bildschirme):

Num Name Flags

1 Bash: 20161115232035 mcb @ ken007: / home / mcb / ken007 RSYNCCMD = "sudo rsync" myrsync.sh -R -r "$ {1}" "$ {BACKUPDIR} $ {2: +" / $ {2} " } "$ 2 bash: 20161115230434 mcb @ ken007: / home / mcb / ken007 ls --color = auto -la $ 3 bash: 20161115230504 mcb @ ken007: / home / mcb / ken007 cat bin / psg.sh $ 4 bash: 20161115222415 mcb @ ken007: / home / mcb / ken007 ssh ken009 $ 5 bash: 20161115222450 mcb @ ken007: / home / mcb / ken007 mycommoncleanup $

Malcolm Boekhoff
quelle