Ant*_*ler 40 bash shell-script trap error-handling
我正在尝试使用 Trap 创建一些错误报告来调用所有错误的函数:
Trap "_func" ERR
Run Code Online (Sandbox Code Playgroud)
是否有可能获得 ERR 信号是从哪条线路发送的?外壳是 bash。
如果我这样做,我可以阅读和报告使用了什么命令并记录/执行一些操作。
或者也许我在这一切都错了?
我测试了以下内容:
#!/bin/bash
trap "ECHO $LINENO" ERR
echo hello | grep "asdf"
Run Code Online (Sandbox Code Playgroud)
并且$LINENO
正在返回 2. 不工作。
Mat*_*Mat 81
正如评论中指出的那样,您的引用是错误的。您需要单引号以防止$LINENO
在首次解析陷阱行时被扩展。
这有效:
#! /bin/bash
err_report() {
echo "Error on line $1"
}
trap 'err_report $LINENO' ERR
echo hello | grep foo # This is line number 9
Run Code Online (Sandbox Code Playgroud)
运行它:
$ ./test.sh
Error on line 9
Run Code Online (Sandbox Code Playgroud)
小智 27
您还可以使用 bash 内置的“caller”:
#!/bin/bash
err_report() {
echo "errexit on line $(caller)" >&2
}
trap err_report ERR
echo hello | grep foo
Run Code Online (Sandbox Code Playgroud)
它也打印文件名:
$ ./test.sh
errexit on line 9 ./test.sh
Run Code Online (Sandbox Code Playgroud)
unp*_*nic 13
我真的很喜欢上面@Mat 给出的答案。在此基础上,我写了一个小助手,它为错误提供了更多的上下文:
我们可以检查导致失败的行的脚本:
err() {
echo "Error occurred:"
awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR
Run Code Online (Sandbox Code Playgroud)
这是一个小的测试脚本:
#!/bin/bash
set -e
err() {
echo "Error occurred:"
awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR
echo one
echo two
echo three
echo four
false
echo five
echo six
echo seven
echo eight
Run Code Online (Sandbox Code Playgroud)
当我们运行它时,我们得到:
$ /tmp/test.sh
one
two
three
four
Error occurred:
12 echo two
13 echo three
14 echo four
15 >>>false
16 echo five
17 echo six
18 echo seven
Run Code Online (Sandbox Code Playgroud)
小智 8
是否有可能获得 ERR 信号是从哪条线路发送的?
是的,LINENO
和BASH_LINENO
变量是获取故障的线路,导致了它的线晚饭有用。
或者也许我在这一切都错了?
不,只是缺少-q
grep 选项...
echo hello | grep -q "asdf"
Run Code Online (Sandbox Code Playgroud)
......随着-q
选项grep
将返回0
的true
和1
为false
。而在 Bash 中,它trap
不是Trap
......
trap "_func" ERR
Run Code Online (Sandbox Code Playgroud)
......我需要一个本地解决方案......
这是一个陷阱,你可能会发现它对调试具有更多圈复杂度的事物很有用......
## Outputs Front-Mater formatted failures for functions not returning 0
## Use the following line after sourcing this file to set failure trap
## trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
failure(){
local -n _lineno="${1:-LINENO}"
local -n _bash_lineno="${2:-BASH_LINENO}"
local _last_command="${3:-${BASH_COMMAND}}"
local _code="${4:-0}"
## Workaround for read EOF combo tripping traps
if ! ((_code)); then
return "${_code}"
fi
local _last_command_height="$(wc -l <<<"${_last_command}")"
local -a _output_array=()
_output_array+=(
'---'
"lines_history: [${_lineno} ${_bash_lineno[*]}]"
"function_trace: [${FUNCNAME[*]}]"
"exit_code: ${_code}"
)
if [[ "${#BASH_SOURCE[@]}" -gt '1' ]]; then
_output_array+=('source_trace:')
for _item in "${BASH_SOURCE[@]}"; do
_output_array+=(" - ${_item}")
done
else
_output_array+=("source_trace: [${BASH_SOURCE[*]}]")
fi
if [[ "${_last_command_height}" -gt '1' ]]; then
_output_array+=(
'last_command: ->'
"${_last_command}"
)
else
_output_array+=("last_command: ${_last_command}")
fi
_output_array+=('---')
printf '%s\n' "${_output_array[@]}" >&2
exit ${_code}
}
Run Code Online (Sandbox Code Playgroud)
...以及一个示例使用脚本,用于揭示如何为函数跟踪设置上述陷阱的细微差异...
#!/usr/bin/env bash
set -E -o functrace
## Optional, but recommended to find true directory this script resides in
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
__SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"
## Source module code within this script
source "${__DIR__}/modules/trap-failure/failure.sh"
trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
something_functional() {
_req_arg_one="${1:?something_functional needs two arguments, missing the first already}"
_opt_arg_one="${2:-SPAM}"
_opt_arg_two="${3:0}"
printf 'something_functional: %s %s %s' "${_req_arg_one}" "${_opt_arg_one}" "${_opt_arg_two}"
## Generate an error by calling nothing
"${__DIR__}/nothing.sh"
}
## Ignoring errors prevents trap from being triggered
something_functional || echo "Ignored something_functional returning $?"
if [[ "$(something_functional 'Spam!?')" == '0' ]]; then
printf 'Nothing somehow was something?!\n' >&2 && exit 1
fi
## And generating an error state will cause the trap to _trace_ it
something_functional '' 'spam' 'Jam'
Run Code Online (Sandbox Code Playgroud)
以上是在 Bash 版本 4+ 上测试的,所以如果需要 4 之前版本的某些东西,请留下评论,或者如果它无法在最低版本为 4 的系统上捕获故障,请打开一个问题。
主要的收获是...
set -E -o functrace
Run Code Online (Sandbox Code Playgroud)
-E
导致函数内的错误冒泡
-o functrace
当函数中的某些内容失败时,原因允许更详细
trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
Run Code Online (Sandbox Code Playgroud)
单引号用于函数调用,双引号用于单个参数
传递对LINENO
和 的引用BASH_LINENO
而不是当前值,尽管这可能会在链接到陷阱的后续版本中缩短,以便最终的失败行使其成为输出
传递BASH_COMMAND
和退出状态 ( $?
) 的值,首先获取返回错误的命令,其次确保陷阱不会在非错误状态下触发
虽然其他人可能不同意,但我发现构建输出数组并使用 printf 在它自己的行上打印每个数组元素更容易......
printf '%s\n' "${_output_array[@]}" >&2
Run Code Online (Sandbox Code Playgroud)
...>&2
最后的位也会导致错误到他们应该去的地方(标准错误),并允许只捕获错误......
## ... to a file...
some_trapped_script.sh 2>some_trapped_errros.log
## ... or by ignoring standard out...
some_trapped_script.sh 1>/dev/null
Run Code Online (Sandbox Code Playgroud)
正如Stack Overflow 上的这些和其他示例所示,有很多方法可以使用内置实用程序构建调试辅助工具。
这是另一个版本,灵感来自 @sanmai 和 @unpythonic。它显示错误周围的脚本行、行号和退出状态 - 使用 tail 和 head,因为这似乎比 awk 解决方案更简单。
为了便于阅读,将其显示为两行 - 如果您愿意,可以将这些行合并为一行(保留;
):
trap 'echo >&2 "Error - exited with status $? at line $LINENO:";
pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7 >&2' ERR
Run Code Online (Sandbox Code Playgroud)
set -eEuo pipefail
这与(非官方严格模式)配合得很好
ERR
伪信号,但其他情况确实会显示上下文。输出示例:
myscript.sh: line 27: blah: command not found
Error - exited with status 127 at line 27:
24 # Do something
25 lines=$(wc -l /etc/passwd)
26 # More stuff
27 blah
28
29 # Check time
30 time=$(date)
Run Code Online (Sandbox Code Playgroud)