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

How to trap ERR when using 'set -e' in Bash

我有一个简单的脚本:

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

function func(){
    ls /root/
}

func

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

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

... A trap on ERR, if set, is executed before the shell exits. ...

为什么我的陷阱没有被执行?从手册页看来应该如此。

ERR 替换为 EXIT 即可。

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

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

是最好的方案:如果要结合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 

一个更复杂的示例trap 以红色打印消息并打印退出代码的示例:
trap 'printf "\e[31m%s: %s\e[m\n" "BOO!" $?' ERR


man bash 表示 set -o errtrace / set -E:

If set, any trap on ERR is inherited by shell functions, command substitutions, and commands executed in a subshell environment. The ERR trap is normally not inherited in such cases.

我认为正在发生的事情:

  • Without -els 命令在您的函数中失败,并且由于是函数中的最后一个命令,该函数向调用者(您的顶级脚本范围)报告 ls 的非零退出代码。在那个作用域中,ERR陷阱生效,并被调用(但请注意,执行将继续,除非你明确地从陷阱中调用exit) .

  • -e(但没有-E):ls命令在里面失败你的函数,因为set -e生效,Bash立即退出,直接从函数范围 - 因为没有 ERR 陷阱生效 那里 (因为它不是从父范围继承的),你的陷阱不会被调用。

虽然 man 页面没有错误,但我同意这种行为并不十分明显 - 您必须进行推断。

继承陷阱的功能需要使用set -o errtrace

我们有这些调试选项:

  • -e失败立即退出
  • -E 如果设置,ERR 上的任何陷阱都会被 shell 函数继承
  • -u有未绑定变量时退出
  • -o给个option-name设置
    • pipefail 最后一个(最右边)命令(退出代码)的 return 值
  • -v 在读取时打印所有 shell 输入行
  • -x 打印命令跟踪

为了处理错误,我们可以使用 trap

捕获目录
trap 'echo >&2 "Error - exited with status $? at line $LINENO' ERR

或更好的版本ref :

trap 'echo >&2 "Error - exited with status $? at line $LINENO:";
         pr -tn [=11=] | tail -n+$((LINENO - 3)) | head -n7' ERR

或者一个函数:

function __error_handing__(){
    local last_status_code=;
    local error_line_number=;
    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 [=12=]
}

并这样称呼它:

trap  '__error_handing__ $? $LINENO' ERR