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 trap '' 2 2 # 忽略中断2(Control-C), 没有指定处理动作. 3 4 trap 'echo "Control-C disabled."' 2 5 # 当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 #!/bin/bash 2 # 使用trap来捕捉变量值. 3 4 trap 'echo Variable Listing --- a = $a b = $b' EXIT 5 # EXIT是脚本中exit命令所产生信号的名字. 6 # 7 # "trap"所指定的命令并不会马上执行, 8 #+ 只有接收到合适的信号, 这些命令才会执行. 9 10 echo "This prints before the \"trap\" --" 11 echo "even though the script sees the \"trap\" first." 12 echo 13 14 a=39 15 16 b=36 17 18 exit 0 19 # 注意, 即使注释掉上面的这行'exit'命令, 也不会产生什么不同的结果, 20 #+ 这是因为所有命令都执行完毕后, 不管怎么样, 脚本都会退出的. </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 #!/bin/bash 2 # logon.sh: 一个检查你是否在线的脚本, 这个脚本实现的很简陋. 3 4 umask 177 # 确保temp文件并不是所有用户都有权限访问. 5 6 7 TRUE=1 8 LOGFILE=/var/log/messages 9 # 注意: $LOGFILE必须是可读的 10 #+ (使用root身份来执行, chmod 644 /var/log/messages). 11 TEMPFILE=temp.$$ 12 # 使用脚本的进程ID, 来创建一个"唯一"的临时文件名. 13 # 也可以使用'mktemp'. 14 # 比如: 15 # TEMPFILE=`mktemp temp.XXXXXX` 16 KEYWORD=address 17 # 登陆时, 把"remote IP address xxx.xxx.xxx.xxx" 18 # 添加到/var/log/messages中. 19 ONLINE=22 20 USER_INTERRUPT=13 21 CHECK_LINES=100 22 # 日志文件有多少行需要检查. 23 24 trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT 25 # 如果脚本被control-c中途中断的话, 那么就清除掉临时文件. 26 27 echo 28 29 while [ $TRUE ] #死循环. 30 do 31 tail -$CHECK_LINES $LOGFILE> $TEMPFILE 32 # 将系统日志文件的最后100行保存到临时文件中. 33 # 这么做很有必要, 因为新版本的内核在登陆的时候, 会产生许多登陆信息. 34 search=`grep $KEYWORD $TEMPFILE` 35 # 检查是否存在"IP address"片断, 36 #+ 它用来提示, 这是一次成功的网络登陆. 37 38 if [ ! -z "$search" ] # 必须使用引号, 因为变量可能会包含一些空白符. 39 then 40 echo "On-line" 41 rm -f $TEMPFILE # 清除临时文件. 42 exit $ONLINE 43 else 44 echo -n "." # echo的-n选项不会产生换行符. 45 #+ 这样你就可以在一行上连续打印. 46 fi 47 48 sleep 1 49 done 50 51 52 # 注意: 如果你将变量KEYWORD的值改为"Exit", 53 #+ 当在线时, 这个脚本就可以被用来检查 54 #+ 意外的掉线情况. 55 56 # 练习: 按照上面"注意"中所说的那样来修改这个脚本, 57 # 让它表现的更好. 58 59 exit 0 60 61 62 # Nick Drage建议使用另一种方法: 63 64 while true 65 do ifconfig ppp0 | grep UP 1> /dev/null && echo "connected" && exit 0 66 echo -n "." # 不停的打印(.....), 用来提示用户等待, 直到连接上位置. 67 sleep 2 68 done 69 70 # 问题: 使用Control-C来终止这个进程可能是不够的. 71 #+ (可能还会继续打印"...") 72 # 练习: 修复这个问题. 73 74 75 76 # Stephane Chazelas提出了另一种方法: 77 78 CHECK_INTERVAL=1 79 80 while ! tail -1 "$LOGFILE" | grep -q "$KEYWORD" 81 do echo -n . 82 sleep $CHECK_INTERVAL 83 done 84 echo "On-line" 85 86 # 练习: 讨论一下这几种不同方法 87 # 各自的优缺点. </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 #!/bin/bash 2 3 trap 'echo "VARIABLE-TRACE> \$variable = \"$variable\""' DEBUG 4 # 当每个命令执行之后, 就会打印出$variable的值. 5 6 variable=29 7 8 echo "Just initialized \"\$variable\" to $variable." 9 10 let "variable *= 3" 11 echo "Just multiplied \"\$variable\" by 3." 12 13 exit $? 14 15 # "trap 'command1 . . . command2 . . .' DEBUG"结构更适合于 16 #+ 使用在复杂脚本的上下文中, 17 #+ 如果在这种情况下大量使用"echo $variable"语句的话, 18 #+ 将会非常笨拙, 而且很耗时. 19 20 # 感谢, Stephane Chazelas指出这点. 21 22 23 脚本的输出: 24 25 VARIABLE-TRACE> $variable = "" 26 VARIABLE-TRACE> $variable = "29" 27 Just initialized "$variable" to 29. 28 VARIABLE-TRACE> $variable = "29" 29 VARIABLE-TRACE> $variable = "87" 30 Just multiplied "$variable" by 3. 31 VARIABLE-TRACE> $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 #!/bin/bash 2 # parent.sh 3 # 在多处理器(SMP box)的机器里运行多进程. 4 # 作者: Tedman Eng 5 6 # 我们下面要介绍两个脚本, 这是第一个, 7 #+ 这两个脚本都要放到当前工作目录下. 8 9 10 11 12 LIMIT=$1 # 想要启动的进程总数 13 NUMPROC=4 # 并发的线程数量(forks?) 14 PROCID=1 # 启动的进程ID 15 echo "My PID is $$" 16 17 function start_thread() { 18 if [ $PROCID -le $LIMIT ] ; then 19 ./child.sh $PROCID& 20 let "PROCID++" 21 else 22 echo "Limit reached." 23 wait 24 exit 25 fi 26 } 27 28 while [ "$NUMPROC" -gt 0 ]; do 29 start_thread; 30 let "NUMPROC--" 31 done 32 33 34 while true 35 do 36 37 trap "start_thread" SIGRTMIN 38 39 done 40 41 exit 0 42 43 44 45 # ======== 下面是第二个脚本 ======== 46 47 48 #!/bin/bash 49 # child.sh 50 # 在SMP box上运行多进程. 51 # 这个脚本会被parent.sh调用. 52 # 作者: Tedman Eng 53 54 temp=$RANDOM 55 index=$1 56 shift 57 let "temp %= 5" 58 let "temp += 4" 59 echo "Starting $index Time:$temp" "$@" 60 sleep ${temp} 61 echo "Ending $index" 62 kill -s SIGRTMIN $PPID 63 64 exit 0 65 66 67 # ======================= 脚本作者注 ======================= # 68 # 这个脚本并不是一点bug都没有. 69 # 我使用limit = 500来运行这个脚本, 但是在过了开头的一两百个循环后, 70 #+ 有些并发线程消失了! 71 # 还不能确定这是否是由捕捉信号的冲突引起的, 或者是其他什么原因. 72 # 一旦接收到捕捉的信号, 那么在下一次捕捉到来之前, 73 #+ 会有一段短暂的时间来执行信号处理程序, 74 #+ 在信号处理程序处理的过程中, 很有可能会丢失一个想要捕捉的信号, 因此失去一个产生子进程的机会. 75 76 # 毫无疑问的, 肯定有人能够找出产生这个bug的原因, 77 #+ 并且在将来的某个时候. . . 修正它. 78 79 80 81 # ===================================================================== # 82 83 84 85 # ----------------------------------------------------------------------# 86 87 88 89 ################################################################# 90 # 下面的脚本是由Vernia Damiano原创. 91 # 不幸的是, 它并不能正常工作. 92 ################################################################# 93 94 #!/bin/bash 95 96 # 要想调用这个脚本, 至少需要一个整形参数 97 #+ (并发的进程数). 98 # 所有的其他参数都传递给要启动的进程. 99 100 101 INDICE=8 # 想要启动的进程数目102 TEMPO=5 # 每个进程最大的睡眠时间103 E_BADARGS=65 # 如果没有参数传到脚本中, 那么就返回这个错误码. 104 105 if [ $# -eq 0 ] # 检查一下, 至少要传递一个参数给脚本. 106 then107 echo "Usage: `basename $0` number_of_processes [passed params]"108 exit $E_BADARGS109 fi110 111 NUMPROC=$1 # 并发进程数112 shift113 PARAMETRI=( "$@" ) # 每个进程的参数114 115 function avvia() {116 local temp117 local index118 temp=$RANDOM119 index=$1120 shift121 let "temp %= $TEMPO"122 let "temp += 1"123 echo "Starting $index Time:$temp" "$@"124 sleep ${temp}125 echo "Ending $index"126 kill -s SIGRTMIN $$127 }128 129 function parti() {130 if [ $INDICE -gt 0 ] ; then131 avvia $INDICE "${PARAMETRI[@]}" &132 let "INDICE--"133 else134 trap : SIGRTMIN135 fi136 }137 138 trap parti SIGRTMIN139 140 while [ "$NUMPROC" -gt 0 ]; do141 parti;142 let "NUMPROC--"143 done144 145 wait146 trap - SIGRTMIN147 148 exit $?149 150 : <<SCRIPT_AUTHOR_COMMENTS151 我需要使用指定的选项来运行一个程序, 152 并且能够接受不同的文件, 而且要运行在一个多处理器(SMP)的机器上. 153 所以我想(我也会)运行指定数目个进程, 154 并且每个进程终止之后, 都要启动一个新进程. 155 156 "wait"命令并没有提供什么帮助, 因为它需要等待一个指定的后台进程, 157 或者等待*全部*的后台进程. 所以我编写了[这个]bash脚本程序来完成这个工作, 158 并且使用了"trap"指令. 159 --Vernia Damiano160 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 trap '' 2 # 信号2就是Control-C, 现在被禁用了. 2 command 3 command 4 command 5 trap 2 # 重新恢复Control-C 6 </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 + -
显示快捷键?