“Exit Traps”如何使您的 Bash 脚本更加健壮和可靠
有一个简单、有用的习惯用法可以使您的 bash 脚本更加健壮——确保它们始终执行必要的清理操作,即使出现意外错误也是如此。秘诀是 bash 提供的伪信号,称为 EXIT,您可以捕获它;当脚本因任何原因退出时,捕获在其上的命令或函数将执行。让我们看看它是如何工作的。
基本的代码结构是这样的:
#!/bin/bash
function finish {
# Your cleanup code here
}
trap finish EXIT
您可以在这个“finish”函数中放置任何您希望确保运行的代码。一个很好的常见示例:创建一个临时目录,然后删除它。
#!/bin/bash
scratch=$(mktemp -d -t tmp.XXXXXXXXXX)
function finish {
rm -rf "$scratch"
}
trap finish EXIT
将此与您如何在没有陷阱的情况下删除临时目录进行比较:
#!/bin/bash
# DON'T DO THIS!
scratch=$(mktemp -d -t tmp.XXXXXXXXXX)
# Insert dozens or hundreds of lines of code here...
# All done, now remove the directory before we exit
rm -rf "$scratch"
这有什么问题吗?
- 如果某些错误导致脚本过早退出,则暂存目录及其内容不会被删除。这是资源泄漏,也可能具有安全隐患。
- 如果脚本设计为在结束前退出,则必须在每个退出点手动复制 'n 粘贴 rm 命令。
- 也存在可维护性问题。如果您稍后添加一个新的脚本内退出,很容易忘记包括删除 - 可能会造成神秘的海森泄密。
无论如何都要保持服务正常
另一种情况:假设您正在自动执行一些系统管理任务,需要您暂时停止服务器并且您希望确定它在最后再次启动,即使存在一些运行时错误。那么方案是:
function finish {
# re-start service
sudo /etc/init.d/something start
}
trap finish EXIT
sudo /etc/init.d/something stop
# Do the work...
# Allow the script to end and the trapped finish function to start the
# daemon back up.
一个具体的例子:假设你有 MongoDB 在 Ubuntu 服务器上运行,并且想要一个 cronned 脚本来暂时停止一些常规维护任务的进程。处理方法是:
function finish {
# re-start service
sudo service mongdb start
}
trap finish EXIT
# Stop the mongod instance
sudo service mongdb stop
# (If mongod is configured to fork, e.g. as part of a replica set, you
# may instead need to do "sudo killall --wait /usr/bin/mongod".)
限制昂贵的资源
exit trap 在另一种情况下非常有用:如果您的脚本启动一个昂贵的资源,仅在脚本执行时才需要,并且您希望确保它在完成后释放该资源。例如,假设您正在使用 Amazon Web Services (AWS),并且想要一个创建新图像的脚本。
(如果您对此不熟悉:在亚马逊云上运行的服务器称为“实例”。实例是从亚马逊机器映像启动的,又名“AMI”或“图像”。AMI 有点像服务器的快照,位于一个特定的时刻。)
创建自定义 AMI 的常见模式如下所示:
- 从一些基本 AMI 运行实例(即启动服务器)。
- 对其进行一些修改,可能是通过复制脚本然后执行它。
- 从这个现在修改过的实例创建一个新图像。
- 终止您不再需要的正在运行的实例。
最后一步非常重要。如果您的脚本无法终止实例,它将继续运行并向您的账户收取费用。(在最坏的情况下,直到月底你才会注意到,那时你的账单比你预期的要高得多。)
如果我们的 AMI 创建被封装在脚本中,我们可以设置一个退出陷阱来销毁实例。让我们依靠 EC2 命令行工具:
#!/bin/bash
# define the base AMI ID somehow
ami=$1
# Store the temporary instance ID here
instance=''
# While we are at it, let me show you another use for a scratch directory.
scratch=$(mktemp -d -t tmp.XXXXXXXXXX)
function finish {
if [ -n "$instance" ]; then
ec2-terminate-instances "$instance"
fi
rm -rf "$scratch"
}
trap finish EXIT
# This line runs the instance, and stores the program output (which
# shows the instance ID) in a file in the scratch directory.
ec2-run-instances "$ami" > "$scratch/run-instance"
# Now extract the instance ID.
instance=$(grep '^INSTANCE' "$scratch/run-instance" | cut -f 2)
此时在脚本中,实例(EC2 服务器)正在运行。您可以做任何您喜欢的事情:在实例上安装软件,以编程方式修改其配置,等等,最后从最终版本创建一个映像。当脚本退出时,该实例将为您终止 , 即使某些未捕获的错误导致它提前退出。(只要确保阻塞直到图像创建过程完成。)