debugging.html

来自「BASH Shell 编程 经典教程 《高级SHELL脚本编程》中文版」· HTML 代码 · 共 1,476 行 · 第 1/2 页

HTML
1,476
字号
ALIGN="LEFT"VALIGN="TOP"><P><ANAME="SIGNALD"></A>A			 <EM>signal</EM>就是发往进程的一个简单消息, 		这个消息即可以由内核发出, 也可以由另一个进程发出, 		发送这个消息的目的是为了通知目的进程采取一些指定动作(通常都是终止动作). 		比如说, 按下<BCLASS="KEYCAP">Control</B>-<BCLASS="KEYCAP">C</B>,		就会发送一个用户中断(即INT信号)到运行中的进程. </P></TD></TR></TABLE></DIV>		<TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;trap '' 2  2&nbsp;# 忽略中断2(Control-C), 没有指定处理动作.   3&nbsp;  4&nbsp;trap 'echo "Control-C disabled."' 2  5&nbsp;# 当Control-C按下时, 显示一行信息. </PRE></FONT></TD></TR></TABLE>	      </P></DD></DL></DIV><DIVCLASS="EXAMPLE"><HR><ANAME="EX76"></A><P><B>例子 29-5. 捕获exit</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# 使用trap来捕捉变量值.   3&nbsp;  4&nbsp;trap 'echo Variable Listing --- a = $a  b = $b' EXIT  5&nbsp;#  EXIT是脚本中exit命令所产生信号的名字.   6&nbsp;#  7&nbsp;#  "trap"所指定的命令并不会马上执行,   8&nbsp;#+ 只有接收到合适的信号, 这些命令才会执行.   9&nbsp; 10&nbsp;echo "This prints before the \"trap\" --" 11&nbsp;echo "even though the script sees the \"trap\" first." 12&nbsp;echo 13&nbsp; 14&nbsp;a=39 15&nbsp; 16&nbsp;b=36 17&nbsp; 18&nbsp;exit 0 19&nbsp;#  注意, 即使注释掉上面的这行'exit'命令, 也不会产生什么不同的结果,  20&nbsp;#+ 这是因为所有命令都执行完毕后, 不管怎么样, 脚本都会退出的. </PRE></FONT></TD></TR></TABLE><HR></DIV><DIVCLASS="EXAMPLE"><HR><ANAME="ONLINE"></A><P><B>例子 29-6. Control-C之后, 清除垃圾</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# logon.sh: 一个检查你是否在线的脚本, 这个脚本实现的很简陋.   3&nbsp;  4&nbsp;umask 177  # 确保temp文件并不是所有用户都有权限访问.   5&nbsp;  6&nbsp;  7&nbsp;TRUE=1  8&nbsp;LOGFILE=/var/log/messages  9&nbsp;#  注意: $LOGFILE必须是可读的 10&nbsp;#+ (使用root身份来执行, chmod 644 /var/log/messages). 11&nbsp;TEMPFILE=temp.$$ 12&nbsp;#  使用脚本的进程ID, 来创建一个"唯一"的临时文件名.  13&nbsp;#     也可以使用'mktemp'.  14&nbsp;#     比如:  15&nbsp;#     TEMPFILE=`mktemp temp.XXXXXX` 16&nbsp;KEYWORD=address 17&nbsp;#  登陆时, 把"remote IP address xxx.xxx.xxx.xxx" 18&nbsp;#                      添加到/var/log/messages中.  19&nbsp;ONLINE=22 20&nbsp;USER_INTERRUPT=13 21&nbsp;CHECK_LINES=100 22&nbsp;#  日志文件有多少行需要检查.  23&nbsp; 24&nbsp;trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT 25&nbsp;#  如果脚本被control-c中途中断的话, 那么就清除掉临时文件.  26&nbsp; 27&nbsp;echo 28&nbsp; 29&nbsp;while [ $TRUE ]  #死循环.  30&nbsp;do 31&nbsp;  tail -$CHECK_LINES $LOGFILE&#62; $TEMPFILE 32&nbsp;  #  将系统日志文件的最后100行保存到临时文件中.  33&nbsp;  #  这么做很有必要, 因为新版本的内核在登陆的时候, 会产生许多登陆信息.  34&nbsp;  search=`grep $KEYWORD $TEMPFILE` 35&nbsp;  #  检查是否存在"IP address"片断,  36&nbsp;  #+ 它用来提示, 这是一次成功的网络登陆.  37&nbsp; 38&nbsp;  if [ ! -z "$search" ] #  必须使用引号, 因为变量可能会包含一些空白符.  39&nbsp;  then 40&nbsp;     echo "On-line" 41&nbsp;     rm -f $TEMPFILE    #  清除临时文件.  42&nbsp;     exit $ONLINE 43&nbsp;  else 44&nbsp;     echo -n "."        #  echo的-n选项不会产生换行符.  45&nbsp;                        #+ 这样你就可以在一行上连续打印.  46&nbsp;  fi 47&nbsp; 48&nbsp;  sleep 1   49&nbsp;done   50&nbsp; 51&nbsp; 52&nbsp;#  注意: 如果你将变量KEYWORD的值改为"Exit",  53&nbsp;#+ 当在线时, 这个脚本就可以被用来检查 54&nbsp;#+ 意外的掉线情况.  55&nbsp; 56&nbsp;# 练习: 按照上面"注意"中所说的那样来修改这个脚本,  57&nbsp;#       让它表现的更好.  58&nbsp; 59&nbsp;exit 0 60&nbsp; 61&nbsp; 62&nbsp;# Nick Drage建议使用另一种方法:  63&nbsp; 64&nbsp;while true 65&nbsp;  do ifconfig ppp0 | grep UP 1&#62; /dev/null &#38;&#38; echo "connected" &#38;&#38; exit 0 66&nbsp;  echo -n "."   # 不停的打印(.....), 用来提示用户等待, 直到连接上位置.  67&nbsp;  sleep 2 68&nbsp;done 69&nbsp; 70&nbsp;# 问题: 使用Control-C来终止这个进程可能是不够的.  71&nbsp;#+         (可能还会继续打印"...") 72&nbsp;# 练习: 修复这个问题.  73&nbsp; 74&nbsp; 75&nbsp; 76&nbsp;# Stephane Chazelas提出了另一种方法:  77&nbsp; 78&nbsp;CHECK_INTERVAL=1 79&nbsp; 80&nbsp;while ! tail -1 "$LOGFILE" | grep -q "$KEYWORD" 81&nbsp;do echo -n . 82&nbsp;   sleep $CHECK_INTERVAL 83&nbsp;done 84&nbsp;echo "On-line" 85&nbsp; 86&nbsp;# 练习: 讨论一下这几种不同方法 87&nbsp;#       各自的优缺点. </PRE></FONT></TD></TR></TABLE><HR></DIV><DIVCLASS="NOTE"><P></P><TABLECLASS="NOTE"WIDTH="100%"BORDER="0"><TR><TDWIDTH="25"ALIGN="CENTER"VALIGN="TOP"><IMGSRC="./images/note.gif"HSPACE="5"ALT="Note"></TD><TDALIGN="LEFT"VALIGN="TOP"><P>如果使用<BCLASS="COMMAND">trap</B>命令的<CODECLASS="OPTION">DEBUG</CODE>参数, 	那么当脚本中每个命令执行完毕后, 都会执行指定的动作. 	比方说, 你可以跟踪某个变量的值.       <DIVCLASS="EXAMPLE"><HR><ANAME="VARTRACE"></A><P><B>例子 29-7. 跟踪一个变量</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;  3&nbsp;trap 'echo "VARIABLE-TRACE&#62; \$variable = \"$variable\""' DEBUG  4&nbsp;# 当每个命令执行之后, 就会打印出$variable的值.   5&nbsp;  6&nbsp;variable=29  7&nbsp;  8&nbsp;echo "Just initialized \"\$variable\" to $variable."  9&nbsp; 10&nbsp;let "variable *= 3" 11&nbsp;echo "Just multiplied \"\$variable\" by 3." 12&nbsp; 13&nbsp;exit $? 14&nbsp; 15&nbsp;#  "trap 'command1 . . . command2 . . .' DEBUG"结构更适合于 16&nbsp;#+ 使用在复杂脚本的上下文中,  17&nbsp;#+ 如果在这种情况下大量使用"echo $variable"语句的话,  18&nbsp;#+ 将会非常笨拙, 而且很耗时.  19&nbsp; 20&nbsp;# 感谢, Stephane Chazelas指出这点.  21&nbsp; 22&nbsp; 23&nbsp;脚本的输出:  24&nbsp; 25&nbsp;VARIABLE-TRACE&#62; $variable = "" 26&nbsp;VARIABLE-TRACE&#62; $variable = "29" 27&nbsp;Just initialized "$variable" to 29. 28&nbsp;VARIABLE-TRACE&#62; $variable = "29" 29&nbsp;VARIABLE-TRACE&#62; $variable = "87" 30&nbsp;Just multiplied "$variable" by 3. 31&nbsp;VARIABLE-TRACE&#62; $variable = "87"</PRE></FONT></TD></TR></TABLE><HR></DIV>      </P></TD></TR></TABLE></DIV><P>当然, 除了调试之外, 		  <BCLASS="COMMAND">trap</B>命令还有其他的用途.         </P><DIVCLASS="EXAMPLE"><HR><ANAME="MULTIPLEPROC"></A><P><B>例子 29-8. 运行多进程(在对称多处理器(SMP box)的机器上)</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# parent.sh  3&nbsp;# 在多处理器(SMP box)的机器里运行多进程.   4&nbsp;# 作者: Tedman Eng  5&nbsp;  6&nbsp;#  我们下面要介绍两个脚本, 这是第一个,   7&nbsp;#+ 这两个脚本都要放到当前工作目录下.   8&nbsp;  9&nbsp; 10&nbsp; 11&nbsp; 12&nbsp;LIMIT=$1         # 想要启动的进程总数 13&nbsp;NUMPROC=4        # 并发的线程数量(forks?) 14&nbsp;PROCID=1         # 启动的进程ID 15&nbsp;echo "My PID is $$" 16&nbsp; 17&nbsp;function start_thread() { 18&nbsp;        if [ $PROCID -le $LIMIT ] ; then 19&nbsp;                ./child.sh $PROCID&#38; 20&nbsp;                let "PROCID++" 21&nbsp;        else 22&nbsp;           echo "Limit reached." 23&nbsp;           wait 24&nbsp;           exit 25&nbsp;        fi 26&nbsp;} 27&nbsp; 28&nbsp;while [ "$NUMPROC" -gt 0 ]; do 29&nbsp;        start_thread; 30&nbsp;        let "NUMPROC--" 31&nbsp;done 32&nbsp; 33&nbsp; 34&nbsp;while true 35&nbsp;do 36&nbsp; 37&nbsp;trap "start_thread" SIGRTMIN 38&nbsp; 39&nbsp;done 40&nbsp; 41&nbsp;exit 0 42&nbsp; 43&nbsp; 44&nbsp; 45&nbsp;# ======== 下面是第二个脚本 ======== 46&nbsp; 47&nbsp; 48&nbsp;#!/bin/bash 49&nbsp;# child.sh 50&nbsp;# 在SMP box上运行多进程.  51&nbsp;# 这个脚本会被parent.sh调用.  52&nbsp;# 作者: Tedman Eng 53&nbsp; 54&nbsp;temp=$RANDOM 55&nbsp;index=$1 56&nbsp;shift 57&nbsp;let "temp %= 5" 58&nbsp;let "temp += 4" 59&nbsp;echo "Starting $index  Time:$temp" "$@" 60&nbsp;sleep ${temp} 61&nbsp;echo "Ending $index" 62&nbsp;kill -s SIGRTMIN $PPID 63&nbsp; 64&nbsp;exit 0 65&nbsp; 66&nbsp; 67&nbsp;# ======================= 脚本作者注 ======================= # 68&nbsp;#  这个脚本并不是一点bug都没有.  69&nbsp;#  我使用limit = 500来运行这个脚本, 但是在过了开头的一两百个循环后,  70&nbsp;#+ 有些并发线程消失了!  71&nbsp;#  还不能确定这是否是由捕捉信号的冲突引起的, 或者是其他什么原因.  72&nbsp;#  一旦接收到捕捉的信号, 那么在下一次捕捉到来之前,  73&nbsp;#+ 会有一段短暂的时间来执行信号处理程序,  74&nbsp;#+ 在信号处理程序处理的过程中, 很有可能会丢失一个想要捕捉的信号, 因此失去一个产生子进程的机会.  75&nbsp; 76&nbsp;#  毫无疑问的, 肯定有人能够找出产生这个bug的原因,  77&nbsp;#+ 并且在将来的某个时候. . . 修正它. 78&nbsp; 79&nbsp; 80&nbsp; 81&nbsp;# ===================================================================== # 82&nbsp; 83&nbsp; 84&nbsp; 85&nbsp;# ----------------------------------------------------------------------# 86&nbsp; 87&nbsp; 88&nbsp; 89&nbsp;################################################################# 90&nbsp;# 下面的脚本是由Vernia Damiano原创.  91&nbsp;# 不幸的是, 它并不能正常工作.  92&nbsp;################################################################# 93&nbsp; 94&nbsp;#!/bin/bash 95&nbsp; 96&nbsp;#  要想调用这个脚本, 至少需要一个整形参数 97&nbsp;#+ (并发的进程数).  98&nbsp;#  所有的其他参数都传递给要启动的进程.  99&nbsp;100&nbsp;101&nbsp;INDICE=8        # 想要启动的进程数目102&nbsp;TEMPO=5         # 每个进程最大的睡眠时间103&nbsp;E_BADARGS=65    # 如果没有参数传到脚本中, 那么就返回这个错误码. 104&nbsp;105&nbsp;if [ $# -eq 0 ] # 检查一下, 至少要传递一个参数给脚本. 106&nbsp;then107&nbsp;  echo "Usage: `basename $0` number_of_processes [passed params]"108&nbsp;  exit $E_BADARGS109&nbsp;fi110&nbsp;111&nbsp;NUMPROC=$1              # 并发进程数112&nbsp;shift113&nbsp;PARAMETRI=( "$@" )      # 每个进程的参数114&nbsp;115&nbsp;function avvia() {116&nbsp;         local temp117&nbsp;         local index118&nbsp;         temp=$RANDOM119&nbsp;         index=$1120&nbsp;         shift121&nbsp;         let "temp %= $TEMPO"122&nbsp;         let "temp += 1"123&nbsp;         echo "Starting $index Time:$temp" "$@"124&nbsp;         sleep ${temp}125&nbsp;         echo "Ending $index"126&nbsp;         kill -s SIGRTMIN $$127&nbsp;}128&nbsp;129&nbsp;function parti() {130&nbsp;         if [ $INDICE -gt 0 ] ; then131&nbsp;              avvia $INDICE "${PARAMETRI[@]}" &#38;132&nbsp;                let "INDICE--"133&nbsp;         else134&nbsp;                trap : SIGRTMIN135&nbsp;         fi136&nbsp;}137&nbsp;138&nbsp;trap parti SIGRTMIN139&nbsp;140&nbsp;while [ "$NUMPROC" -gt 0 ]; do141&nbsp;         parti;142&nbsp;         let "NUMPROC--"143&nbsp;done144&nbsp;145&nbsp;wait146&nbsp;trap - SIGRTMIN147&nbsp;148&nbsp;exit $?149&nbsp;150&nbsp;: &#60;&#60;SCRIPT_AUTHOR_COMMENTS151&nbsp;我需要使用指定的选项来运行一个程序, 152&nbsp;并且能够接受不同的文件, 而且要运行在一个多处理器(SMP)的机器上. 153&nbsp;所以我想(我也会)运行指定数目个进程, 154&nbsp;并且每个进程终止之后, 都要启动一个新进程. 155&nbsp;156&nbsp;"wait"命令并没有提供什么帮助, 因为它需要等待一个指定的后台进程, 157&nbsp;或者等待*全部*的后台进程. 所以我编写了[这个]bash脚本程序来完成这个工作, 158&nbsp;并且使用了"trap"指令. 159&nbsp;  --Vernia Damiano160&nbsp;SCRIPT_AUTHOR_COMMENTS</PRE></FONT></TD></TR></TABLE><HR></DIV><DIVCLASS="NOTE"><P></P><TABLECLASS="NOTE"WIDTH="100%"BORDER="0"><TR><TDWIDTH="25"ALIGN="CENTER"VALIGN="TOP"><IMGSRC="./images/note.gif"HSPACE="5"ALT="Note"></TD><TDALIGN="LEFT"VALIGN="TOP"><P><KBDCLASS="USERINPUT">trap '' SIGNAL</KBD>(两个引号之间为空)在剩余的脚本中禁用了SIGNAL信号的动作. 	<KBDCLASS="USERINPUT">trap SIGNAL</KBD>则会恢复处理SIGNAL的动作. 	当你想保护脚本的临界部分不受意外的中断骚扰, 	那么上面讲的这种办法就非常有用了. 	</P></TD></TR></TABLE></DIV><P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;	trap '' 2  # 信号2就是Control-C, 现在被禁用了.   2&nbsp;	command  3&nbsp;	command  4&nbsp;	command  5&nbsp;	trap 2     # 重新恢复Control-C  6&nbsp;	</PRE></FONT></TD></TR></TABLE></P><TABLECLASS="SIDEBAR"BORDER="1"CELLPADDING="5"><TR><TD><DIVCLASS="SIDEBAR"><P></P><ANAME="AEN15184"></A><P>Bash<AHREF="bashver3.html#BASH3REF">3.0</A>之后增加了如下这些特殊变量用于调试.        <P></P><OLTYPE="1"><LI><P>$BASH_ARGC</P></LI><LI><P>$BASH_ARGV</P></LI><LI><P>$BASH_COMMAND</P></LI><LI><P>$BASH_EXECUTION_STRING</P></LI><LI><P>$BASH_LINENO</P></LI><LI><P>$BASH_SOURCE</P></LI><LI><P><AHREF="internalvariables.html#BASHSUBSHELLREF">$BASH_SUBSHELL</A></P></LI></OL></P><P></P></DIV></TD></TR></TABLE></DIV><H3CLASS="FOOTNOTES">注意事项</H3><TABLEBORDER="0"CLASS="FOOTNOTES"WIDTH="100%"><TR><TDALIGN="LEFT"VALIGN="TOP"WIDTH="5%"><ANAME="FTN.AEN15038"HREF="debugging.html#AEN15038"><SPANCLASS="footnote">[1]</SPAN></A></TD><TDALIGN="LEFT"VALIGN="TOP"WIDTH="95%"><P>事实上, Rocky Bernstein的<AHREF="http://bashdb.sourceforge.net"TARGET="_top">Bash debugger</A>填补了这项空白. </P></TD></TR><TR><TDALIGN="LEFT"VALIGN="TOP"WIDTH="5%"><ANAME="FTN.AEN15137"HREF="debugging.html#AEN15137"><SPANCLASS="footnote">[2]</SPAN></A></TD><TDALIGN="LEFT"VALIGN="TOP"WIDTH="95%"><P>根据惯例, 			<TTCLASS="REPLACEABLE"><I>信号0</I></TT>被指定为<AHREF="exit-status.html#EXITCOMMANDREF">exit</A>.  </P></TD></TR></TABLE><DIVCLASS="NAVFOOTER"><HRALIGN="LEFT"WIDTH="100%"><TABLESUMMARY="Footer navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top"><AHREF="zeros.html"ACCESSKEY="P">前一页</A></TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><AHREF="index.html"ACCESSKEY="H">首页</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top"><AHREF="options.html"ACCESSKEY="N">下一页</A></TD></TR><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top">Zero与Null</TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><AHREF="part4.html"ACCESSKEY="U">上一级</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top">选项</TD></TR></TABLE></DIV></BODY></HTML>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?