首页 文章

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

提问于
浏览
239

是否有类似于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

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

13 回答

  • 14

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

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

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

    对于偶然发现此主题的fish shell用户 .

    foo 是一个没有"return"(echo)值的函数,但它像往常一样设置退出代码 .
    为避免在调用函数后检查 $status ,您可以执行以下操作:

    foo; and echo success; or echo failure
    

    如果它太长而不适合在一条线上:

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

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

    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最好写:

    #!/bin/sh
    
     command1 || exit
     command2 || exit
     command3 || exit
    

    要么

    #!/bin/sh
    
    command1 && command2 && command3
    
  • 177

    我有一套脚本功能,我在我的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
    }
    
  • 1

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

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

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

  • 30

    另一种方法是简单地将命令与 && 连接在一起,这样第一个失败就会阻止其余的执行:

    command1 &&
      command2 &&
      command3
    

    这不是您描述的用例的常见模式 . 一般情况下,命令应该负责打印失败,这样你就不必手动执行(当你没有编辑它们以便在失败时大喊大叫时,可能会使用 -q 标记来消除错误,而不是将它们包含在其他内容中这样做 .


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

    command1
    if [ $? -ne 0 ]; then
    

    你可以简单地说:

    if ! command1; then
    
  • 3

    而不是创建转轮功能或使用 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 .

  • 0

    我个人更喜欢使用轻量级方法,如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"
    
  • 255
    run() {
      $*
      if [ $? -ne 0 ]
      then
        echo "$* failed with exit code $?"
        return 1
      else
        return 0
      fi
    }
    
    run command1 && run command2 && run command3
    
  • 6

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

    代码是我bash boilerplate/framework的一部分 . 它进一步扩展了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
    }
    

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

  • 50

    当我使用 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
    
  • 2

    以功能方式检查状态

    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
    

相关问题