it-swarm.com.de

BASH: Echo des letzten Befehlslaufs

Ich versuche den letzten Befehl innerhalb eines Bash-Skripts zu wiederholen. Ich habe einen Weg gefunden, es mit history,tail,head,sed zu tun, was gut funktioniert, wenn Befehle eine bestimmte Zeile in meinem Skript vom Standpunkt des Parsers 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, den letzten Befehl auszuführen, unabhängig davon, wie/wo der Befehl innerhalb des Bash-Skripts aufgerufen wird?

Meine Antwort

Trotz der sehr geschätzten Beiträge meiner SO-Kollegen entschied ich mich für das Schreiben einer run-Funktion, 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 die Befehle voranstellen, die ich mit run überprüfen möchte. Dadurch bleiben sie in einer Zeile und beeinflussen nicht die Prägnanz meines Skripts
- Immer wenn das Skript bei einem dieser Befehle ausfällt, wird in der letzten Ausgabezeile meines Skripts eindeutig angezeigt, welcher Befehl zusammen mit dem Beendigungscode fehlschlägt, wodurch das Debuggen vereinfacht wird

Beispielskript:

#!/bin/bash
die() { echo >&2 -e "\nERROR: [email protected]\n"; exit 1; }
run() { "[email protected]"; 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, zitierte Argumente ... und die run-Funktion hat sie nicht beschädigt. Das einzige Problem, das ich bisher gefunden habe, ist, ein Echo auszuführen, das bricht, aber ich habe nicht vor, meine Echos zu überprüfen.

67
Max

Der Befehlsverlauf ist eine interaktive Funktion. In die Historie werden nur vollständige Befehle eingegeben. Beispielsweise wird das Konstrukt case als Ganzes eingegeben, wenn die Shell das Parsen abgeschlossen hat. Weder das Nachschlagen des Verlaufs mit der eingebauten Variable history (noch das Drucken über die Shell-Erweiterung (!:p)) macht das, was Sie wollen, nämlich das Aufrufen einfacher Befehle.

Mit der DEBUG trap können Sie einen Befehl direkt vor der Ausführung eines einfachen Befehls ausführen. Eine Zeichenfolgenversion des auszuführenden Befehls (mit durch Leerzeichen getrennten Wörtern) ist in der Variablen BASH_COMMAND verfügbar.

trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
…
echo "last command is $previous_command"

Beachten Sie, dass sich previous_command bei jeder Ausführung eines Befehls ändert. Speichern Sie ihn in einer Variablen, um ihn verwenden zu können. Wenn Sie auch den Rückkehrstatus des vorherigen Befehls erfahren möchten, speichern Sie beide in einem einzigen Befehl.

cmd=$previous_command ret=$?
if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi

Wenn Sie nur bei einem fehlgeschlagenen Befehl abbrechen möchten, verwenden Sie set -e , um das Skript beim ersten fehlgeschlagenen Befehl zu beenden. Sie können den letzten Befehl über die EXIT-Trap anzeigen.

set -e
trap 'echo "exit $? due to $previous_command"' EXIT

Wenn Sie versuchen, Ihr Skript zu verfolgen, um zu sehen, was es tut, vergessen Sie das alles und verwenden Sie set -x .

52
Gilles

Bash verfügt über integrierte Funktionen für den Zugriff auf den zuletzt ausgeführten Befehl. Dies ist jedoch der letzte Befehl (z. B. der gesamte Befehl case), nicht einzelne Befehle, die 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 Befehlszeile

usw.

Die einfachste Antwort auf diese Frage lautet also:

echo !!

...Alternative: 

echo "Last command run was ["!:0"] with arguments ["!:*"]"

Versuch es selber!

echo this is a test
echo !!

In einem Skript ist die Protokollerweiterung standardmäßig deaktiviert. Sie müssen sie mit aktivieren

set -o history -o histexpand
143
groovyspaceman

Nachdem ich die answer from Gilles gelesen hatte, entschied ich mich zu sehen, ob der $BASH_COMMAND var auch in einer EXIT-Falle (und dem gewünschten Wert) vorhanden war - und das ist es!

Das folgende Bash-Skript funktioniert also wie erwartet:

#!/bin/bash

exit_trap () {
  local lc="$BASH_COMMAND" rc=$?
  echo "Command [$lc] exited with code [$rc]"
}

trap exit_trap EXIT
set -e

echo "foo"
false 12345
echo "bar"

Die Ausgabe ist

foo
Command [false 12345] exited with code [1]

bar wird nie gedruckt, da set -e das Skript beendet, wenn ein Befehl fehlschlägt und der false-Befehl (per Definition) immer ausfällt. Der an false übergebene 12345 zeigt nur an, dass die Argumente für den fehlgeschlagenen Befehl ebenfalls erfasst werden (der Befehl false ignoriert alle an ihn übergebenen Argumente.)

13
Hercynium

Ich konnte dies erreichen, indem ich set -x im Hauptskript verwendete (wodurch das Skript jeden ausgeführten Befehl ausdrucken lässt) und ein Wrapper-Skript geschrieben wurde, das nur die letzte Ausgabezeile von set -x anzeigt.

Dies ist das Hauptskript:

#!/bin/bash
set -x
echo some command here
echo last command

Und das ist das Wrapper-Skript:

#!/bin/sh
./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'

Beim Ausführen des Wrapper-Skripts wird dies als Ausgabe erzeugt:

echo last command
7
Mark Drago

history | tail -2 | head -1 | cut -c8-999

tail -2 gibt die letzten beiden Befehlszeilen aus dem Verlauf zurück. head -1 gibt nur die erste Zeile zurück. cut -c8-999 gibt nur die Befehlszeile zurück, wobei PID und Leerzeichen entfernt werden.

0
Guma

Es gibt eine Racebedingung zwischen den Variablen des letzten Befehls ($ _) und des letzten Fehlers ($?). Wenn Sie versuchen, eine davon in einer eigenen Variablen zu speichern, haben beide aufgrund des Befehls set bereits neue Werte gefunden. Eigentlich hat der letzte Befehl in diesem Fall überhaupt keinen Wert.

So habe ich (fast) beide Informationen in eigenen Variablen gespeichert, sodass mein Bash-Skript feststellen kann, ob ein Fehler aufgetreten ist, und den Titel mit dem letzten Ausführungsbefehl gesetzt hat:

   # This construct is needed, because of a racecondition when trying to obtain
   # both of last command and error. With this the information of last error is
   # implied by the corresponding case while command is retrieved.

   if   [[ "${?}" == 0 && "${_}" != "" ]] ; then
    # Last command MUST be retrieved first.
      LASTCOMMAND="${_}" ;
      RETURNSTATUS='✓' ;
   Elif [[ "${?}" == 0 && "${_}" == "" ]] ; then
      LASTCOMMAND='unknown' ;
      RETURNSTATUS='✓' ;
   Elif [[ "${?}" != 0 && "${_}" != "" ]] ; then
    # Last command MUST be retrieved first.
      LASTCOMMAND="${_}" ;
      RETURNSTATUS='✗' ;
      # Fixme: "$?" not changing state until command executed.
   Elif [[ "${?}" != 0 && "${_}" == "" ]] ; then
      LASTCOMMAND='unknown' ;
      RETURNSTATUS='✗' ;
      # Fixme: "$?" not changing state until command executed.
   fi

Dieses Skript speichert die Informationen, wenn ein Fehler aufgetreten ist, und ruft den letzten Ausführungsbefehl auf. Aufgrund der Racebedingung kann ich den tatsächlichen Wert nicht speichern. Außerdem kümmern sich die meisten Befehle eigentlich nicht einmal um Fehlernummern, sie geben nur etwas anderes als '0' zurück. Sie werden das merken, 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 bash extention, aber so etwas ist mir nicht vertraut, und es wäre auch nicht kompatibel.

KORREKTUR

Ich habe nicht gedacht, dass es möglich ist, beide Variablen gleichzeitig abzurufen. Obwohl ich den Stil des Codes mag, nahm ich an, dass er als zwei Befehle interpretiert werden würde. Das war falsch, also meine Antwort lautet:

   # Because of a racecondition, both MUST be retrieved at the same time.
   declare RETURNSTATUS="${?}" LASTCOMMAND="${_}" ;

   if [[ "${RETURNSTATUS}" == 0 ]] ; then
      declare RETURNSYMBOL='✓' ;
   else
      declare RETURNSYMBOL='✗' ;
   fi

Obwohl mein Beitrag möglicherweise keine positive Bewertung erhält, habe ich mein Problem schließlich selbst gelöst .. _. Und dies scheint in Bezug auf den ersten Beitrag angemessen zu sein. :)

0
WGRM