如何在Bash中使用'set -e'时捕获ERR

irr*_*rom 27 bash interrupt-handling

我有一个简单的脚本:

#!/bin/bash
set -e
trap "echo BOO!" ERR 

function func(){
    ls /root/
}

func
Run Code Online (Sandbox Code Playgroud)

如果我的脚本失败,我想捕获ERR(因为它将在这里b/c我没有查看/ root的权限).但是,使用set -e它时不会被困住.没有set -eERR被困.

根据bash手册页,对于set -e:

... ERR上的陷阱(如果已设置)在shell退出之前执行....

为什么我的陷阱没有执行?从手册页看起来应该是这样.

mkl*_*nt0 39

chepner的回答是最好的解决办法:如果你想结合set -e(同:set -o errexit)与ERR陷阱,也可以使用set -o errtrace(同:set -E).

简而言之:set -eE用以代替set -e:

#!/bin/bash

set -eE  # same as: `set -o errexit -o errtrace`
trap 'echo BOO!' ERR 

function func(){
  ls /root/
}

# Thanks to -E / -o errtrace, this still triggers the trap, 
# even though the failure occurs *inside the function*.
func 
Run Code Online (Sandbox Code Playgroud)

man bash关于set -o errtrace/ set -E:

如果设置,则ERR上的任何陷阱都由shell函数,命令替换和子shell环境中执行的命令继承.在这种情况下,ERR陷阱通常不会被继承.

我相信发生了什么:

  • 没有 -e:ls命令在你的函数内失败,并且,由于是函数中的最后一个命令,函数ls会向调用者报告你的非零退出代码,即你的顶级脚本范围.在该范围内,ERR陷阱有效,并且它被调用(但请注意,执行将继续,除非您exit从陷阱显式调用).

  • 随着 -e(但没有-E):该ls命令失败你的函数里面,因为set -e在实际上,猛砸立即退出,直接从功能范围 -而因为没有ERR有效的陷阱存在(因为它没有从父作用域继承) ,你的陷阱没有被召唤.

虽然man页面不正确,但我同意这种行为并不十分明显 - 你必须推断它.


Sha*_*iri 14

我们有以下调试选项:

  • -e失败立即退出
  • -E如果设置,则 ERR 上的任何陷阱都会由 shell 函数继承
  • -u当有未绑定变量时退出
  • -o给出要设置的选项名称
    • pipelinefail 最后一个(最右边)命令的返回值(退出代码)
  • -v读取所有 shell 输入行时打印它们
  • -x打印命令跟踪

为了处理错误,我们可以使用trap捕获目录

trap 'echo >&2 "Error - exited with status $? at line $LINENO' ERR
Run Code Online (Sandbox Code Playgroud)

或者更好的版本参考

trap 'echo >&2 "Error - exited with status $? at line $LINENO:";
         pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7' ERR
Run Code Online (Sandbox Code Playgroud)

或者一个函数:

function __error_handing__(){
    local last_status_code=$1;
    local error_line_number=$2;
    echo 1>&2 "Error - exited with status $last_status_code at line $error_line_number";
    perl -slne 'if($.+5 >= $ln && $.-4 <= $ln){ $_="$. $_"; s/$ln/">" x length($ln)/eg; s/^\D+.*?$/\e[1;31m$&\e[0m/g;  print}' -- -ln=$error_line_number $0
}
Run Code Online (Sandbox Code Playgroud)

并这样称呼它:

trap  '__error_handing__ $? $LINENO' ERR
Run Code Online (Sandbox Code Playgroud)


che*_*ner 10

您需要使用set -o errtrace该函数来继承陷阱.


Rit*_*esh 7

替换ERREXIT,它将起作用。

trap命令的语法是:trap [COMMANDS] [SIGNALS]

有关更多信息,请阅读http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html

  • 虽然这是一个可行的解决方法(尽管不需要,如果使用了 `set -o errtrace`),您应该提到在 _successful_ 终止的情况下也会调用 `EXIT`,因此您需要添加一个条件,例如`如果 [[ $? -ne 0 ]] ...` 到处理程序。 (13认同)