D26
对于大而复杂的脚本,需要考虑脚本运行时出现用户退出或系统关机会发生什么是很重要的,程序应该根据这些事件发送的信号,采取措施以确保程序正常有序地终结。
1. trap语句
Bash的内部命令trap,可以在Shell脚本中捕获特定的信号并对它们进行处理。
语法:trap command signal [ signal ... ]
其中:
command可以是一个脚本或一个函数;
signal可以用信号名或信号值。
如果不带参数执行trap,将会打印与每个要捕获的信号相关联的命令的列表。
$ trap trap -- ‘‘ SIGTSTP trap -- ‘‘ SIGTTIN trap -- ‘‘ SIGTTOU
当shell收到信号signal时,command会被读取和执行。如:
signal为0或Exit时,command将会在shell退出时被执行;
signal为DEBUG时,command将会在每个命令后被执行;
signal也可以被指定为ERR,那么每当一个命令以非0状态退出时,command就会被执行(注意:当非0退出状态来自一个if语句部分,或来自while、until循环时,command不会被执行)。
1)定义ERR捕获
#使用mktemp命令创建一个临时文件; #使用-u选项表示并不真正创建文件,只是打印生成的文件名; #‘XXXXXX‘表示生成6位随机数字 $ FILE=`mktemp -u /tmp/testtrap.$$.XXXXXX` #定义捕获错误信号 $ trap ‘echo There exist some error‘ ERR #查看定义的捕获错误信号 $ trap trap -- ‘‘ SIGTSTP trap -- ‘‘ SIGTTIN trap -- ‘‘ SIGTTOU trap -- ‘echo There exist some error‘ ERR #删除不存在的文件,出现了ERR信号 $ rm $FILE rm: cannot remove ‘/tmp/testtrap.3112.bjnJNj‘: No such file or directory There exist some error
2)定义DEBUG捕获
当调试较大的脚本时,可能想要赋予某个变量一个踪迹树形,并捕获变量的调试信息
$ cat testdebugtrap.sh #!/bin/bash #202006 declare -t VARIABLE=value #声明变量VARIABLE,并赋予其踪迹属性,替换到普通的赋值语句:VARIABLE=value trap ‘echo VARIABLE is being used here.‘ DEBUG #捕获DEBUG VARIABLE=changed #会触发trap echo "VARIABLE = $VARIABLE" #会触发trap #执行 $ ./testdebugtrap.sh VARIABLE is being used here. VARIABLE is being used here. VARIABLE = changed
3)定义退出码捕获
$ cat testtrapexit.sh #!/bin/bash #202006 #捕获退出状态0 trap ‘echo "Exit 0 signal detected..."‘ 0 echo "This script is used for testing trap command." #以状态0(信号0)退出此脚本 exit 0 #执行 $ ./testtrapexit.sh This script is used for testing trap command. Exit 0 signal detected...
4)定义SIGINT、SIGTERM捕获
$ cat testtrapsig.sh #!/bin/bash #202006 #捕获信号SIGINT,打印信息 trap "echo ‘You hit Ctrl+C! I am ingnoring you.‘" SIGINT #捕获信号SIGTERM,打印信息 trap "echo ‘You tried to kill me! I am ingnoring you.‘" SIGTERM #循环5次,每次打印信息+暂停 for i in {1..5} do echo "Iteration $i of 5" sleep 5 done
执行过程中如果使用Ctrl+C组合键,会中断sleep命令进入下一次循环,可以看到捕获SIGINT的输出信息:
$ sh ./testtrapsig.sh Iteration 1 of 5 ^CYou hit Ctrl+C! I am ingnoring you. Iteration 2 of 5 Iteration 3 of 5 ^CYou hit Ctrl+C! I am ingnoring you. Iteration 4 of 5 Iteration 5 of 5
执行过程中尝试使用kill命令终结此脚本,实际会继续运行并看到捕获SIGTERM的输出信息:
$ sh ./testtrapsig.sh Iteration 1 of 5 Iteration 2 of 5 Iteration 3 of 5 You tried to kill me! I am ingnoring you. Iteration 4 of 5 Iteration 5 of 5 You tried to kill me! I am ingnoring you. #注:执行过程中在另一终端执行 kill 15139 #此脚本的PID为15139
5)捕获信号后不做处理
比如当脚本处理较大的文件时,不希望被Ctrl+C或Ctrl+\组合键中断。可以使用空字符串(" "或‘ ‘)作为trap的命令参数,Shell就会忽略指定的信号。语法如:
$ trap ‘ ‘ SIGHUP SIGINT [ signal ... ]
2. 使用trap语句捕获信号
1)前面学习了信号多用于友好的结束一个进程的执行,即允许进程在退出前做一些清理工作。
信号还可以有其他用途,如当终端窗口的大小改变时,在此窗口运行的Shell都会收到信号SIGWINCH。通常该信号被忽略,但如果一个程序关心窗口变化,就可以捕获这个信号并用特定的方式处理它。
注意:除了SIGKILL以外的其他信号,都可以被捕获并通过调用C函数signal处理。
$ cat sigwinch_handler.sh #!/bin/bash #202006 echo "Adjust the size of your window now." #捕获SIGWINCH信号 trap ‘echo Window size changed.‘ SIGWINCH COUNT=0 #循环30次 while [ $COUNT -lt 30 ] do COUNT=$(($COUNT+1)) sleep 1 done #执行 #过程中调整窗口大小 $ ./sigwinch_handler.sh Adjust the size of your window now. Window size changed. Window size changed. Window size changed.
2)使用trap命令调用函数来处理响应的信号
$ cat trapbg_clearup.sh #!/bin/bash #202006 #捕获SIGINT和SIGQUIT,执行my_exit后退出 trap ‘my_exit; exit‘ SIGINT SIGQUIT #捕获SIGHUP trap ‘echo Going down on a SITHUP - signal 1, now 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
执行并查看临时文件:
$ ./trapbg_clearup.sh 1 2 3 #在另外一个终端查看临时文件 $ ls -trl /tmp/ | tail -1 -rw-------. 1 ntrade ntrade 16 Jun 10 16:24 file.15190.btyytG $ cat /tmp/file.15190.btyytG Do something... #输入Crtl+C组合键或Ctrl+\组合键 $ ./trapbg_clearup.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ^CYou hit Ctrl-C/Ctrl-\, now exiting... #查看临时文件,已经被删除 cat: /tmp/file.15190.btyytG: No such file or directory
脚本在后台执行,也可以捕获信号:
#后台执行 $ ./trapbg_clearup.sh & [1] 15363 $ 1 2 3 #在另外一个终端发送HUP信号 $ kill -1 15363 #在脚本运行的终端窗口 $ ./trapbg_clearup.sh & [1] 15363 $ 1 2 3 4 5 6 7 8 Going down on a SITHUP - signal 1, now exiting ... [1]+ Done ./trapbg_clearup.sh
3)使用LINENO和BASH_COMMAND变量
Bash的内部变量LINENO和BASH_COMMAND(Bash特有的)可以在处理信号时,为我们提供更多的与脚本终结相关的信息。
分别用于报告脚本当前执行的行号和脚本当前运行的命令。
$ cat trap_report.sh #!/bin/bash #202006 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" #other command } while : do sleep 1 count=$(expr $count + 1) echo $count done #执行 #过程中使用kill -1 PID $ ./trap_report.sh 1 2 3 4 5 6 7 8 trap_report.sh caught error on line: 1 command was: sleep #查看/var/log/messages有类似信息 script: trap_report.sh was terminated: line: 1, command was sleep
4)使用trap语句在脚本的一部分中忽略某些信号,之后重新定义捕获这些信号
$ cat trapoff_on.sh #!/bin/bash #20200610 #忽略QUIT和INT trap ‘‘ SIGQUIT SIGINT echo "You can‘t terminate me using Ctrl+c or Ctrl+\." sleep 10 #捕获QUIT和INT trap ‘echo Terminated; exit‘ SIGQUIT SIGINT echo "OK! You can now terminate me by using these keystrokes." sleep 10 #执行 $ ./trapoff_on.sh You can‘t terminate me using Ctrl+c or Ctrl+\. ^COK! You can now terminate me by using these keystrokes. ^CTerminated
3. 移除捕获
如果在脚本中应用了trap捕获,通常会在脚本结尾恢复。重置(移除)捕获的语法如下:
trap - signal [ signal ... ]
$ cat trap_reset.sh #!/bin/bash #202006 function cleanup() { if [[ -e $msgfile ]] then echo "line number: `cat $msgfile | wc -l` " mv $msgfile $msgfile.dead fi exit } #捕获INT和TERM信号,执行cleanup函数 trap cleanup INT TERM msgfile=`mktemp /tmp/testtrap.$$.XXXXXX` #通过命令行向此临时文件写入内容 cat > $msgfile #other command rm $msgfile
#如果Ctrl+d停止输入信息,会删除临时文件,不会调用cleanup,因此不再需要清理操作,所以移除捕获
trap - INT TERM
#执行:
$ ./trap_reset.sh
abcd
efg
a
^Cline number: 3 #这里使用Ctrl+C停止
#查看临时文件
$ cat /tmp/testtrap.15628.vTiQll.dead
abcd
efg
a
本节结束