Ich versuche, den letzten Befehl in einem Bash-Skript wiederzugeben. Ich habe einen Weg gefunden, dies mit einigen zu tun, history,tail,head,sed
der gut funktioniert, wenn Befehle eine bestimmte Zeile in meinem Skript vom Parser-Standpunkt aus darstellen. Unter bestimmten Umständen erhalte ich jedoch nicht die erwartete Ausgabe, beispielsweise wenn der Befehl in eine case
Anweisung eingefügt wird :
Das Skript:
#!/bin/bash
set -o history
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
case "1" in
"1")
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
;;
esac
Die Ausgabe:
Tue May 24 12:36:04 CEST 2011
last command is [date]
Tue May 24 12:36:04 CEST 2011
last command is [echo "last command is [$last]"]
[F] Kann mir jemand helfen, einen Weg zu finden, den letzten Ausführungsbefehl wiederzugeben, unabhängig davon, wie / wo dieser Befehl im Bash-Skript aufgerufen wird?
Meine Antwort
Trotz der sehr geschätzten Beiträge meiner SO'-Kollegen habe ich mich dafür entschieden, eine run
Funktion zu schreiben, die alle ihre Parameter als einen einzigen Befehl ausführt und den Befehl und seinen Fehlercode anzeigt, wenn er fehlschlägt - mit den folgenden Vorteilen:
-Ich muss nur Stellen Sie den Befehlen, mit run
denen ich prüfen möchte,
voran, wodurch sie in einer Zeile bleiben und die Prägnanz meines Skripts nicht beeinträchtigt wird. Wenn das Skript bei einem dieser Befehle fehlschlägt, ist die letzte Ausgabezeile meines Skripts eine Meldung, die deutlich anzeigt, welcher Befehl angezeigt wird schlägt zusammen mit dem Exit-Code fehl, was das Debuggen erleichtert
Beispielskript:
#!/bin/bash
die() { echo >&2 -e "\nERROR: $@\n"; exit 1; }
run() { "$@"; code=$?; [ $code -ne 0 ] && die "command [$*] failed with error code $code"; }
case "1" in
"1")
run ls /opt
run ls /wrong-dir
;;
esac
Die Ausgabe:
$ ./test.sh
apacheds google iptables
ls: cannot access /wrong-dir: No such file or directory
ERROR: command [ls /wrong-dir] failed with error code 2
Ich habe verschiedene Befehle mit mehreren Argumenten getestet, Bash-Variablen als Argumente, Argumente in Anführungszeichen ... und die run
Funktion hat sie nicht gebrochen. Das einzige Problem, das ich bisher gefunden habe, ist das Ausführen eines Echos, das unterbrochen wird, aber ich habe sowieso nicht vor, meine Echos zu überprüfen.
run()
dies nicht richtig funktioniert, wenn Anführungszeichen verwendet werden. Dies schlägt beispielsweise fehl :run ssh-keygen -t rsa -C [email protected] -f ./id_rsa -N ""
."something"
Argumente mit'"something"'
(oder besser gesagt,"'something'"
umsomething
(z. B. Variablen) auf der ersten Ebene zu interpretieren / zu bewerten, falls erforderlich)run() { $*; … }
in ein nahezu korrektes geändert,run() { "$@"; … }
da die fehlerhafte Antwort dazu führte, dass die Fragecp
mit einem 64-Fehlerstatus beendet wurde. Das Problem bestand darin, dass$*
die Befehlsargumente an den Leerzeichen in den Namen gebrochen wurden, dies aber"$@"
nicht taten.last=$(history | tail -n1 | sed 's/^[[:space:]][0-9]*[[:space:]]*//g')
funktionierte besser, zumindest für zsh und macOS 10.11Antworten:
Der Befehlsverlauf ist eine interaktive Funktion. In den Verlauf werden nur vollständige Befehle eingetragen. Beispielsweise wird das
case
Konstrukt als Ganzes eingegeben, wenn die Shell das Parsen beendet hat. Weder das Nachschlagen des Verlaufs mit demhistory
integrierten Element (noch das Drucken über die Shell-Erweiterung (!:p
)) führt zu dem, was Sie zu wollen scheinen, nämlich das Aufrufen von Aufrufen einfacher Befehle.Mit dem
DEBUG
Trap können Sie einen Befehl unmittelbar vor einer einfachen Befehlsausführung ausführen. In derBASH_COMMAND
Variablen ist eine Zeichenfolgenversion des auszuführenden Befehls (mit durch Leerzeichen getrennten Wörtern) verfügbar .Beachten Sie, dass
previous_command
sich dies jedes Mal ändert, wenn Sie einen Befehl ausführen. Speichern Sie ihn daher in einer Variablen, um ihn zu verwenden. Wenn Sie auch den Rückgabestatus des vorherigen Befehls wissen möchten, speichern Sie beide in einem einzigen Befehl.Wenn Sie nur bei fehlgeschlagenen Befehlen abbrechen möchten, verwenden Sie diese Option
set -e
, um Ihr Skript beim ersten fehlgeschlagenen Befehl zu beenden. Sie können den letzten Befehl aus derEXIT
Trap anzeigen .Beachten Sie, dass Sie all dies vergessen und verwenden müssen, wenn Sie versuchen, Ihr Skript zu verfolgen, um zu sehen, was es tut
set -x
.quelle
-x
gibt jeden einzelnen Befehl aus, aber leider bin ich nur daran interessiert, die fehlgeschlagenen Befehle zu sehen (was ich mit meinem Befehl erreichen kann, wenn ich ihn in eine[ ! "$? == "0" ]
Anweisungset -e
(häufig, aber nicht immer, der Befehl erzeugt eine ausreichend gute Fehlermeldung, sodass Sie keinen weiteren Kontext angeben müssen).eval echo "${BASH_COMMAND}"
kann beliebiger Code in Befehlsersetzungen ausgeführt werden. Es ist gefährlich.cd $(ls -td | head -n 1)
Stellen Sie sich einen Befehl wie - vor und stellen Sie sich nun die Befehlsersetzung vor, die aufgerufen wird,rm
oder so.Bash hat Funktionen für den Zugriff auf den zuletzt ausgeführten Befehl integriert. Aber das ist der letzte ganze Befehl (z. B. der ganze
case
Befehl), nicht einzelne einfache Befehle, wie Sie sie ursprünglich angefordert haben.!:0
= der Name des ausgeführten Befehls.!:1
= der erste Parameter des vorherigen Befehls!:*
= alle Parameter des vorherigen Befehls!:-1
= der letzte Parameter des vorherigen Befehls!!
= die vorherige Befehlszeileetc.
Die einfachste Antwort auf die Frage lautet also:
...Alternative:
Versuch es selber!
In einem Skript ist die Verlaufserweiterung standardmäßig deaktiviert. Sie müssen sie mit aktivieren
quelle
sudo !!
set -o history -o histexpand; echo "!!"
in einem Bash-Skript erhalte ich immer noch die Fehlermeldung:!!: event not found
(Es ist das gleiche ohne Anführungszeichen.)set -o history -o histexpand
in Skripten -> Lebensretter! Vielen Dank!bash
aufrufe, wird weiter gedruckt !! anstelle des letzten Ausführungsbefehls. Wo ist das dokumentiert?Nachdem ich die Antwort von Gilles gelesen hatte , entschied ich mich zu prüfen, ob die
$BASH_COMMAND
Variable auch in einerEXIT
Falle verfügbar war (und der gewünschte Wert) - und das ist es!Das folgende Bash-Skript funktioniert also wie erwartet:
Die Ausgabe ist
bar
wird nie gedruckt, daset -e
bash das Skript beendet, wenn ein Befehl fehlschlägt und der falsche Befehl immer fehlschlägt (per Definition). Das12345
übergeben anfalse
ist nur da, um zu zeigen, dass die Argumente für den fehlgeschlagenen Befehl ebenfalls erfasst werden (derfalse
Befehl ignoriert alle an ihn übergebenen Argumente).quelle
Dies konnte ich erreichen, indem ich
set -x
im Hauptskript (das das Skript jeden ausgeführten Befehl ausdrucken lässt) und ein Wrapper-Skript schrieb, das nur die letzte von erzeugte Ausgabezeile anzeigtset -x
.Dies ist das Hauptskript:
Und das ist das Wrapper-Skript:
Das Ausführen des Wrapper-Skripts erzeugt dies als Ausgabe:
quelle
history | tail -2 | head -1 | cut -c8-999
tail -2
Gibt die letzten beiden Befehlszeilen aus dem Verlaufhead -1
zurück. Gibt nur die erste Zeilecut -c8-999
zurück. Gibt nur die Befehlszeile zurück und entfernt PID und Leerzeichen.quelle
Zwischen den Variablen des letzten Befehls ($ _) und des letzten Fehlers ($?) Gibt es eine Racebedingung. Wenn Sie versuchen, einen von ihnen in einer eigenen Variablen zu speichern, sind beide aufgrund des Befehls set bereits auf neue Werte gestoßen. Tatsächlich hat der letzte Befehl in diesem Fall überhaupt keinen Wert.
Folgendes habe ich getan, um (fast) beide Informationen in eigenen Variablen zu speichern, damit mein Bash-Skript feststellen kann, ob ein Fehler aufgetreten ist UND den Titel mit dem letzten Ausführungsbefehl festgelegt hat:
Dieses Skript behält die Informationen bei, wenn ein Fehler aufgetreten ist, und ruft den letzten Ausführungsbefehl ab. Aufgrund der Racecondition kann ich den Istwert nicht speichern. Außerdem kümmern sich die meisten Befehle nicht einmal um Fehlernummern, sondern geben nur etwas anderes als '0' zurück. Sie werden das bemerken, wenn Sie die errono-Erweiterung von bash verwenden.
Es sollte mit so etwas wie einem "internen" Skript für Bash möglich sein, wie in der Bash-Erweiterung, aber ich kenne so etwas nicht und es wäre auch nicht kompatibel.
KORREKTUR
Ich dachte nicht, dass es möglich ist, beide Variablen gleichzeitig abzurufen. Obwohl mir der Stil des Codes gefällt, habe ich angenommen, dass er als zwei Befehle interpretiert wird. Das war falsch, daher lautet meine Antwort:
Obwohl mein Beitrag möglicherweise keine positive Bewertung erhält, habe ich mein Problem endlich selbst gelöst. Und dies scheint in Bezug auf den ersten Beitrag angemessen zu sein. :) :)
quelle