有效地检查几个命令的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你的命令。 假设command1command2是已设置为命令的环境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_STEPSvariables设置为日志文件名。

用法

 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