使用 Trap 处理错误

ShellBeginner
立即练习

简介

脚本运行时经常会创建临时文件或启动后台进程。如果用户中断脚本(例如按下 Ctrl+C),这些临时文件通常会残留,造成系统杂乱。

为避免这种情况,脚本需要能够检测自身何时即将停止,并执行必要的清理操作。

在 Linux 中,我们使用 trap 命令捕获中断信号和退出事件。信号是操作系统与进程通信的方式之一。例如,按下 Ctrl+C 通常会发送 SIGINT,请求进程停止。

这样,我们就可以定义一个「退出时」执行的过程,让脚本在终止前完成自身清理。

在本实验中,你将:

  1. 创建一个脚本,生成临时文件。
  2. 中断脚本,查看残留文件。
  3. 实现 trap,在脚本退出时自动删除临时文件。

创建长时间运行的进程

首先,创建一个模拟长时间运行任务的脚本。该脚本会创建一个临时文件,然后无限等待。

长时间运行的脚本在系统管理工作中很常见。它们可能会监视日志、等待输入、轮询服务,或持续处理任务。脚本运行的时间越长,被中断时正确清理资源就越重要。

在项目目录中创建并打开 long_process.sh:

cd ~/project
nano long_process.sh

输入以下代码:

#!/bin/bash

## Create a temporary file
touch temp_data.txt
echo "Temporary file 'temp_data.txt' created."

## Fall asleep (Simulate a long job)
echo "Process is running. Press Ctrl+C to stop..."
while true; do
  sleep 1
done

保存(Ctrl+O,然后按 Enter)并退出(Ctrl+X)。

为脚本添加可执行权限:

chmod +x long_process.sh

问题:如果运行此脚本,然后使用 Ctrl+C 停止它,文件 temp_data.txt 仍会保留在磁盘上。这是不好的实践,因为反复运行脚本可能会产生大量无用文件,最终占满磁盘空间。

临时文件只是一个典型例子。同样的问题也可能出现在锁文件、套接字、挂载点或后台辅助进程上。清理的核心目标,是让系统始终保持在可预期的状态。

定义清理函数

为了正确处理清理操作,最佳实践是定义一个专用函数,将所有清理逻辑集中在其中。

将清理逻辑放在单独的函数中,可以使脚本结构更清晰。这样,不同的退出路径都可以复用同一套清理逻辑,而不必在多个位置重复编写。

再次打开脚本:

nano long_process.sh

在脚本顶部添加 cleanup 函数:

#!/bin/bash

## Define the cleanup function
cleanup() {
  echo ""
  echo "Cleaning up temporary files..."
  rm -f temp_data.txt
  echo "Cleanup complete."
}

## Create a temporary file
touch temp_data.txt
echo "Temporary file 'temp_data.txt' created."

## Simulate a long-running process
echo "Process is running. Press Ctrl+C to stop..."
while true; do
  sleep 1
done

保存并退出。

此时,我们已经定义了函数,但还没有告诉脚本何时调用它。

设置 Trap 捕获信号

现在使用 trap 命令告诉 Bash:「如果收到中断信号(例如 Ctrl+C),就运行 cleanup 函数。」

这是 Shell 脚本响应操作系统事件的典型示例,而不仅仅是处理文本输入。信号是系统或用户异步发送给进程的消息,而 trap 让脚本有机会主动做出响应。

打开脚本:

nano long_process.sh

将 trap 命令添加到函数定义下方、主要逻辑开始之前。

#!/bin/bash

cleanup() {
  echo ""
  echo "Cleaning up temporary files..."
  rm -f temp_data.txt
  echo "Cleanup complete."
  exit
}

## Register the trap
## SIGINT = Ctrl+C
## EXIT = Any exit (success or failure)
trap cleanup SIGINT EXIT

## The rest of the script...
touch temp_data.txt
echo "Temporary file 'temp_data.txt' created."

echo "Process is running. Press Ctrl+C to stop..."
while true; do
  sleep 1
done

**理解 trap arguments**:

  • cleanup:要运行的命令或函数。
  • SIGINT:按下 Ctrl+C 时发送的信号。
  • EXIT:一种伪信号,脚本因任何原因退出时都会触发。

同时使用 SIGINT 和 EXIT 可以让清理操作更加可靠。SIGINT 负责处理常见的键盘中断,EXIT 则为正常终止和许多失败情况提供更通用的保障。

保存并退出。

验证清理操作

现在验证清理机制是否正常工作。

运行脚本:

./long_process.sh

脚本会创建 temp_data.txt,然后开始运行。

现在按下 Ctrl+C 中断脚本。

你应该会看到以下输出:

^C
Cleaning up temporary files...
Cleanup complete.

最后,检查文件是否已删除:

ls temp_data.txt

命令应该返回类似以下错误:

ls: cannot access 'temp_data.txt': No such file or directory

这说明 trap 命令成功拦截了停止信号,并在脚本终止前执行了清理代码。

这是专业脚本编写中的一个良好习惯:如果脚本创建了临时状态,就应该同时定义如何删除这些状态。trap 是 Bash 为此提供的主要工具之一。

总结

在本实验中,你学习了如何使用 Trap 编写更健壮的脚本。

  1. 信号:理解 Ctrl+C 会向进程发送 SIGINT 信号。
  2. Trap:使用 trap 捕获信号并执行指定代码。
  3. 清理:自动执行维护任务,保持系统整洁。

专业脚本总会处理各种退出场景,以避免数据损坏和文件残留。