有效地检查多个命令的Bash退出状态

有效地检查多个命令的Bash退出状态,bash,exit,Bash,Exit,对于多个命令是否有类似于pipefail的东西,比如一个“try”语句,但在bash中。我想这样做: echo "trying stuff" try { command1 command2 command3 } 在任何时候,如果任何命令失败,退出并回显该命令的错误。我不想做这样的事情: command1 if [ $? -ne 0 ]; then echo "command1 borked it" fi command2 if [ $? -ne 0 ]; th

对于多个命令是否有类似于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
等等。。。或者类似于:

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

因为我相信每个命令的参数(如果我错了,请纠正我)都会相互干扰。这两种方法对我来说似乎冗长不堪,令人讨厌,所以我在这里呼吁寻找一种更有效的方法。

您可以编写一个函数来启动并测试命令。假设
command1
command2
是已设置为命令的环境变量

run() {
  $*
  if [ $? -ne 0 ]
  then
    echo "$* failed with exit code $?"
    return 1
  else
    return 0
  fi
}

run command1 && run command2 && run command3
function mytest {
    "$@"
    local status=$?
    if (( status != 0 )); then
        echo "error with $1" >&2
    fi
    return $status
}

mytest "$command1"
mytest "$command2"
你说的“退出并回应错误”是什么意思?如果您的意思是希望脚本在任何命令失败时立即终止,那么只需这样做

set -e    # DON'T do this.  See commentary below.
在脚本的开头(但请注意下面的警告)。不要费心回显错误消息:让失败的命令处理它。换句话说,如果您这样做:

#!/bin/sh

set -e    # Use caution.  eg, don't do this
command1
command2
command3
command2失败,在向stderr打印错误消息时,您似乎已经实现了所需的功能。(除非我误解了你的意思!)

因此,您编写的任何命令都必须表现良好:它必须向stderr而不是stdout报告错误(问题中的示例代码将错误打印到stdout),并且在失败时必须以非零状态退出

然而,我不再认为这是一个好的实践。code>set-e在不同版本的bash中改变了它的语义,尽管它对于一个简单的脚本来说工作得很好,但是有太多的边缘情况,它基本上是不可用的。(考虑以下情况:
set-e;foo(){false;echo不应打印;};foo&&echo ok
这里的语义有些合理,但是如果您将代码重构为依赖于选项设置提前终止的函数,您很容易被咬到。)在我看来,最好编写:

 #!/bin/sh

 command1 || exit
 command2 || exit
 command3 || exit


不要创建运行程序函数或使用
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

陷阱甚至可以访问触发它的命令的行号和命令行。变量是
$BASH\u LINENO
$BASH\u COMMAND

我有一组脚本函数,在我的Red Hat系统上广泛使用。他们使用
/etc/init.d/functions
中的系统功能打印绿色
[OK]
和红色
[FAILED]
状态指示器

如果要记录哪些命令失败,可以选择将
$LOG\u STEPS
变量设置为日志文件名

用法 输出 代码
#/bin/bash
. /etc/init.d/functions
#使用step()、try()和next()执行一系列命令并打印
#最后是[确定]或[失败]。如果任何个人
#命令失败。
#
#例如:
#步骤“以读写方式重新安装/和/引导:”
#尝试挂载-o重新挂载,rw/
#尝试装载-o重新装载,rw/boot
#下一个
步骤({
echo-n“$@”
步骤_OK=0
[[-w/tmp]]&&echo$STEP\u OK>/tmp/STEP$$
}
try(){
#检查“%b”参数以在后台运行命令。
本地背景=
[$1==-b]&&{BG=1;shift;}
[$1==--]&&&{shift;}
#运行命令。
如果[[-z$BG]];则
"$@"
其他的
"$@" &
fi
#检查命令是否失败,如果失败,则更新$STEP\u OK。
本地出口代码=$?
如果[$EXIT_CODE-ne 0]];则
步骤\确定=$EXIT\代码
[[-w/tmp]]&&echo$STEP\u OK>/tmp/STEP$$
如果[[-n$LOG_步骤]];则
本地文件=$(readlink-m“${BASH_SOURCE[1]}”)
本地行=${BASH_LINENO[0]}
echo“$FILE:line$line:Command\`$*'失败,退出代码为$exit\u code。”>>“$LOG\u步骤”
fi
fi
返回$EXIT_代码
}
下一个(){
[[-f/tmp/step.$]&&{step_OK=$(
值得一提的是,编写代码检查每个命令是否成功的较短方法是:

command1 || echo "command1 borked it"
command2 || echo "command2 borked it"

这仍然很枯燥,但至少它是可读的。

很抱歉,我不能对第一个答案发表评论 但是您应该使用新实例来执行命令:cmd_output=$($@)


就我个人而言,我更喜欢使用轻量级的方法,如图所示

用法示例:

try apt-fast upgrade -y
try asuser vagrant "echo 'uname -a' >> ~/.profile"

我在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块嵌套在其内部

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__!"
}
代码是我的一部分。它进一步扩展了try&catch的思想,包括使用回溯和异常的错误处理(以及其他一些不错的特性)

下面是负责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
}

您可以随意使用、fork和contribution—它已启用。

另一种方法是将命令与
&&
连接在一起,以便第一个失败的命令阻止其余命令执行:

command1 &&
  command2 &&
  command3
这不是您在问题中要求的语法,但它是您描述的用例的常见模式。一般来说,这些命令应该负责打印失败,这样您就不必手动执行(可以使用
-q
标志在不需要错误时消除错误)。如果您有能力修改这些命令,我会对它们进行编辑,以便在失败时大声叫喊,而不是将它们包装在其他类似的东西中


还请注意,您不需要执行以下操作:

command1
if [ $? -ne 0 ]; then
你可以简单地说:

if ! command1; then
当您确实需要检查返回代码时,请使用算术上下文而不是
[…-ne

ret=$?
# do something
if (( ret != 0 )); then
对于偶然发现此线程的用户

foo
成为一个不“返回”(echo)值的函数,但它会像往常一样设置退出代码。
要避免在调用函数后检查
$status
,可以执行以下操作:

foo; and echo success; or echo failure
如果是
command1
if [ $? -ne 0 ]; then
if ! command1; then
ret=$?
# do something
if (( ret != 0 )); then
foo; and echo success; or echo failure
foo; and begin
  echo success
end; or begin
  echo failure
end
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
. /etc/init.d/functions
cmd1 succeed
cmd2 fail