BASH: Echo ostatniego uruchomienia polecenia

Próbuję odtworzyć ostatnią komendę run wewnątrz skryptu bash. Znalazłem sposób, aby to zrobić z jakimś history,tail,head,sed, który działa dobrze, gdy polecenia reprezentują konkretną linię w moim skrypcie z punktu widzenia parsera. Jednak w pewnych okolicznościach nie otrzymuję oczekiwanego wyniku, na przykład gdy polecenie jest wstawione wewnątrz case instrukcji:

Scenariusz:

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

Wyjście:

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]"]

[Q] Czy ktoś może mi pomóc znaleźć sposób na echo ostatniej komendy run niezależnie od jak / gdzie to polecenie jest wywoływane w skrypcie bash?

Moja odpowiedź

Pomimo bardzo cenionego wkładu moich kolegów SO ' er, zdecydowałem się napisać run funkcję-która uruchamia wszystkie swoje parametry jako jedno polecenie i wyświetla polecenie i jego kod błędu, gdy się nie powiedzie-z następującymi korzyściami:]} -Muszę tylko przedłożyć polecenia, które chcę sprawdzić run, co utrzymuje je w jednej linii i nie wpływa na zwięzłość mojego skrypt
-Ilekroć skrypt nie powiedzie się na jednym z tych poleceń, ostatnia linia wyjściowa mojego skryptu jest komunikatem, który wyraźnie wyświetla, które polecenie nie powiedzie się wraz z kodem zakończenia, co ułatwia debugowanie

Przykładowy skrypt:

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

Wyjście:

$ ./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

Przetestowałem różne polecenia z wieloma argumentami, zmienne bash jako argumenty, cytowane argumenty... a funkcja run ich nie złamała. Jedyny problem jaki do tej pory znalazłem to uruchomić echo które się łamie ale ja i tak nie planuj sprawdzać mojego ECHA.

Author: Jonathan Leffler, 2011-05-24

5 answers

Historia poleceń jest funkcją interaktywną. W historii wpisywane są tylko kompletne polecenia. Na przykład, case konstrukt jest wprowadzany jako całość, gdy powłoka zakończy jego przetwarzanie. Ani przeglądanie historii za pomocą wbudowanego history (ani drukowanie jej przez rozszerzenie powłoki (!:p)) nie robi tego, co chcesz, czyli wypisuje wywołania prostych poleceń.

The DEBUG trap pozwala na wykonanie polecenia tuż przed zwykłym wykonaniem polecenia. A tekstowa wersja polecenia do wykonania (ze słowami oddzielonymi spacjami) jest dostępna w BASH_COMMAND zmienna.

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

Zauważ, że previous_command zmieni się przy każdym uruchomieniu polecenia, więc zapisz go do zmiennej, aby go użyć. Jeśli chcesz znać status powrotu poprzedniego polecenia, Zapisz oba w jednym poleceniu.

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

Ponadto, jeśli chcesz zrezygnować tylko z nieudanych poleceń, użyj set -e aby skrypt zakończył się przy pierwszym nieudanym poleceniu. Możesz wyświetlić ostatnią komendę z EXIT pułapka .

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

Zauważ, że jeśli próbujesz wyśledzić skrypt, aby zobaczyć, co robi, zapomnij o tym wszystkim i użyj set -x.

 50
Author: Gilles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-26 08:04:39

Bash ma wbudowane funkcje dostępu do ostatniego wykonanego polecenia. Ale to jest ostatnie całe polecenie (np. całe polecenie case), a nie pojedyncze proste polecenia, o które pierwotnie prosiłeś.

!:0 = Nazwa wykonanego polecenia.

!:1 = pierwszy parametr poprzedniego polecenia

!:* = wszystkie parametry poprzedniego polecenia

!:-1 = ostatni parametr poprzedniego polecenia

!! = poprzednie polecenie linia

Itd.

Więc najprostsza odpowiedź na to pytanie brzmi:

echo !!

...alternatywnie:

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

Spróbuj sam!

echo this is a test
echo !!

W skrypcie Rozszerzenie historii jest domyślnie wyłączone, musisz włączyć je za pomocą

set -o history -o histexpand
 135
Author: groovyspaceman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-20 11:49:55

Po przeczytaniu odpowiedzi z Gilles , postanowiłem sprawdzić, czy $BASH_COMMAND var jest również dostępny (i pożądana wartość) w EXIT pułapce - i jest!

Tak więc następujący skrypt bash działa zgodnie z oczekiwaniami:

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

Wyjście to

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

bar nigdy nie jest drukowana, ponieważ set -e powoduje, że bash kończy działanie skryptu, gdy polecenie nie powiedzie się, a polecenie false zawsze zawiedzie (z definicji). 12345 przekazany do {[7] } jest tylko po to, aby pokazać, że argumenty do nieudanego polecenia są również przechwytywane (false polecenie ignoruje wszelkie przekazane do niego argumenty)

 11
Author: Hercynium,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:02

Udało mi się to osiągnąć używając set -x w skrypcie głównym (co sprawia, że skrypt wypisuje wszystkie wykonywane polecenia) i pisząc skrypt wrappera, który pokazuje ostatnią linię wyjścia wygenerowaną przez set -x.

To jest główny skrypt:

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

A to jest skrypt wrappera:

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

Uruchomienie skryptu wrappera powoduje, że jest to wyjście:

echo last command
 7
Author: Mark Drago,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-24 13:07:15

Pomiędzy ostatnim poleceniem ($_) a ostatnim błędem ($?) zmienne. Jeśli spróbujesz zapisać jedną z nich we własnej zmiennej, obie napotkają nowe wartości już z powodu polecenia set. Ostatnie polecenie nie ma w tym przypadku żadnej wartości.

Oto, co zrobiłem, aby przechowywać (prawie) obie informacje we własnych zmiennych, więc mój skrypt bash może określić, czy wystąpił błąd i ustawienie tytułu ostatnim poleceniem run:

   # 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

Ten skrypt będzie Zachowaj informacje, jeśli wystąpi błąd i uzyska ostatnią komendę run. Ze względu na warunki wyścigowe nie mogę przechowywać rzeczywistej wartości. Poza tym, większość komend faktycznie nawet nie dba o błędy, po prostu zwracają coś innego niż '0'. Zauważysz to, jeśli użyjesz błędnego rozszerzenia bash.

Powinno być możliwe z czymś w rodzaju skryptu "intern" dla Basha, jak w bash extention, ale nie jestem zaznajomiony z czymś takim i nie byłoby również kompatybilny.

Korekta

Nie sądziłem, że da się pobierać obie zmienne jednocześnie. Chociaż podoba mi się styl kodu, założyłem, że będzie on interpretowany jako dwie komendy. To było złe, więc moja odpowiedź sprowadza się do:

   # 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

Chociaż mój post może nie uzyskać żadnej pozytywnej oceny, w końcu sam rozwiązałem swój problem. I to wydaje się właściwe w odniesieniu do intial post. :)

 0
Author: WGRM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-16 22:04:43