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%"> </TD><TDWIDTH="45%"ALIGN="LEFT"VALIGN="TOP"><I><P><I>首先, 调试要比编写代码困难得多, 因此, 如果你尽可能聪明的编写代码, 你就不会在调试的时候花费很多精力.</I></P></I></TD></TR><TR><TDWIDTH="45%"> </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 #!/bin/bash 2 # ex74.sh 3 4 # 这是一个错误脚本. 5 # 哪里出了错? 6 7 a=37 8 9 if [$a -gt 27 ] 10 then 11 echo $a 12 fi 13 14 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 #!/bin/bash 2 # missing-keyword.sh: 这个脚本会产生什么错误? 3 4 for a in 1 2 3 5 do 6 echo "$a" 7 # done # 第7行上的关键字done'被注释掉了. 8 9 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 #!/bin/bash 2 3 # 这个脚本的目的是删除当前目录下的某些文件, 4 #+ 这些文件特指那些文件名包含空格的文件. 5 # 但是不能如我们所愿的那样工作. 6 # 为什么? 7 8 9 badname=`ls | grep ' '` 10 11 # 试试这个: 12 # echo "$badname" 13 14 rm "$badname" 15 16 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 # 删除文件名中包含空格的文件, 下面这才是正确的方法. 2 rm *\ * 3 rm *" "* 4 rm *' '* 5 # 感谢. 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 ### debecho (debug-echo), 由Stefano Falsetto编写 ### 2 ### 只有在DEBUG变量被赋值的情况下, 才会打印传递进来的参数. ### 3 debecho () { 4 if [ ! -z "$DEBUG" ]; then 5 echo "$1" >&2 6 # ^^^ 打印到stderr 7 fi 8 } 9 10 DEBUG=on 11 Whatever=whatnot 12 debecho $Whatever # whatnot 13 14 DEBUG= 15 Whatever=notwhat 16 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 #!/bin/bash 2 # assert.sh 3 4 assert () # 如果条件为false, 5 { #+ 那么就打印错误信息并退出脚本. 6 E_PARAM_ERR=98 7 E_ASSERT_FAILED=99 8 9 10 if [ -z "$2" ] # 传递进来的参数个数不够. 11 then 12 return $E_PARAM_ERR # 什么都不做就return. 13 fi 14 15 lineno=$2 16 17 if [ ! $1 ] 18 then 19 echo "Assertion failed: \"$1\"" 20 echo "File \"$0\", line $lineno" 21 exit $E_ASSERT_FAILED 22 # else 23 # 返回 24 # 然后继续执行脚本余下的代码. 25 fi 26 } 27 28 29 a=5 30 b=4 31 condition="$a -lt $b" # 产生错误信息并退出脚本. 32 # 尝试把这个"条件"放到其他的地方, 33 #+ 然后看看发生了什么. 34 35 assert "$condition" $LINENO 36 # 只有在"assert"成功时, 脚本余下的代码才会继续执行. 37 38 39 # 这里放置的是其他的一些命令. 40 # ... 41 echo "This statement echoes only if the \"assert\" does not fail." 42 # ... 43 # 这里也放置其他一些命令. 44 45 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 + -
显示快捷键?