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

jwb*_*ley 252 bash exit

是否有类似于pipefail的多个命令,比如'try'语句但在bash中.我想做这样的事情:

echo "trying stuff"
try {
    command1
    command2
    command3
}
Run Code Online (Sandbox Code Playgroud)

并且在任何时候,如果任何命令失败,则退出并回显该命令的错误.我不想做以下事情:

command1
if [ $? -ne 0 ]; then
    echo "command1 borked it"
fi

command2
if [ $? -ne 0 ]; then
    echo "command2 borked it"
fi
Run Code Online (Sandbox Code Playgroud)

等等......或类似的东西:

pipefail -o
command1 "arg1" "arg2" | command2 "arg1" "arg2" | command3
Run Code Online (Sandbox Code Playgroud)

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

krt*_*tek 270

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

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

mytest $command1
mytest $command2
Run Code Online (Sandbox Code Playgroud)

  • 另外,我会避免使用名称`test`,因为这是一个内置命令. (81认同)
  • 不要使用`$*`,如果任何参数中有空格,它将失败; 用"$ @"代替.同样,将`$ 1`放在`echo`命令的引号内. (30认同)
  • test()返回的退出代码在发生错误时始终返回0,因为上一个执行的命令是'echo'.您可能需要保存$的值?第一. (7认同)
  • 这不是一个好主意,它会鼓励不良做法。考虑一下“ ls”的简单情况。如果您调用`ls foo`并收到格式为`ls:foo的错误消息:没有这样的文件或目录,您就可以理解问题所在。相反,如果得到`ls:foo:ls \ n没有此类文件或目录\错误,则您会被多余的信息分散注意力。在这种情况下,很容易辩称多余是微不足道的,但是它很快就增长了。简洁的错误消息很重要。但是更重要的是,这种类型的包装器也鼓励编写者完全忽略良好的错误消息。 (2认同)

Wil*_*ell 182

你是什​​么意思"退出并回应错误"?如果您的意思是希望脚本在任何命令失败后立即终止,那么就这样做

set -e    # DON'T do this.  See commentary below.
Run Code Online (Sandbox Code Playgroud)

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

#!/bin/sh

set -e    # Use caution.  eg, don't do this
command1
command2
command3
Run Code Online (Sandbox Code Playgroud)

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

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

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

 #!/bin/sh

 command1 || exit
 command2 || exit
 command3 || exit
Run Code Online (Sandbox Code Playgroud)

要么

#!/bin/sh

command1 && command2 && command3
Run Code Online (Sandbox Code Playgroud)

  • 可以使用陷阱完成清理.(例如`trap some_func 0`将在退出时执行`some_func`) (6认同)
  • 另请注意,errexit(set -e)的语义在不同版本的bash中已更改,并且在函数调用和其他设置期间通常会出现意外行为.我不再推荐它的使用.IMO,最好写`|| 每个命令后显式退出`. (3认同)

Joh*_*ica 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
Run Code Online (Sandbox Code Playgroud)

产量

Installing XFS filesystem tools:        [  OK  ]
Configuring udev:                       [FAILED]
Adding rc.postsysinit hook:             [  OK  ]
Run Code Online (Sandbox Code Playgroud)

#!/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
}
Run Code Online (Sandbox Code Playgroud)

  • 这个工具有正式名称吗?我想阅读这种步骤/尝试/下一次记录的手册页 (2认同)

Joh*_*ica 50

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

command1 || echo "command1 borked it"
command2 || echo "command2 borked it"
Run Code Online (Sandbox Code Playgroud)

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

  • 要以静默方式执行命令并实现相同的功能:`command1&>/dev/null || echo"command1 borked it"` (3认同)
  • @AndreasKralj,是的,您可以运行一个班轮在失败后执行多个命令:command1 || { echo command1 打破它; 出口; 最后一个分号是必须的! (2认同)

dim*_*414 35

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

command1 &&
  command2 &&
  command3
Run Code Online (Sandbox Code Playgroud)

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


另请注意,您无需执行以下操作:

command1
if [ $? -ne 0 ]; then
Run Code Online (Sandbox Code Playgroud)

你可以简单地说:

if ! command1; then
Run Code Online (Sandbox Code Playgroud)

  • 谢谢`if!命令1; 然后` - 更简洁! (6认同)

Pau*_*ce. 31

而不是创建转轮功能或使用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
Run Code Online (Sandbox Code Playgroud)

陷阱甚至可以访问触发它的命令的行号和命令行.变量是$BASH_LINENO$BASH_COMMAND.

  • 如果你想更接近地模仿try块,使用`trap-ERR`在"块"结束时关闭陷阱. (4认同)

sle*_*cal 14

我个人更喜欢使用轻量级的方法,因为看到这里 ;

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }
asuser() { sudo su - "$1" -c "${*:2}"; }
Run Code Online (Sandbox Code Playgroud)

用法示例:

try apt-fast upgrade -y
try asuser vagrant "echo 'uname -a' >> ~/.profile"
Run Code Online (Sandbox Code Playgroud)


Eri*_*rik 8

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

run command1 && run command2 && run command3
Run Code Online (Sandbox Code Playgroud)

  • 不要运行`$*`,如果任何参数中有空格,它将失败; 用"$ @"代替.(虽然'echo`命令中的$*正常.) (6认同)

nii*_*ani 6

我在bash中开发了一个几乎完美无缺的try&catch实现,允许你编写如下代码:

try 
    echo 'Hello'
    false
    echo 'This will not be displayed'

catch 
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
Run Code Online (Sandbox Code Playgroud)

你甚至可以将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__!"
}
Run Code Online (Sandbox Code Playgroud)

代码是我的bash样板/框架的一部分.它进一步扩展了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
}
Run Code Online (Sandbox Code Playgroud)

随意使用,分叉和贡献 - 它在GitHub上.