debugging.html

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

HTML
1,476
字号
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><HTML><HEAD><TITLE>调试</TITLE><METANAME="GENERATOR"CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINKREL="HOME"TITLE="高级Bash脚本编程指南"HREF="index.html"><LINKREL="UP"TITLE="高级主题"HREF="part4.html"><LINKREL="PREVIOUS"TITLE="Zero与Null"HREF="zeros.html"><LINKREL="NEXT"TITLE="选项"HREF="options.html"></HEAD><BODYCLASS="CHAPTER"BGCOLOR="#FFFFFF"TEXT="#000000"LINK="#0000FF"VLINK="#840084"ALINK="#0000FF"><DIVCLASS="NAVHEADER"><TABLESUMMARY="Header navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><THCOLSPAN="3"ALIGN="center">高级Bash脚本编程指南: 一本深入学习shell脚本艺术的书籍</TH></TR><TR><TDWIDTH="10%"ALIGN="left"VALIGN="bottom"><AHREF="zeros.html"ACCESSKEY="P">前一页</A></TD><TDWIDTH="80%"ALIGN="center"VALIGN="bottom"></TD><TDWIDTH="10%"ALIGN="right"VALIGN="bottom"><AHREF="options.html"ACCESSKEY="N">下一页</A></TD></TR></TABLE><HRALIGN="LEFT"WIDTH="100%"></DIV><DIVCLASS="CHAPTER"><H1><ANAME="DEBUGGING"></A>29. 调试</H1><TABLEBORDER="0"WIDTH="100%"CELLSPACING="0"CELLPADDING="0"CLASS="EPIGRAPH"><TR><TDWIDTH="45%">&nbsp;</TD><TDWIDTH="45%"ALIGN="LEFT"VALIGN="TOP"><I><P><I>首先, 调试要比编写代码困难得多, 		  因此, 如果你尽可能聪明的编写代码, 		  你就不会在调试的时候花费很多精力.</I></P></I></TD></TR><TR><TDWIDTH="45%">&nbsp;</TD><TDWIDTH="45%"ALIGN="RIGHT"VALIGN="TOP"><I><SPANCLASS="ATTRIBUTION">Brian Kernighan</SPAN></I></TD></TR></TABLE><P>Bash并不包含调试器, 		甚至都没有包含任何用于调试目的的命令和结构. 	 	   <ANAME="AEN15038"HREF="#FTN.AEN15038"><SPANCLASS="footnote">[1]</SPAN></A>		脚本中的语法错误, 或者拼写错误只会产生模糊的错误信息, 	当你调试一些非功能性脚本的时候, 	这些错误信息通常都不会提供有意义的帮助. 	</P><DIVCLASS="EXAMPLE"><HR><ANAME="EX74"></A><P><B>例子 29-1. 一个错误脚本</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# ex74.sh  3&nbsp;  4&nbsp;# 这是一个错误脚本.   5&nbsp;# 哪里出了错?   6&nbsp;  7&nbsp;a=37  8&nbsp;  9&nbsp;if [$a -gt 27 ] 10&nbsp;then 11&nbsp;  echo $a 12&nbsp;fi   13&nbsp; 14&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>脚本的输出: 	<TABLEBORDER="1"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="SCREEN"><SAMPCLASS="COMPUTEROUTPUT">./ex74.sh: [37: command not found</SAMP></PRE></FONT></TD></TR></TABLE>        上边的脚本究竟哪错了(提示: 注意<BCLASS="COMMAND">if</B>的后边)? </P><DIVCLASS="EXAMPLE"><HR><ANAME="MISSINGKEYWORD"></A><P><B>例子 29-2. 缺少<AHREF="internal.html#KEYWORDREF">关键字</A></B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# missing-keyword.sh: 这个脚本会产生什么错误?   3&nbsp;  4&nbsp;for a in 1 2 3  5&nbsp;do  6&nbsp;  echo "$a"  7&nbsp;# done     # 第7行上的关键字done'被注释掉了.   8&nbsp;  9&nbsp;exit 0  </PRE></FONT></TD></TR></TABLE><HR></DIV><P>脚本的输出: 	<TABLEBORDER="1"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="SCREEN"><SAMPCLASS="COMPUTEROUTPUT">missing-keyword.sh: line 10: syntax error: unexpected end of file</SAMP>	</PRE></FONT></TD></TR></TABLE>	注意, 其实<EM>不</EM>必参考错误信息中指出的错误行号. 	这行只不过是Bash解释器最终认定错误的地方. 	</P><P>出错信息在报告产生语法错误的行号时, 		可能会忽略脚本的注释行.         </P><P>如果脚本可以执行, 但并不如你所期望的那样工作, 怎么办? 	通常情况下, 这都是由常见的逻辑错误所产生的. </P><DIVCLASS="EXAMPLE"><HR><ANAME="EX75"></A><P><B>例子 29-3. test24, 另一个错误脚本</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;  3&nbsp;#  这个脚本的目的是删除当前目录下的某些文件,   4&nbsp;#+ 这些文件特指那些文件名包含空格的文件.   5&nbsp;#  但是不能如我们所愿的那样工作.   6&nbsp;#  为什么?   7&nbsp;  8&nbsp;  9&nbsp;badname=`ls | grep ' '` 10&nbsp; 11&nbsp;# 试试这个:  12&nbsp;# echo "$badname" 13&nbsp; 14&nbsp;rm "$badname" 15&nbsp; 16&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>为了找出<AHREF="debugging.html#EX75">例子 29-3</A>中的错误, 		我们可以把<KBDCLASS="USERINPUT">echo "$badname"</KBD>行的注释符去掉. 		echo出来的信息能够帮助你判断脚本是否按你期望的方式运行. 	</P><P>在这个特定的例子里, 	<KBDCLASS="USERINPUT">rm "$badname"</KBD>之所以没有给出期望的结果, 	是因为<CODECLASS="VARNAME">$badname</CODE>不应该被引用起来. 	加上引号会保证<BCLASS="COMMAND">rm</B>只有一个参数(这就只能匹配一个文件名). 	一种不完善的解决办法是去掉<CODECLASS="VARNAME">$badname</CODE>外面的引号, 	并且重新设置<CODECLASS="VARNAME">$IFS</CODE>, 	让<CODECLASS="VARNAME">$IFS</CODE>只包含一个换行符, 	<KBDCLASS="USERINPUT">IFS=$'\n'</KBD>. 	但是, 下面这个方法更简单. 	<TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;# 删除文件名中包含空格的文件, 下面这才是正确的方法.   2&nbsp;rm *\ *  3&nbsp;rm *" "*  4&nbsp;rm *' '*  5&nbsp;# 感谢. S.C.</PRE></FONT></TD></TR></TABLE>		</P><P>总结一下这个脚本的症状, 	<P></P><OLTYPE="1"><LI><P>由于<SPANCLASS="QUOTE">"<SPANCLASS="ERRORNAME">syntax error</SPAN>"</SPAN>(语法错误)使得脚本停止运行, </P></LI><LI><P>或者脚本能够运行, 		但是并不是按照我们所期望的那样运行(<SPANCLASS="ERRORNAME">逻辑错误</SPAN>). </P></LI><LI><P>脚本能够按照我们所期望的那样运行, 		但是有烦人的副作用(<SPANCLASS="ERRORNAME">逻辑炸弹</SPAN>). </P></LI></OL>      </P><P>如果想调试不工作的脚本, 有如下工具可用: 	<P></P><OLTYPE="1"><LI><P><AHREF="internal.html#ECHOREF">echo</A>语句可以放在脚本中存在疑问的位置上, 		来观察变量的值, 	    也可以了解脚本后续的动作. </P><DIVCLASS="TIP"><P></P><TABLECLASS="TIP"WIDTH="90%"BORDER="0"><TR><TDWIDTH="25"ALIGN="CENTER"VALIGN="TOP"><IMGSRC="./images/tip.gif"HSPACE="5"ALT="Tip"></TD><TDALIGN="LEFT"VALIGN="TOP"><P>最好只在<EM>调试</EM>的时候才使用<BCLASS="COMMAND">echo</B>语句. 	        <TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;### debecho (debug-echo), 由Stefano Falsetto编写 ###  2&nbsp;### 只有在DEBUG变量被赋值的情况下, 才会打印传递进来的参数. ###  3&nbsp;debecho () {  4&nbsp;  if [ ! -z "$DEBUG" ]; then  5&nbsp;     echo "$1" &#62;&#38;2  6&nbsp;     #         ^^^ 打印到stderr  7&nbsp;  fi  8&nbsp;}  9&nbsp; 10&nbsp;DEBUG=on 11&nbsp;Whatever=whatnot 12&nbsp;debecho $Whatever   # whatnot 13&nbsp; 14&nbsp;DEBUG= 15&nbsp;Whatever=notwhat 16&nbsp;debecho $Whatever   # (这里就不会打印.)</PRE></FONT></TD></TR></TABLE>            </P></TD></TR></TABLE></DIV></LI><LI><P>使用过滤器<AHREF="extmisc.html#TEEREF">tee</A>来检查临界点上的进程或数据流. 	      </P></LI><LI><P>设置选项<CODECLASS="OPTION">-n -v -x</CODE></P><P><KBDCLASS="USERINPUT">sh -n scriptname</KBD>不会运行脚本, 只会检查脚本的语法错误. 		这等价于把<KBDCLASS="USERINPUT">set -n</KBD>或<KBDCLASS="USERINPUT">set -o noexec</KBD>插入脚本中. 			注意, 某些类型的语法错误不会被这种方式检查出来. 	      </P><P><KBDCLASS="USERINPUT">sh -v scriptname</KBD>将会在运行脚本之前, 			 打印出每一个命令. 	      这等价于把<KBDCLASS="USERINPUT">set -v</KBD>或<KBDCLASS="USERINPUT">set	      -o verbose</KBD>插入到脚本中. </P><P>选项<CODECLASS="OPTION">-n</CODE>和<CODECLASS="OPTION">-v</CODE>可以同时使用. 		<KBDCLASS="USERINPUT">sh -nv scriptname</KBD>将会给出详细的语法检查.  		</P><P><KBDCLASS="USERINPUT">sh -x scriptname</KBD>会打印出每个命令执行的结果, 			但只使用缩写形式. 			这等价于在脚本中插入<KBDCLASS="USERINPUT">set -x</KBD>或<KBDCLASS="USERINPUT">set -o xtrace</KBD>. </P><P>把<KBDCLASS="USERINPUT">set -u</KBD>或<KBDCLASS="USERINPUT">set 				-o nounset</KBD>插入到脚本中, 并运行它, 		就会在每个试图使用未声明变量的地方给出一个<SPANCLASS="ERRORNAME">unbound variable</SPAN>错误信息. 		</P></LI><LI><P>使用<SPANCLASS="QUOTE">"assert"</SPAN>(断言)函数在脚本的临界点上测试变量或条件. (这是从C语言中引入的.)</P><DIVCLASS="EXAMPLE"><HR><ANAME="ASSERT"></A><P><B>例子 29-4. 使用<SPANCLASS="QUOTE">"assert"</SPAN>来测试条件</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# assert.sh  3&nbsp;  4&nbsp;assert ()                 #  如果条件为false,   5&nbsp;{                         #+ 那么就打印错误信息并退出脚本.   6&nbsp;  E_PARAM_ERR=98  7&nbsp;  E_ASSERT_FAILED=99  8&nbsp;  9&nbsp; 10&nbsp;  if [ -z "$2" ]          # 传递进来的参数个数不够.  11&nbsp;  then 12&nbsp;    return $E_PARAM_ERR   # 什么都不做就return.  13&nbsp;  fi 14&nbsp; 15&nbsp;  lineno=$2 16&nbsp; 17&nbsp;  if [ ! $1 ]  18&nbsp;  then 19&nbsp;    echo "Assertion failed:  \"$1\"" 20&nbsp;    echo "File \"$0\", line $lineno" 21&nbsp;    exit $E_ASSERT_FAILED 22&nbsp;  # else 23&nbsp;  #   返回 24&nbsp;  #   然后继续执行脚本余下的代码.  25&nbsp;  fi   26&nbsp;}     27&nbsp; 28&nbsp; 29&nbsp;a=5 30&nbsp;b=4 31&nbsp;condition="$a -lt $b"     # 产生错误信息并退出脚本.  32&nbsp;                          #  尝试把这个"条件"放到其他的地方,  33&nbsp;                          #+ 然后看看发生了什么.  34&nbsp; 35&nbsp;assert "$condition" $LINENO 36&nbsp;# 只有在"assert"成功时, 脚本余下的代码才会继续执行.  37&nbsp; 38&nbsp; 39&nbsp;# 这里放置的是其他的一些命令.  40&nbsp;# ... 41&nbsp;echo "This statement echoes only if the \"assert\" does not fail." 42&nbsp;# ... 43&nbsp;# 这里也放置其他一些命令.  44&nbsp; 45&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV></LI><LI><P>使用变量<AHREF="internalvariables.html#LINENOREF">$LINENO</A>和内建命令<AHREF="internal.html#CALLERREF">caller</A>. 	      </P></LI><LI><P>捕获exit. </P><P>脚本中的<BCLASS="COMMAND">exit</B>命令会触发一个信号<SPANCLASS="RETURNVALUE">0</SPAN>, 			这个信号终止进程, 也就是终止脚本本身. 			<ANAME="AEN15137"HREF="#FTN.AEN15137"><SPANCLASS="footnote">[2]</SPAN></A>		  捕获<BCLASS="COMMAND">exit</B>在某些情况下很有用, 		  比如说强制<SPANCLASS="QUOTE">"打印"</SPAN>变量值. 	      <BCLASS="COMMAND">trap</B>命令必须放在脚本中第一个命令的位置上. 	      </P></LI></OL>      </P><P></P><DIVCLASS="VARIABLELIST"><P><B><ANAME="TRAPREF1"></A>捕获信号</B></P><DL><DT><BCLASS="COMMAND">trap</B></DT><DD><P>可以在收到一个信号的时候指定一个处理动作; 				在调试的时候, 这一点也非常有用. 	      <DIVCLASS="NOTE"><P></P><TABLECLASS="NOTE"WIDTH="90%"BORDER="0"><TR><TDWIDTH="25"ALIGN="CENTER"VALIGN="TOP"><IMGSRC="./images/note.gif"HSPACE="5"ALT="Note"></TD><TD

⌨️ 快捷键说明

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