当 Bash 发生错误时,如何找到行号?

当 Bash 发生错误时,如何找到行号?

如何找到 Bash 中发生错误的行号?

例子

我创建了以下带有行号的简单脚本来解释我们需要什么。该脚本将从以下位置复制文件

cp $file1 $file2
cp $file3 $file4

当其中一个cp命令失败时,该函数将退出1号出口。我们希望向该函数添加打印错误及行号(例如 8 或 12)的功能。

这可能吗?

示例脚本

1 #!/bin/bash
2
3
4 function in_case_fail {
5 [[ $1 -ne 0 ]] && echo "fail on $2" && exit 1
6 }
7
8 cp $file1 $file2
9 in_case_fail $? "cp $file1 $file2"
10
11
12 cp $file3 $file4
13 in_case_fail $? "cp $file3 $file4"
14

答案1

我不会使用你的函数,而是使用这个方法:

$ cat yael.bash
#!/bin/bash

set -eE -o functrace

file1=f1
file2=f2
file3=f3
file4=f4

failure() {
  local lineno=$1
  local msg=$2
  echo "Failed at $lineno: $msg"
}
trap 'failure ${LINENO} "$BASH_COMMAND"' ERR

cp -- "$file1" "$file2"
cp -- "$file3" "$file4"

这是通过捕获 ERR 然后failure()使用当前行号 + 执行的 bash 命令调用该函数来实现的。

例子

在这里,我没有注意创建文件f1f2f3f4。当我运行上面的脚本时:

$ ./yael.bash
cp: cannot stat ‘f1’: No such file or directory
Failed at 17: cp -- "$file1" "$file2"

它失败,报告行号加上已执行的命令。

答案2

除了LINENO包含当前行号之外,还有BASH_LINENOand FUNCNAME(and BASH_SOURCE) 数组包含函数名称和调用它们的行号。

所以你可以这样做:

#!/bin/bash

error() {
        printf "'%s' failed with exit code %d in function '%s' at line %d.\n" "${1-something}" "$?" "${FUNCNAME[1]}" "${BASH_LINENO[0]}"
}

foo() {
        ( exit   0 ) || error "this thing"
        ( exit 123 ) || error "that thing"
}

foo

运行会打印

'that thing' failed with exit code 123 in function 'foo' at line 9.

如果您使用set -e, 或trap ... ERR来自动检测错误,请注意它们有一些警告。包含脚本当时正在执行的操作的描述也更困难(就像您在示例中所做的那样),尽管这对于普通用户来说可能比行号更有用。

例如,请参阅以下问题set -e以及其他问题:

答案3

Bash 有一个内置变量$LINENO,在语句中时它会被当前行号替换,所以你可以这样做

in_case_fail $? "at $LINENO: cp $file1 $file2"

您还可以尝试使用trap ... ERR命令失败时运行的命令(如果结果未经测试)。例如:

trap 'rc=$?; echo "error code $rc at $LINENO"; exit $rc' ERR

然后,如果类似的命令cp $file1 $file2失败,您将收到带有行号和退出的错误消息。您还会发现变量中的命令有错误$BASH_COMMAND(尽管没有任何重定向等)。

答案4

我决定使用以下内容,其中包括堆栈跟踪,就好像list of fns [list of line numbers]脚本位于函数中一样,否则包含脚本行号。还要感谢之前的答案,我在经过一些实验来处理基于 fn 和非 fn 的脚本后对其进行了扩展。

set -eE -o functrace

failure() {
  local lineno=$2
  local fn=$3
  local exitstatus=$4
  local msg=$5
  local lineno_fns=${1% 0}
  if [[ "$lineno_fns" != "0" ]] ; then
    lineno="${lineno} ${lineno_fns}"
  fi
  echo "${BASH_SOURCE[1]}:${fn}[${lineno}] Failed with status ${exitstatus}: $msg"
}
trap 'failure "${BASH_LINENO[*]}" "$LINENO" "${FUNCNAME[*]:-script}" "$?" "$BASH_COMMAND"' ERR

所以一个简单的命令失败看起来像:

/tmp/b:script[32] Failed with status 1: cp fred john

和嵌套函数(其中 hello1 调用 hello2):

/tmp/b:hello2 hello1 main[24 19 29] Failed with status 1: cp john 22

我偶尔会报告退出状态,它会为您提供额外的信息,并且与其他人不同的是,我想要脚本的完整路径名。

需要进一步的工作来报告因信号而退出的情况。

相关内容