本文转载自微信公众号《Linux开发那些事》,作者LinuxThings。转载此文请联系Linux开发那些事公众号.shell是用户与操作系统交互的程序。常用于执行一些自动化或重复性繁琐的工作。现在所有的Linux系统基本上都自带这个程序。我们只需要编写一个shell脚本,直接执行即可。无需安装额外的软件和配置编译环境。可以说使用起来非常方便,但是在调试的时候却常常让人头疼。本文主要介绍shell脚本的常用调试方法。在调试常用选项时,经常会用到几个。调试选项,使脚本在执行过程中会输出一些调试信息。根据调试信息,可以定位到具体有问题的代码。具体选项及说明如下:选项说明-x在输出结果前输出执行的命令-u遇到不存在的变量时,会报错并停止执行-e发生错误时,执行将被终止-n检查语法错误-opipefail当管道子命令发生错误时,将终止执行跟踪脚本的执行并输出调试信息通常情况下,脚本执行完后,只会输出结果。当运行多个命令时,会连续输出多个结果,无法区分哪个命令对应哪个结果。使用-x选项会先输出要执行命令的调试信息。然后执行命令existingscriptta.sh,功能是输出当前日期,内容如下#!/bin/bashecho"todayis:"$(date+'%Y-%m-%d')我们使用-x选项执行Script,结果如下[root@VM-0-2-centosshell_debug]#bash-xta.sh++date+%Y-%m-%d+echo'todayis:2021-07-10'todayis:2021-07-10从结果可以看出,每行命令在执行前都会打印出来,行前的+号表示调试信息,其实就是环境变量PS4的值,PS4的第一个字符将根据嵌入而改变命令越深,前面的+号就越多。结果中第一行意思是执行date+'%Y-%m-%d'命令,在里层,所以打印两个+号,第二行意思是执行echo"todayis:"$(date+'%Y-%m-%d')命令,在外层,只打印一个+号,把-x选项放到#!/bin/bash语句后,同样的效果可以执行时不用-x即可实现。上面的脚本只需要将#!/bin/bash改成#!/bin/bash-x就可以输出上面例子中脚本的行号了,内容很少。试想一下,如果脚本内容达到数百行或数千行,要阅读每一行命令的提示信息将非常费力。在这种情况下,我们在每行输出编号之前添加一行,您可以直接定位到具体行并修改ta.sh脚本。修改后的内容如下#!/bin/bashPS4='+${BASH_SOURCE}:${LINENO}'echo"start..."set-xecho"todayis:"$(date+'%Y-%m-%d')set+xecho"end..."修改后的脚本增加了PS4变量,它是调试信息的前缀,默认值为“+”,我们可以修改它的值来达到包含的目的输出调试信息中的行号。上面代码中的“${BASH_SOURCE}”表示当前正在执行的shell脚本的相对路径,这里用来表示脚本文件名,“${LINENO}”表示行号。修改PS4后,输出的调试信息会包含脚本名和行号。我们执行脚本,看看结果[root@VM-0-2-centosshell_debug]#bash-xta.sh+PS4='+${BASH_SOURCE}:${LINENO}'+ta.sh:4echostart...start...++ta.sh:5date+%Y-%m-%d+ta.sh:5echo'todayis:2021-07-10'todayis:2021-07-10+ta.sh:6echoend...end...从结果可以看出,每条命令行的调试信息包括调试信息的文件名和行号输出部分。有时候,我们只需要输出部分调试信息。这时候我们需要手动设置-x选项。将需要输出调试信息的命令放在set-x和set+x之间,修改ta.sh脚本。内容如下#!/bin/bashecho"test..."set-xecho"todayis:"$(date+'%Y-%m-%d')set+xecho"finish..."执行脚本,结果如下[root@VM-0-2-centosshell_debug]#./ta.sh[root@VM-0-2-centosshell_debug]#./ta.shtest...++date+%Y-%m-%d+echo'todayis:2021-07-10'todayis:2021-07-10+set+xfinish...从结果可以看出,只有echotodayis:"$(date+'%Y-%m-%d')命令输出调试信息,set-x相当于开启调试信息,set+x是关闭调试信息,这里需要注意的是在脚本中使用set-x时,do执行时不加-x日志打印常见的调试shell脚本的方式是打印日志这种方式在一行命令前后打印变量值或命令结果,通过日志判断是否有errors,不再需要了,所以需要逐行删除日志打印,下面介绍一种方法,在所有日志打印中添加一个开关脚本。打开开关后,会输出调试相关的日志。不需要的时候,可以直接关掉开关。现有脚本debug1.sh,内容如下#!/bin/bash#调试开关,on表示打开,其他表示关闭IS_DEBUG="on"#调试开关函数function_DEBUG(){["$IS_DEBUG"=="on"]&&$@}va=1_DEBUGecho'oldvalue:'$va#Variablevalplus1letva++echo'newvalue:'$va上面脚本中,IS_DEBUG变量是调试开关,"on"表示开启,其他表示关闭_DEBUG()是调试开关函数,其作用是:if如果IS_DEBUG为“on”,则执行以下命令,否则忽略先打开调试开关,执行脚本,结果如??下[root@VM-0-2-centosshell_debug]#./debug1.sholdvalue:1newvalue:2然后关闭debug开关,执行Script,结果如下[root@VM-0-2-centosshell_debug]#./debug1.shnewvalue:2从上面两个测试结果可以看出,当debug开关打开,即设置IS_DEBUG="on"后,statement_DEBUGecho'oldvalue:'$va会执行echo'oldvalue:'$va命令。当IS_DEBUG="off"时,echo'oldvalue:'$va命令将被忽略。所以,在调试的时候,打开调试开关,调试完成后,脚本不需要做任何修改,只需要关闭开关,调试相关的命令就不会执行了。不存在常见的错误处理。忽略现有脚本td.sh,内容如下#!/bin/bashecho"start..."echo$taecho"end..."脚本中ta为不存在的变量,脚本执行结果如下[root@VM-0-2-centosshell_debug]#./td.shstart...end...可以看到,echo$ta输出了一个空行,脚本忽略了非-直接存在ta变量,继续执行后面的命令。这种情况通常不是我们想要的结果。如果遇到不存在的变量,应该直接报错,停止执行后面的命令。在脚本开头添加set-u语句或者在执行脚本时添加-u,可以得到我们预期的结果,在脚本开头添加set-u语句,整个脚本内容如下按照#!/bin/bashset-uecho"start..."echo$taecho"end..."执行脚本,结果如??下[root@VM-0-2-centosshell_debug]#./td.shstart..../td.sh:line5:ta:unboundvariable可以看到,加上set-u语句后最后遇到不存在的变量ta,直接报错,停止执行后面的命令。当然我们使用bash-utd.sh命令执行脚本也会得到同样的结果。语法错误。语法错误是shell脚本执行错误的原因之一。1.执行脚本时加上-n。当脚本出现语法错误时,不会继续执行,而是打印错误信息。现有脚本te.sh,内容如下#!/bin/bashif[$#-le0];thenecho"noparam.."输入bash-nte.sh命令回车,结果如下[root@VM-0-2-centosshell_debug]#bash-nte.shte.sh:line5:syntaxerror:unexpectedendofffile上面的脚本中如果没有结尾的fi,那么执行bash-nte.sh命令后,会有一个语法错误提示。这个选项很有用,尤其是当我们写完shell脚本的时候,不要急于执行。先用-n选项检查是否有语法错误,可以帮助我们提前发现错误,终止执行。一般情况下,如果脚本执行过程中出现错误,它会继续执行后面的命令。现有脚本tf.sh,内容如下#!/bin/bashecho"start..."abcecho"end..."执行脚本,结果如??下[root@VM-0-2-centosshell_debug]#./tf.shstart..../tf.sh:line4:abc:commandnotfoundend..从结果可以看出脚本第四行的abc是未知命令,出现错误在执行期间,但脚本继续向后执行直到结束。这种行为不利于脚本的安全和错误检查。在应用程序中,如果发生错误,应停止脚本的执行,以防止错误累积。我们可以使用-e选项来避免这个问题。添加-e选项再次执行上述脚本,结果如??下[root@VM-0-2-centosshell_debug]#bash-e./tf.shstart..../tf.sh:line4:abc:commandnotfound从上面的结果我们可以知道脚本执行到第四行的时候出现了错误,此时脚本停止执行如果pipeline子命令失败,上面提到的-e选项的执行有一个特殊的情况下,不适用于管道命令。管道命令是用管道符号“|”组合的命令。有关详细信息,请参见以下示例。现有脚本tg.sh,内容如下#!/bin/bashecho"start..."abc|echo"111"echo"end..."脚本的第四行,abc|echo"111"是一个管道命令。我们执行bash-e./tg.sh命令后,结果如下[root@VM-0-2-centosshell_debug]#bash-e./tg.shstart..../tg.sh:line4:abc:commandnotfound111end...可以看到即使使用-e选项执行脚本,当出现错误时,也会一直执行到结束。我们使用set-opipefail来解决这种情况,只要pipeline命令中的某个子命令出错,整个pipeline命令就会失败,脚本就会终止执行。修改以上脚本,内容如下#!/bin/bashset-opipefailecho"start..."abc|echo"111"echo"end..."再次执行脚本,结果如??下[root@VM-0-2-centosshell_debug]#bash-etg.shstart...tg.sh:line5:abc:commandnotfound111可以看到在tg.sh脚本开头加入set-opipefail语句后,执行再次编写脚本,管道命令abc|echo"111"在执行子命令abc时出错,后面的子命令不再执行。整个管道命令失败,因为在执行过程中添加了-e选项。当管道命令执行失败时,脚本会终止执行,所以不会执行echo"end..."
