Sprawne sprawdzanie stanu wyjścia Bash kilku poleceń
Czy istnieje coś podobnego do pipefail dla wielu poleceń, jak polecenie "try", ale wewnątrz Basha. Chciałbym zrobić coś takiego:
echo "trying stuff"
try {
command1
command2
command3
}
I w dowolnym momencie, jeśli jakieś polecenie zawiedzie, wycofaj i wypisz błąd tego polecenia. Nie chcę robić czegoś takiego:
command1
if [ $? -ne 0 ]; then
echo "command1 borked it"
fi
command2
if [ $? -ne 0 ]; then
echo "command2 borked it"
fi
I tak dalej... lub coś podobnego:
pipefail -o
command1 "arg1" "arg2" | command2 "arg1" "arg2" | command3
Ponieważ argumenty każdego polecenia wierzę (popraw mnie, jeśli się mylę) będą ze sobą kolidować. Te dwie metody wydają się strasznie długo i paskudnie dla mnie, więc apeluję o bardziej efektywną metodę.
13 answers
Możesz napisać funkcję, która uruchamia i testuje polecenie za Ciebie. Załóżmy, że command1
i {[2] } są zmiennymi środowiskowymi, które zostały ustawione na polecenie.
function mytest {
"$@"
local status=$?
if [ $status -ne 0 ]; then
echo "error with $1" >&2
fi
return $status
}
mytest $command1
mytest $command2
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-04-21 21:45:00
Co masz na myśli mówiąc "porzuć i echo błędu"? Jeśli masz na myśli, że chcesz, aby skrypt zakończył się natychmiast po niepowodzeniu polecenia, po prostu wykonaj
set -e
Na początku skryptu (ale Uwaga ostrzeżenie poniżej). Nie kłopocz się wyświetlaniem Komunikatu o błędzie: niech to zajmie się nieudane polecenie. Innymi słowy, jeśli to zrobisz:
#!/bin/sh
set -e # Use caution. eg, don't do this
command1
command2
command3
I command2 nie powiedzie się, podczas drukowania Komunikatu o błędzie na stderr, wtedy wydaje się, że osiągnąłeś to, co chcesz. (Chyba, że źle zinterpretuję to, co Ty chcesz!)
Jako następstwo, każde polecenie, które piszesz, musi zachowywać się dobrze: musi zgłaszać błędy na stderr zamiast na stdout (przykładowy kod w pytaniu wyświetla błędy na stdout) i musi zakończyć się ze statusem niezerowym, gdy się nie powiedzie.
[6]}jednak nie uważam tego za dobrą praktykę.set -e
zmienił swoją semantykę z różnymi wersjami Basha i chociaż działa dobrze dla prostego skryptu, jest tak wiele przypadków krawędzi, że jest zasadniczo bezużyteczny. (Rozważ rzeczy takie jak: set -e; foo() { false; echo should not print; } ; foo && echo ok
semantyka tutaj jest dość rozsądna, ale jeśli refaktorujesz kod do funkcji, która opierała się na ustawieniu opcji do wcześniejszego zakończenia, możesz łatwo zostać ugryziony.) IMO lepiej napisać:
#!/bin/sh
command1 || exit
command2 || exit
command3 || exit
Lub
#!/bin/sh
command1 && command2 && command3
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-04-03 22:30:14
Mam zestaw funkcji skryptowych, które szeroko wykorzystuję w moim systemie Red Hat. Używają funkcji systemowych z /etc/init.d/functions
do wyświetlania wskaźników statusu na Zielono [ OK ]
i na Czerwono [FAILED]
.
Możesz opcjonalnie ustawić zmienną $LOG_STEPS
na nazwę pliku dziennika, jeśli chcesz zapisać, które polecenia zawiodą.
Użycie
step "Installing XFS filesystem tools:"
try rpm -i xfsprogs-*.rpm
next
step "Configuring udev:"
try cp *.rules /etc/udev/rules.d
try udevtrigger
next
step "Adding rc.postsysinit hook:"
try cp rc.postsysinit /etc/rc.d/
try ln -s rc.d/rc.postsysinit /etc/rc.postsysinit
try echo $'\nexec /etc/rc.postsysinit' >> /etc/rc.sysinit
next
Wyjście
Installing XFS filesystem tools: [ OK ]
Configuring udev: [FAILED]
Adding rc.postsysinit hook: [ OK ]
Kod
#!/bin/bash
. /etc/init.d/functions
# Use step(), try(), and next() to perform a series of commands and print
# [ OK ] or [FAILED] at the end. The step as a whole fails if any individual
# command fails.
#
# Example:
# step "Remounting / and /boot as read-write:"
# try mount -o remount,rw /
# try mount -o remount,rw /boot
# next
step() {
echo -n "$@"
STEP_OK=0
[[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
}
try() {
# Check for `-b' argument to run command in the background.
local BG=
[[ $1 == -b ]] && { BG=1; shift; }
[[ $1 == -- ]] && { shift; }
# Run the command.
if [[ -z $BG ]]; then
"$@"
else
"$@" &
fi
# Check if command failed and update $STEP_OK if so.
local EXIT_CODE=$?
if [[ $EXIT_CODE -ne 0 ]]; then
STEP_OK=$EXIT_CODE
[[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
if [[ -n $LOG_STEPS ]]; then
local FILE=$(readlink -m "${BASH_SOURCE[1]}")
local LINE=${BASH_LINENO[0]}
echo "$FILE: line $LINE: Command \`$*' failed with exit code $EXIT_CODE." >> "$LOG_STEPS"
fi
fi
return $EXIT_CODE
}
next() {
[[ -f /tmp/step.$$ ]] && { STEP_OK=$(< /tmp/step.$$); rm -f /tmp/step.$$; }
[[ $STEP_OK -eq 0 ]] && echo_success || echo_failure
echo
return $STEP_OK
}
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-03-04 20:17:09
Jeśli to coś warte, krótszym sposobem na napisanie kodu sprawdzającego powodzenie każdej komendy jest:
command1 || echo "command1 borked it"
command2 || echo "command2 borked it"
Wciąż jest nudny, ale przynajmniej czytelny.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-03-04 16:11:23
Alternatywą jest po prostu połączenie poleceń razem z &&
tak, aby pierwsza z nich nie powiodła się, uniemożliwiając wykonanie pozostałych:
command1 &&
command2 &&
command3
Nie jest to składnia, o którą prosiłeś w pytaniu, ale jest to wspólny wzorzec dla przypadków użycia, które opisujesz. Ogólnie polecenia powinny być odpowiedzialne za błędy drukowania, więc nie musisz tego robić ręcznie(może z flagą -q
, aby wyciszyć błędy, gdy ich nie chcesz). Jeśli masz możliwość modyfikacji tych polecenia, edytowałbym je tak, aby krzyczały na porażkę, zamiast owijać je w coś innego, co tak robi.
Zauważ również, że nie musisz tego robić:
command1
if [ $? -ne 0 ]; then
Możesz po prostu powiedzieć:
if ! command1; then
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-02-24 16:47:11
Zamiast tworzyć funkcje runner lub używać set -e
, Użyj trap
:
trap 'echo "error"; do_cleanup failed; exit' ERR
trap 'echo "received signal to stop"; do_cleanup interrupted; exit' SIGQUIT SIGTERM SIGINT
do_cleanup () { rm tempfile; echo "$1 $(date)" >> script_log; }
command1
command2
command3
Pułapka ma nawet dostęp do numeru linii i wiersza poleceń komendy, która ją uruchomiła. Zmienne to $BASH_LINENO
i $BASH_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-03-04 16:19:43
Osobiście wolę używać lekkiego podejścia, jak widać TUTAJ ;
yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }
asuser() { sudo su - "$1" -c "${*:2}"; }
Przykładowe użycie:
try apt-fast upgrade -y
try asuser vagrant "echo 'uname -a' >> ~/.profile"
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 11:33:25
run() {
$*
if [ $? -ne 0 ]
then
echo "$* failed with exit code $?"
return 1
else
return 0
fi
}
run command1 && run command2 && run command3
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-03-04 15:29:05
Opracowałem prawie bezbłędną implementację try & catch w bash, która pozwala na pisanie kodu w stylu:
try
echo 'Hello'
false
echo 'This will not be displayed'
catch
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
Możesz nawet zagnieżdżać klocki try-catch wewnątrz siebie!
try {
echo 'Hello'
try {
echo 'Nested Hello'
false
echo 'This will not execute'
} catch {
echo "Nested Caught (@ $__EXCEPTION_LINE__)"
}
false
echo 'This will not execute too'
} catch {
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}
Kod jest częścią mojego Bash boilerplate / framework . Dodatkowo rozszerza ideę try & catch o takie rzeczy jak obsługa błędów z backtrace i wyjątkami (plus kilka innych fajnych funkcji).
Oto kod, który odpowiada tylko za próbę & "catch": {]}
set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0
# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
__oo__insideTryCatch+=1; ( set -e;
trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "
Exception.Capture() {
local script="${BASH_SOURCE[1]#./}"
if [[ ! -f /tmp/stored_exception_source ]]; then
echo "$script" > /tmp/stored_exception_source
fi
if [[ ! -f /tmp/stored_exception_line ]]; then
echo "$1" > /tmp/stored_exception_line
fi
return 0
}
Exception.Extract() {
if [[ $__oo__insideTryCatch -gt 1 ]]
then
set -e
fi
__oo__insideTryCatch+=-1
__EXCEPTION_CATCH__=( $(Exception.GetLastException) )
local retVal=$1
if [[ $retVal -gt 0 ]]
then
# BACKWARDS COMPATIBILE WAY:
# export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
# export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
return 1 # so that we may continue with a "catch"
fi
}
Exception.GetLastException() {
if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
then
cat /tmp/stored_exception
cat /tmp/stored_exception_line
cat /tmp/stored_exception_source
else
echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
fi
rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
return 0
}
Nie krępuj się używać, fork i przyczynić się - to jest na GitHub .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
2015-05-03 22:12:02
Przepraszam, że nie mogę skomentować pierwszej odpowiedzi Ale powinieneś użyć nowej instancji do wykonania polecenia: cmd_output=$($@)
#!/bin/bash
function check_exit {
cmd_output=$($@)
local status=$?
echo $status
if [ $status -ne 0 ]; then
echo "error with $1" >&2
fi
return $status
}
function run_command() {
exit 1
}
check_exit run_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
2014-10-03 08:16:06
dla użytkowników fish shell , którzy natknęli się na ten wątek.
Niech {[2] } będzie funkcją, która nie" zwraca " (echo) wartości, ale ustawia kod wyjścia jak zwykle.
Aby uniknąć sprawdzania $status
Po wywołaniu funkcji, można wykonać:
foo; and echo success; or echo failure
A jeśli jest za długi, aby zmieścić się w jednej linii:
foo; and begin
echo success
end; or begin
echo failure
end
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
2015-10-25 20:05:09
Kiedy używam ssh
muszę rozróżnić problemy spowodowane problemami z połączeniem i kody błędów polecenia zdalnego w errexit
(set -e
) mode. Używam następującej funkcji:
# prepare environment on calling site:
rssh="ssh -o ConnectionTimeout=5 -l root $remote_ip"
function exit255 {
local flags=$-
set +e
"$@"
local status=$?
set -$flags
if [[ $status == 255 ]]
then
exit 255
else
return $status
fi
}
export -f exit255
# callee:
set -e
set -o pipefail
[[ $rssh ]]
[[ $remote_ip ]]
[[ $( type -t exit255 ) == "function" ]]
rjournaldir="/var/log/journal"
if exit255 $rssh "[[ ! -d '$rjournaldir/' ]]"
then
$rssh "mkdir '$rjournaldir/'"
fi
rconf="/etc/systemd/journald.conf"
if [[ $( $rssh "grep '#Storage=auto' '$rconf'" ) ]]
then
$rssh "sed -i 's/#Storage=auto/Storage=persistent/' '$rconf'"
fi
$rssh systemctl reenable systemd-journald.service
$rssh systemctl is-enabled systemd-journald.service
$rssh systemctl restart systemd-journald.service
sleep 1
$rssh systemctl status systemd-journald.service
$rssh systemctl is-active systemd-journald.service
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-01-19 12:48:08
Sprawdzanie stanu w sposób funkcjonalny
assert_exit_status() {
lambda() {
local val_fd=$(echo $@ | tr -d ' ' | cut -d':' -f2)
local arg=$1
shift
shift
local cmd=$(echo $@ | xargs -E ':')
local val=$(cat $val_fd)
eval $arg=$val
eval $cmd
}
local lambda=$1
shift
eval $@
local ret=$?
$lambda : <(echo $ret)
}
Użycie:
assert_exit_status 'lambda status -> [[ $status -ne 0 ]] && echo Status is $status.' lls
Wyjście
Status is 127
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-31 12:57:30