有效地检查几个命令的Bash退出状态
是否有类似的pipefail多个命令,如“try”语句,但在bash内。 我想要做这样的事情:
echo "trying stuff" try { command1 command2 command3 }
在任何时候,如果有任何命令失败,请退出并回显该命令的错误。 我不想要这样做:
command1 if [ $? -ne 0 ]; then echo "command1 borked it" fi command2 if [ $? -ne 0 ]; then echo "command2 borked it" fi
等等…或类似的东西:
pipefail -o command1 "arg1" "arg2" | command2 "arg1" "arg2" | command3
因为我相信每个命令的论点(如果我错了,纠正我)会互相干扰。 这两种方法对我来说似乎非常啰嗦和讨厌,所以我在这里呼吁一个更有效的方法。
你可以写一个函数来启动并testing你的命令。 假设command1
和command2
是已设置为命令的环境variables。
function mytest { "$@" local status=$? if [ $status -ne 0 ]; then echo "error with $1" >&2 fi return $status } mytest $command1 mytest $command2
“退出并回复错误”是什么意思? 如果你的意思是你希望脚本在任何命令失败时立即终止,那么就这样做
设置-e
在剧本的开始。 不要打扰回应错误消息:让失败的命令处理。 换句话说,如果你这样做:
#!/ bin / sh的 设置-e 命令1 命令2 指令代码
和command2失败,同时打印错误消息到stderr,那么你已经达到了你想要的。 (除非我误解你想要的东西!)
作为推论,您编写的任何命令都必须performance良好:它必须向stderr报告错误而不是stdout(问题中的示例代码将错误输出到stdout),并且在失败时必须以非零状态退出。
我有一套在我的Red Hat系统上广泛使用的脚本function。 他们使用/etc/init.d/functions
的系统函数来打印绿色[ OK ]
和红色[FAILED]
状态指示灯。
如果要logging哪些命令失败,可以select将$LOG_STEPS
variables设置为日志文件名。
用法
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
产量
Installing XFS filesystem tools: [ OK ] Configuring udev: [FAILED] Adding rc.postsysinit hook: [ OK ]
码
#!/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 }
值得一提的是,编写代码来检查每个命令是否成功的更简单的方法是:
command1 || echo "command1 borked it" command2 || echo "command2 borked it"
它仍然乏味,但至less它是可读的。
而不是创build亚军function或使用set -e
,使用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
陷阱甚至可以访问触发它的命令的行号和命令行。 variables是$BASH_LINENO
和$BASH_COMMAND
。
另一种方法是将命令与&&
一起join,以使第一个命令失败,防止其余部分执行:
command1 && command2 && command3
这不是您在问题中所要求的语法,但是这是您描述的用例的常见模式。 一般来说,这些命令应该是打印失败的责任,所以你不必手动这样做(也许当你不需要时用-q
标志来消除错误)。 如果你有修改这些命令的能力,我会编辑它们来大声疾呼,而不是把它们换成别的东西。
还要注意,你不需要这样做:
command1 if [ $? -ne 0 ]; then
你可以简单地说:
if ! command1; then
就我个人而言,我更喜欢使用轻量级的方法,
yell() { echo "$0: $*" >&2; } die() { yell "$*"; exit 111; } try() { "$@" || die "cannot $*"; } asuser() { sudo su - "$1" -c "${*:2}"; }
用法示例:
try apt-fast upgrade -y try asuser vagrant "echo 'uname -a' >> ~/.profile"
run() { $* if [ $? -ne 0 ] then echo "$* failed with exit code $?" return 1 else return 0 fi } run command1 && run command2 && run command3
我在bash中开发了一个几乎完美的try&catch实现,它允许你编写如下代码:
try echo 'Hello' false echo 'This will not be displayed' catch echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
你甚至可以将try-catch块embedded自己的内部!
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__!" }
代码是我bash样板/框架的一部分 。 它进一步扩展了尝试和捕捉与回溯和exceptionerror handling(加上一些其他不错的function)的东西的想法。
这里是负责try&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 }
随意使用,分叉和贡献 – 它在GitHub上 。
对不起,我不能对第一个答案发表评论但是你应该使用新的实例执行命令: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
对于绊倒在这个线程上的鱼壳用户。
假设foo
是一个函数,它不会“返回”(回显)一个值,但会像往常一样设置退出码。
为了避免在调用函数后检查$status
,可以这样做:
foo; and echo success; or echo failure
如果它太长,以适应一行:
foo; and begin echo success end; or begin echo failure end
以function方式检查状态
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) }
用法:
assert_exit_status 'lambda status -> [[ $status -ne 0 ]] && echo Status is $status.' lls
产量
Status is 127