如何在 Linux 上捕获 Bash 脚本中的错误

已发表: 2022-08-27
显示 bash 提示符的 Linux 笔记本电脑
fatmawati achmad zaenuri/Shutterstock.com

默认情况下,Linux 上的 Bash 脚本会报告错误但会继续运行。 我们将向您展示如何自己处理错误,以便您决定接下来需要发生什么。

脚本中的错误处理

处理错误是编程的一部分。 即使您编写了完美的代码,您仍然可能会遇到错误情况。 随着您安装和卸载软件、创建目录以及执行升级和更新,计算机上的环境会随着时间而变化。

在 PowerShell 中自定义参数验证错误
相关在 PowerShell 中自定义参数验证错误

例如,如果目录路径更改或文件的权限发生更改,以前可以正常运行的脚本可能会遇到困难。 Bash shell 的默认操作是打印错误消息并继续执行脚本。 这是一个危险的默认值。

如果失败的操作对脚本中稍后发生的某些其他处理或操作至关重要,则该关键操作将不会成功。 结果是多么灾难性,取决于您的脚本试图做什么。

更强大的方案将检测错误并让脚本在需要关闭或尝试修复故障条件时运行。 例如,如果缺少目录或文件,让脚本重新创建它们可能会令人满意。

如果脚本遇到无法恢复的问题,它可以关闭。 如果脚本必须关闭,它可以有机会执行任何需要的清理工作,例如删除临时文件或将错误条件和关闭原因写入日志文件。

检测退出状态

命令和程序会生成一个值,在它们终止时发送给操作系统。 这称为他们的退出状态。 如果没有错误,则它的值为零,如果发生错误,则它具有一些非零值。

我们可以检查脚本使用的命令的退出状态(也称为返回码),并确定命令是否成功。

在 Bash 中,零等于真。 如果命令的响应不是真的,我们就知道发生了问题,我们可以采取适当的措施。

将此脚本复制到编辑器中,并将其保存到名为“bad_command.sh”的文件中。

 #!/bin/bash

如果(!bad_command); 然后
  echo "bad_command 标记了一个错误。"
  1号出口
菲

您需要使用chmod命令使脚本可执行。 这是使任何脚本可执行所需的步骤,因此如果您想在自己的机器上试用这些脚本,请记住对每个脚本都执行此操作。 在每种情况下替换相应脚本的名称。

 chmod +x bad_command.sh 

使用 chmod 使脚本可执行

当我们运行脚本时,我们会看到预期的错误消息。

 ./bad_command.sh 

检查命令的退出状态以确定是否有错误

没有像“bad_command”这样的命令,也不是脚本中的函数名称。 它无法执行,因此响应为零。 如果响应不为零(此处使用感叹号作为逻辑NOT运算符),则执行if语句的主体。

在现实世界的脚本中,这可能会终止脚本,就像我们的示例所做的那样,或者它可能会尝试修复故障条件。

看起来exit 1线是多余的。 毕竟,脚本中没有其他内容,无论如何它都会终止。 但是使用exit命令可以让我们将退出状态传回给 shell。 如果我们的脚本曾经在第二个脚本中被调用,那么第二个脚本就会知道这个脚本遇到了错误。

您可以将逻辑OR运算符与命令的退出状态一起使用,如果第一个命令有非零响应,则可以在脚本中调用另一个命令或函数。

 命令_1 || 命令_2

这是有效的,因为第一个命令运行OR第二个。 首先运行最左边的命令。 如果成功,则不执行第二个命令。 但如果第一个命令失败,则执行第二个命令。 所以我们可以像这样构造代码。 这是“逻辑或./sh”。

 #!/bin/bash

错误处理程序()
{
  回声“错误:($?)$ 1”
  1号出口
}

坏命令 || error_handler "bad_command 失败,行:${LINENO}"

我们定义了一个名为error_handler的函数。 这将打印出失败命令的退出状态,保存在变量$? 以及在调用函数时传递给它的一行文本。 这保存在变量$1中。 该函数以退出状态 1 终止脚本。

该脚本尝试运行bad_command显然失败了,所以逻辑OR运算符右侧的命令|| , 被执行。 这将调用error_handler函数并传递一个字符串,该字符串命名失败的命令,并包含失败命令的行号。

我们将运行脚本以查看错误处理程序消息,然后使用 echo 检查脚本的退出状态。

 ./逻辑或.sh
 回声$? 

使用逻辑 OR 运算符调用脚本中的错误处理程序

我们的小error_handler函数提供了尝试运行bad_command的退出状态、命令的名称和行号。 在调试脚本时,这是有用的信息。

脚本的退出状态为 1。 error_handler报告的 127 退出状态表示“找不到命令”。 如果我们愿意,我们可以通过将其传递给exit命令,将其用作脚本的退出状态。

另一种方法是扩展error_handler以检查退出状态的不同可能值并相应地执行不同的操作,使用这种类型的构造:

 退出代码=$?

如果 [ $exit_code -eq 1 ]; 然后
  echo "不允许操作"

elif [ $exit_code -eq 2 ]; 然后
  echo "滥用 shell 内置函数"
.
.
.
elif [ $status -eq 128 ]; 然后
  echo "参数无效"
菲

使用 set 强制退出

如果您知道希望脚本在出现错误时退出,您可以强制它这样做。 这意味着您放弃了任何清理或任何进一步损坏的机会,因为您的脚本一旦检测到错误就会终止。

为此,请使用带有-e (错误)选项的set命令。 这告诉脚本在命令失败或返回大于零的退出代码时退出。 此外,使用-E选项可确保错误检测和捕获在 shell 函数中起作用。

如何在 Linux 上的 Bash 脚本中使用 set 和 pipefail
相关如何在 Linux 上的 Bash 脚本中使用 set 和 pipefail

要同时捕获未初始化的变量,请添加-u (未设置)选项。 要确保在管道序列中检测到错误,请添加-o pipefail选项。 没有这个,管道命令序列的退出状态就是序列中最后一个命令的退出状态。 管道序列中间的失败命令将不会被检测到。 -o pipefail选项必须出现在选项列表中。

添加到脚本顶部的序列是:

 设置-Eeuo pipefail

这是一个名为“unset-var.sh”的简短脚本,其中包含一个未设置的变量。

 #!/bin/bash

设置-Eeou pipefail

回声“$ unset_variable”

echo "我们看到这条线了吗?"

当我们运行脚本时, unset_variable 被识别为一个未初始化的变量并且脚本被终止。

 ./unset-var.sh 

如果发生错误,在脚本中使用 set 命令终止脚本

第二个echo命令永远不会执行。

使用有错误的陷阱

Bash 陷阱命令允许您指定一个命令或函数,当特定信号出现时应该调用它。 通常,这用于捕获信号,例如当您按下 Ctrl+C 组合键时引发的SIGINT 。 这个脚本是“sigint.sh”。

 #!/bin/bash

陷阱 "echo -e '\n由 Ctrl+c' 终止;退出" SIGINT

计数器=0

虽然是真的
做 
  echo "循环编号:" $((++counter))
  睡觉 1
完毕

trap命令包含echo命令和exit命令。 当SIGINT被提升时,它将被触发。 脚本的其余部分是一个简单的循环。 如果您运行脚本并按 Ctrl+C,您将看到来自trap定义的消息,并且脚本将终止。

 ./sigint.sh 

在脚本中使用陷阱捕获 Ctrl+c

我们可以使用带有ERR信号的trap来捕获发生的错误。 然后可以将它们馈送到命令或函数。 这是“trap.sh”。 我们正在向一个名为error_handler的函数发送错误通知。

 #!/bin/bash

陷阱'error_handler $? $LINENO' 错误

错误处理程序(){
  echo "错误:($1) 发生在 $2"
}

主要的() {
  echo "main() 函数内部"
  坏命令
  第二
  第三
  退出 $?
}

第二() {
  echo "调用 main() 后"
  echo "在 second() 函数内"
}

第三() {
  echo "inside third() 函数"
}

主要的

大部分脚本在main函数内部,它调用secondthird函数。 当遇到错误时——在这种情况下,因为bad_command不存在—— trap语句将错误定向到error_handler函数。 它将失败命令的退出状态和行号传递给error_handler函数。

 ./trap.sh 

使用带有 ERR 的陷阱来捕获脚本中的错误

我们的error_handler函数只是将错误的详细信息列出到终端窗口。 如果需要,可以向函数添加exit命令以终止脚本。 或者您可以使用一系列if/elif/fi语句来针对不同的错误执行不同的操作。

可能可以纠正一些错误,其他错误可能需要脚本停止。

最后的提示

捕获错误通常意味着先发制人地发现可能出错的事情,并在它们出现时放入代码来处理这些不测事件。 这是除了确保脚本的执行流程和内部逻辑正确之外。

如果您使用此命令运行脚本,Bash 将在脚本执行时向您显示跟踪输出:

 bash -x 你的脚本.sh

Bash 在终端窗口中写入跟踪输出。 它显示每个命令及其参数——如果有的话。 这发生在命令扩展之后但在它们执行之前。

它可以极大地帮助追踪难以捉摸的错误。

相关:如何在运行之前验证 Linux Bash 脚本的语法