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.
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
.
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
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)
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
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. :)
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