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

时间:2011-03-04 15:19:58

标签: 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 ]; then
    echo "command2 borked it"
fi

依此类推......或类似的东西:

pipefail -o
command1 "arg1" "arg2" | command2 "arg1" "arg2" | command3

因为我相信每个命令的参数(如果我错了,请纠正我)会相互干扰。这两种方法对我来说似乎非常啰嗦和讨厌,所以我在这里呼吁采用更有效的方法。

15 个答案:

答案 0 :(得分:259)

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

function mytest {
    "$@"
    local status=$?
    if [ $status -ne 0 ]; then
        echo "error with $1" >&2
    fi
    return $status
}

mytest $command1
mytest $command2

答案 1 :(得分:179)

“辍学并回应错误”是什么意思?如果您的意思是希望脚本在任何命令失败后立即终止,那么只需执行

set -e

在脚本的开头(但在下面注意警告)。不要打扰回显错误消息:让失败的命令处理它。换句话说,如果你这样做:

#!/bin/sh

set -e    # Use caution.  eg, don't do this
command1
command2
command3

和command2失败,在向stderr打印错误消息时,似乎你已经达到了你想要的效果。 (除非我误解了你想要的东西!)

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

然而,我不再认为这是一个好习惯。 set -e已经使用不同版本的bash更改了它的语义,虽然它适用于简单的脚本,但是有很多边缘情况它基本上无法使用。 (考虑一下这样的事情:set -e; foo() { false; echo should not print; } ; foo && echo ok这里的语义有些合理,但如果你将代码重构为依赖于选项设置提前终止的函数,你很容易被咬掉。)IMO最好写一下: / p>

 #!/bin/sh

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

#!/bin/sh

command1 && command2 && command3

答案 2 :(得分:86)

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

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

用法

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
}

答案 3 :(得分:50)

对于它的价值,编写代码以检查每个命令是否成功的简短方法是:

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

它仍然很乏味,但至少它是可读的。

答案 4 :(得分:35)

另一种方法是简单地将命令与&&一起加入,以便第一个失败防止其余的执行:

command1 &&
  command2 &&
  command3

这不是您在问题中要求的语法,但它是您描述的用例的常见模式。一般情况下,命令应该负责打印失败,因此您不必手动执行此操作(可能带有-q标记,以便在您不想要时将错误消除)。如果你能够修改这些命令,我​​就会编辑它们以便在失败时大喊大叫,而不是将它们包装在其他类似的命令中。

另请注意,您不需要这样做:

command1
if [ $? -ne 0 ]; then

你可以简单地说:

if ! command1; then

答案 5 :(得分:30)

使用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_LINENO$BASH_COMMAND

答案 6 :(得分:14)

我个人更喜欢使用轻量级方法,如here;

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"

答案 7 :(得分:8)

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

run command1 && run command2 && run command3

答案 8 :(得分:6)

我已经开发了几乎完美的尝试&amp;在bash中捕获实现,允许您编写如下代码:

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__!"
}

代码是我bash boilerplate/framework的一部分。它进一步扩展了try&amp; amp;通过回溯和异常(以及其他一些不错的功能)来处理错误处理等事情。

这是代码,它只对try&amp;捉:

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

答案 9 :(得分:3)

很抱歉,我无法对第一个答案发表评论 但是您应该使用新实例来执行命令: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

答案 10 :(得分:2)

对于偶然发现此帖子的fish shell个用户。

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

foo; and echo success; or echo failure

如果它太长而无法放在一条线上:

foo; and begin
  echo success
end; or begin
  echo failure
end

答案 11 :(得分:1)

当我使用ssh时,我需要区分由errexitset -e)模式下的连接问题和远程命令的错误代码引起的问题。我使用以下函数:

# 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

答案 12 :(得分:1)

您可以在非RedHat系统上使用@ john-kugelman的awesome solution,方法是在其代码中对此行进行注释:

. /etc/init.d/functions

然后,将以下代码粘贴到最后。完全公开:这只是从Centos 7提取的上述文件的相关位的直接复制和粘贴。

在MacOS和Ubuntu 18.04上进行了测试。


BOOTUP=color
RES_COL=60
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \\033[1;32m"
SETCOLOR_FAILURE="echo -en \\033[1;31m"
SETCOLOR_WARNING="echo -en \\033[1;33m"
SETCOLOR_NORMAL="echo -en \\033[0;39m"

echo_success() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_SUCCESS
    echo -n $"  OK  "
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 0
}

echo_failure() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo -n $"FAILED"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 1
}

echo_passed() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo -n $"PASSED"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 1
}

echo_warning() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo -n $"WARNING"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 1
} 

答案 13 :(得分:0)

以功能方式检查状态

train_op2

用法:

grad_var1 = optimizer.compute_gradients(loss_function1, var_list=<list of first layer variables>)
grad_var2 = optimizer.compute_gradients(loss_function2, var_list=<list of second layer variables>)
with tf.control_dependencies([grad_var1, grad_var2]):
    train_op1 = optimizer.apply_gradients(grad_var1)
    train_op2 = optimizer.apply_gradients(grad_var2)
train_op = tf.group(train_op1, train_op2)

输出

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)

}

答案 14 :(得分:0)

假设

alias command1='grep a <<<abc'
alias command2='grep x <<<abc'
alias command3='grep c <<<abc'

要么

{ command1 1>/dev/null || { echo "cmd1 fail"; /bin/false; } } && echo "cmd1 succeed" &&
{ command2 1>/dev/null || { echo "cmd2 fail"; /bin/false; } } && echo "cmd2 succeed" &&
{ command3 1>/dev/null || { echo "cmd3 fail"; /bin/false; } } && echo "cmd3 succeed"

{ { command1 1>/dev/null && echo "cmd1 succeed"; } || { echo "cmd1 fail"; /bin/false; } } &&
{ { command2 1>/dev/null && echo "cmd2 succeed"; } || { echo "cmd2 fail"; /bin/false; } } &&
{ { command3 1>/dev/null && echo "cmd3 succeed"; } || { echo "cmd3 fail"; /bin/false; } }

收益

cmd1 succeed
cmd2 fail

很乏味。但可读性还不错。