Bash 的内部命令 trap,让我们可以在 Shell 脚本内捕获特定的信号并对它们进行处理。
trap 命令的语法如下所示:
trap command signal [ signal ... ]
上述语法中,command 可以是一个脚本或是一个函数。signal 既可以用信号名,也可以用信号值指定。我们可以不指定任何参数,而直接使用 trap 命令,它将会打印与每个要捕获的信号相关联的命令的列表。
当 Shell 收到信号 signal(s) 时,command 将被读取和执行。比如,如果 signal 是 0 或 EXIT 时,command 会在 Shell 退出时被执行。如果 signal 是 DEBUG 时,command 会在每个命令后被执行。signal 也可以被指定为 ERR,那么每当一个命令以非 0 状态退出时,command 就会被执行(注意,当非 0 退出状态来自一个 if 语句部分,或来自 while、until 循环时,command 不会被执行)。
当调试较大的脚本时,我们可能想要赋予某个变量一个踪迹属性,并捕获变量的调试信息。通常,我们可能只使用一个简单的赋值语句,比如,VARIABLE=value,来定义一个变量。如果使用类似如下的语句替代上述的变量定义,可能会为你提供更有用的调试信息:
# 声明变量 VARIABLE,并赋予其踪迹属性
declare -t VARIABLE=value
# 捕获 DEBUG
trap "echo VARIABLE is being used here." DEBUG
# 脚本的余下部分
现在,我们创建一个名为 testtrap1.sh 的脚本,其内容如下所示:
#! /bin/bash
# 捕获退出状态 0
trap 'echo "Exit 0 signal detected..."' 0
# 打印信息
echo "This script is used for testing trap command."
# 以状态(信号)0 退出此 Shell 脚本
exit 0
上述脚本的运行结果:
我们再创建一个名称为 testtrap2.sh 的脚本,其内容类似如下所示:
#! /bin/bash
# 捕获信号 SIGINT,然后打印相应的信息
trap "echo 'You hit Ctrl + C! I am ignoring you.'" SIGINT
# 捕获信号 SIGTERM,然后打印相应信息
trap "echo 'You tried to kill me! I am ignoring you.'" SIGTERM
# 循环 5 次
for i in {1..5}; do
echo "Iteration $i of 5"
sleep 5
done
有时,接收到一个信号后你可能不想对其做任何处理。比如,当你的脚本处理较大的文件时,你可能希望阻止一些错误地输入 Ctrl+C 或 Ctrl+\ 组合键的做法,并且希望它能执行完成而不被用户中断。这时就可以使用空字符串(" " 或' ')作为 trap 的命令参数,那么 Shell 将忽略这些信号。其用法类似如下所示:
trap ' ' SIGHUP SIGINT [ signals ... ]
信号多用于以友好的方式结束一个进程的执行,即允许进程在退出之前有机会做一些清理工作。然而,信号同样还可用于其他用途。例如,当终端窗口的大小改变时,在此窗口中运行的 Shell 都会接收到信号 SIGWINCH。通常,这个信号是被忽略的,但是,如果一个程序关心窗口大小的变化,它就可以捕获这个信号,并用特定的方式处理它。
** 注意: **
除 SIGKILL 信号以外,其他任何信号都可以被捕获并通过调用 C 函数 signal 处理。
捕获并处理 SIGWINCH 信号实例:
#! /bin/bash
echo "Adjust the size of your window now."
trap "echo Window size changed." SIGWINCH
COUNT=0
while [ $COUNT -lt 30 ] ; do
COUNT=$(($COUNT + 1))
sleep 1
done
上述实例在运行时,我们改变命令行程序的窗口大小的运行结果如下:
接收到退出信号后清理程序的实例:
#! /bin/bash
trap 'my_exit; exit' SIGINT SIGQUIT
# 用户调用 kill -1 PID 命令
trap 'echo Going down on a SIGHUP - signal 1, no exiting...; exit' SIGHUP
count=0
tmp_file=`mktemp /tmp/file.$$.XXXXXX`
my_exit()
{
echo "You hit Ctrl-C/Ctrl-\, now exiting..."
rm -f $tmp_file >& /dev/null
}
echo "Do something..." > $tmp_file
# 执行无限循环
while :
do
sleep 1
count=$(expr $count + 1)
echo $count
done
当上述脚本运行时,接收到 SIGINT 或 SIGQIUT 信号后会调用 my_exit 函数清理临时文件之后退出。这个实例的具体使用效果,大家可以自行尝试。
Bash 中有两个内部变量可以方便地处理信号时,为我们提供更多的与脚本终结相关的信息。这两个变量分别是 LINENO 和 BASH_COMMAND。BASH_COMMAND 是 Bash 中特有的。这两个变量分别用于报告脚本当前执行的行号和脚本当前运行的命令。
LINENO 和 BASH_COMMAND 使用实例:
#! /bin/bash
trap 'my_exit $LINENO $BASH_COMMAND; exit' SIGHUP SIGINT SIGQUIT
my_exit()
{
echo "$(basename $0) caught error on line : $1 command was: $2"
logger -p notice "script: $(basename $0) was terminated: line: $1, command was $2"
# 其他的一些清理命令
}
while :
do
sleep 1
count=$(( $count + 1 ))
echo $count
done
** 移除捕获 **
如果我们在脚本中应用了捕获,我们通常会在脚本的结尾处,将接收到信号时的行为处理重置为默认模式,重置(移除)捕获的语法如下所示:
trap - signal [ signal ... ]
从上述语法中可以看出,使用破折号作为 trap 语句的命令参数,就可以移除信号的捕获。
移除捕获实例:
#! /bin/bash
function cleanup() {
if [[ -e $msgfile ]]; then
mv $msgfile $msgfile.dead
fi
exit
}
trap cleanup INT TERM
msgfile=`mktemp /tmp/testtrap.$$.XXXXXX`
cat > $msgfile
rm $msgfile
trap - INT TERM
本文参考自 《Linux Shell命令行及脚本编程实例详解 》