文章目录

  • 1. 调试常用选项
  • 2. 输出调试信息
    • 2.1 复杂语句展开
    • 2.2 行号信息显示
  • 3. 调试日志打印
  • 4. 常见错误处理
    • 4.1 不存在的变量
    • 4.2 检查语法错误
    • 4.3 发生错误时终止执行
    • 4.4 管道子命令失败时终止执行

本文主要学习参考10分钟学会Bash调试,仅供个人学习记录用。

shell 是用户和操作系统交互的一个程序,经常用于执行一些自动化或者重复繁琐的任务,现在所有的 Linux 系统基本都自带了该程序,我们只需要编写好 shell 脚本,直接执行就可以了,不需要额外安装软件、配置编译环境,可以说使用起来非常的方便,但是它在调试方面常常令人头大,本文主要介绍 shell 脚本常用的调试方法。

1. 调试常用选项

调试 shell 脚本时,常常用到几个调试选项,让脚本在执行的过程中输出一些调试信息,根据调试信息,就可以定位出具体出问题的代码

具体的选项以及说明如下:

选项 说明
-x 当一行 shell 语句包含多个 shell 命令时,进行命令展开。
-u 当进行参数展开时,遇到除 @* 以外的未定义变量时报错。
-e 发生错误后立即停止执行脚本。
-n 用于检查 shell 脚本的语法错误。
-o pipefail 管道子命令发生错误,终止执行。

2. 输出调试信息

2.1 复杂语句展开

有时候,一条 shell 语句可能包含多个 Linux 命令共同完成,此时为确定出错时可能是哪一个命令导致的,就可能使用到这个功能:

[root@centos ~]# cat debug_demo_v1.sh
#!/bin/bashecho "Date of today is: "$(date +'%Y-%m-%d')

我们使用 -x 选项来执行脚本,结果如下:

[root@centos ~]# bash -x debug_demo_v1.sh
++ date +%Y-%m-%d
+ echo 'Date of today is: 2021-07-24'
Date of today is: 2021-07-24

从结果中可以看到,在执行前按照该行中命令的执行顺序进行了调试信息打印,行前面的 + 号表示调试信息,它实际是环境变量 PS4 的值, PS4 的第一个字符会根据嵌套层次进行重复,命令所处的层次越深,前面的 + 号越多:

[root@centos ~]# set | grep PS4
PS4='+ '

结果中第一行表示执行最先 date +'%Y-%m-%d' 命令,它处于第内层,所以打印两个 + 号 ,第二行表示接着执行 echo "today is :"$(date +'%Y-%m-%d') 命令,它处于外层,只打印一个 + 号。

2.2 行号信息显示

上面示例中脚本内容很少,试想下,如果脚本内容达到了几百行或者几千行之后,输出每一行命令的提示信息,阅读起来就很费劲了,在这种情况下,我们在每行输出前加上行号,可以直接定位到具体的行。修改下 debug_demo_v1.sh 脚本,修改后的脚本内容如下:

[root@centos ~]# cat -n debug_demo_v2.sh 1  #!/bin/bash23  PS4='+ ${BASH_SOURCE}:${LINENO} '45  echo --------------------------------------6  echo "Date of today is: "$(date +'%Y-%m-%d')78  echo --------------------------------------9  echo "Time of today is: "$(date +'%H:%M:%S')

修改之后的脚本加入了 PS4 变量, 它是调试信息的前缀,默认值是 + ,我们可以修改它的值,达到输出的调试信息中包含行号的目的。

上述代码中 ${BASH_SOURCE} 表示当前执行的 shell 脚本的相对路径,在这里用来表示脚本文件名, ${LINENO} 表示行号,修改 PS4 之后,输出的调试信息就会包括 脚本名字以及行号,上述脚本执行结果如下:

[root@centos ~]# bash -x debug_demo_v2.sh
+ PS4='+ ${BASH_SOURCE}:${LINENO} '
+ debug_demo_v2.sh:5 echo --------------------------------------
--------------------------------------
++ debug_demo_v2.sh:6 date +%Y-%m-%d
+ debug_demo_v2.sh:6 echo 'Date of today is: 2021-07-24'
Date of today is: 2021-07-24
+ debug_demo_v2.sh:8 echo --------------------------------------
--------------------------------------
++ debug_demo_v2.sh:9 date +%H:%M:%S
+ debug_demo_v2.sh:9 echo 'Time of today is: 13:05:30'
Time of today is: 13:05:30

有时,我们只需要输出部分调试信息,这个时候就需要我们手动去设置 -x 选项了,把需要输出调试信息的命令放到 set -xset +x 之间:

[root@centos ~]# cat debug_demo_v3.sh
#!/bin/bashPS4='+ ${BASH_SOURCE}:${LINENO} 'echo --------------------------------------set -x
echo "Date of today is: "$(date +'%Y-%m-%d')
set +xecho --------------------------------------echo "Time of today is: "$(date +'%H:%M:%S')

执行脚本,结果如下:

[root@centos ~]# sh debug_demo_v3.sh
--------------------------------------
++ debug_demo_v3.sh:8 date +%Y-%m-%d
+ debug_demo_v3.sh:8 echo 'Date of today is: 2021-07-24'
Date of today is: 2021-07-24
+ debug_demo_v3.sh:9 set +x
--------------------------------------
Time of today is: 13:16:33

3. 调试日志打印

通过打印日志来调试 shell 脚本是常用的方式,在一行命令前后打印变量值或者命令结果,可以通过日志来判断脚本是否有错误。

但是,当脚本比较长的时候,需要打印的日志就有点儿多了;而且,调试完了后,这些调试日志就不再需要了,这时就要一行行的删掉日志打印。

下面介绍一种方法,把脚本中所有的日志打印加一个开关,当开关打开的时候,就会输出调试相关的日志,不需要的时候,直接关闭开关即可。

现有脚本 debug.sh ,内容如下:

[root@centos ~]# cat debug.sh
#!/bin/bash# Debugging switch, 'on' for on, otherwise for off.
IS_DEBUG="on"# Debugging switch function
function __DEBUG()
{[ "$IS_DEBUG" == "on" ] && $@
}num=1
__DEBUG echo 'initial value: '$num
# Increase num by 1
let num++
echo 'new value: '$num

上述脚本中, IS_DEBUG 变量是调试开关,on 表示开启,其他表示关闭; __DEBUG() 是调试开关函数,它的功能是:如果 IS_DEBUGon ,执行后面的命令,否则忽略。

先打开调试开关, 执行脚本,结果如下:

[root@centos ~]# sh debug.sh
initial value: 1
new value: 2

再关闭调试开关,执行脚本,结果如下:

[root@centos ~]# sh debug.sh
new value: 2

关于上述脚本中 $@ 的具体含义,请见文章 Linux 脚本中 $#$0$1$@$*$$$?

4. 常见错误处理

4.1 不存在的变量

执行脚本的时候,遇到不存在的变量,默认会忽略它。现有脚本 undef_var.sh ,内容和执行结果如下:

[root@centos ~]# cat undef_var.sh
#!/bin/bashecho "start..."
echo $var
echo "end..."
[root@centos ~]# sh undef_var.sh
start...end...

可以看到, echo $var 输出了一个空行,脚本直接忽略了不存在的 var 变量, 并且继续执行后面的命令。这种情况通常并不是我们希望的结果,遇到不存在的变量,应该直接报错,并停止执行后面的命令,在脚本开头加上 set -u 语句或者执行脚本的时候加上 -u ,可以得到我们期望的结果:

[root@centos ~]# bash -u undef_var.sh
start...
undef_var.sh: line 4: var: unbound variable

4.2 检查语法错误

语法错误是 shell 脚本执行错误的原因之一,执行脚本的时候加上 -n ,当脚本有语法错误,不会继续执行,而是打印错误信息。现有脚本 check_syntax.sh ,内容如下:

[root@centos ~]# cat -n check_syntax.sh 1  #!/bin/bash23  if [ $# -le 0 ];then4     echo "No positional argument..."

输入 bash -n check_syntax.sh 命令,并回车,结果如下:

[root@centos ~]# bash -n check_syntax.sh
check_syntax.sh: line 5: syntax error: unexpected end of file

上面的脚本中的 if 缺少结尾的 fi ,所以执行 bash -n check_syntax.sh 命令之后会出现语法错误的提示。这个选项很实用,特别是当我们写完 shell 脚本之后,不要急着执行,先使用 -n 选项检查下有没有语法错误,它可以帮我们提前发现错误。

4.3 发生错误时终止执行

一般情况下,脚本执行时发生错误了,还是会继续执行后面的命令,现有脚本 exit_on_error.sh ,内容如下:

[root@centos ~]# cat exit_on_error.sh
#!/bin/bashecho "start..."
command_not_exist
echo "end..."

执行脚本,结果如下:

[root@centos ~]# sh exit_on_error.sh
start...
exit_on_error.sh: line 4: command_not_exist: command not found
end...

从结果可以看到,脚本中第 4 行的 command_not_exist 是未知的命令,执行时发生了错误,但是脚本还是继续向后执行,一直到结束。这种行为不利于脚本的安全和错误排查,在实际应用中,发生了错误应该停止执行脚本,防止错误越积越多,我们可以使用 -e 选项来避免这个问题。

加上 -e 选项,再次执行上述脚本,结果如下:

[root@centos ~]# bash -e exit_on_error.sh
start...
exit_on_error.sh: line 4: command_not_exist: command not found

从上面结果可以知道,脚本执行到第 4 行的时候发生了错误,此时脚本停止往下执行了。

4.4 管道子命令失败时终止执行

上面提到的 -e 选项有个特殊的情况,不适用于管道命令,管道命令是通过管道符 | 组合的命令, 具体的看下面的例子吧,现将脚本 exit_on_error.sh 修改后内容如下:

[root@centos ~]# cat exit_on_error.sh
#!/bin/bashecho "start..."
command_not_exist | echo 'Make each day count!'
echo "end..."

执行 bash -e exit_on_error.sh 命令后,结果如下:

[root@centos ~]# bash -e exit_on_error.sh
start...
exit_on_error.sh: line 4: command_not_exist: command not found
Make each day count!
end...

可以看到,即使使用 -e 选项执行脚本,发生错误的时候,还是会继续往下执行,直到结束。我们可以使用 -o pipefail 来解决这种情况,只要管道命令中一个子命令发生了错误,整个管道命令就失败了,脚本就会终止执行。例如:

[root@centos ~]# bash -e -o pipefail exit_on_error.sh
start...
exit_on_error.sh: line 4: command_not_exist: command not found
Make each day count!

加了选项 -o pipefail 之后,再次执行脚本, 管道命令 command_not_exist | echo 'Make each day count!' 执行子命令 command_not_exist 时发生错误,后续的子命令不再执行了,整个管道命令失败了。其实,也可以通过在由管道符连接的命令之前加上 set -o pipefail 来实现同样的效果。

又由于执行时加了 -e 选项,当管道命令执行失败了,脚本就会终止执行,所以 echo "end..." 没有执行。

【Shell 脚本进阶】使用 bash 命令对 shell 脚本进行调试相关推荐

  1. BASH命令和SHELL脚本学习

    BASH命令和SHELL脚本学习 转载于:https://www.cnblogs.com/huolong123/p/6228049.html

  2. 69:shell脚本介绍 | shell脚本结构 | 执行data命令用法 | shell脚本中变量

    2019独角兽企业重金招聘Python工程师标准>>> 1.shell脚本介绍: shell是一种脚本语言和传统的开发语言相比,会比较简单: shell有自己语法,可以支持逻辑判断. ...

  3. linux脚本里调执行命令,使用shell的-n/-x/-x执行选项调试Shell脚本

    我们在前面介绍的调试手段是通过修改shell脚本的源代码,从其输出相关的调试信息来定位错误的,那有没有不修改源代码来调试shell脚本的方法呢?有的,那就是使用shell的执行选项,下面将介绍一些常用 ...

  4. linux shell编程 ppt,Linux常用命令与Shell基本编程.ppt

    Linux常用命令与Shell基本编程.ppt Shell 脚本基本编程,无线产品部 katanazhang 2009-11-09,课程目标,linux 常用命令 shell 脚本编程 awk 的用法 ...

  5. python:通过python脚本快速执行 bash 命令

    * git镇楼:git config --global core.filemode false* 实践出真知.虽然这个脚本代码量不大,但是也是经过3次修改才达到预期效果的. * 第一次写的时候,凭逻辑 ...

  6. Shell编程进阶 1.3data命令

    date命令是显示日期时间的命令 date 2016年 01月 01日 星期五 15:05:01 CST 修改时间的选项是 -s date -s "2016-01-01 12:56:10&q ...

  7. linux 脚本编写基本命令,Linux Shell命令行及脚本编程实例详解

    <Linux典藏大系:Linux Shell命令行及脚本编程实例详解>共15章,分为两篇.主要内容包括:Linux 及Linux Shell简介.初识Linux Shell.常用Shell ...

  8. 【Shell脚本进阶】从此彻底搞懂 Linux 环境变量及 Shell 启动文件 /etc/profile 、 ~/.bash_profile 和 ~/.bashrc(建议收藏)

    文章目录 1. 环境变量详解 1.1 全局环境变量 1.2 本地环境变量 1.3 自定义环境变量 1.3.1 自定义本地环境变量 1.3.2 自定义全局环境变量 1.4 删除环境变量 2. 启动文件详 ...

  9. shell变量加单引号sql_关于shell:在Bash中的命令中扩展变量的单引号

    我想从bash shell脚本中运行一个命令,该脚本在单引号和变量中包含单引号和一些其他命令. 如repo forall -c '....$variable'. 在这种格式中,对$进行转义,不展开变量 ...

最新文章

  1. linux下glew例子,一个简单的GLSL Shader例子
  2. 区块链与边缘计算(3)系统介绍
  3. Java学习日志(23-3-网络编程-TCP)
  4. Oracle中类似于isql或osql的命令行工具
  5. docker rabbitmq php扩展,Docker开启RabbitMQ延时消息队列
  6. 关于Typora编辑器编写markdown文档时插入出现前字吞后字的解决方法
  7. 西门子智能门锁设计_如何过度设计门锁
  8. Git 历史记录内容对比
  9. 纽约出租车计费问题:一个简单的线性模型
  10. Unity3D贪吃蛇
  11. 扩展GridView控件(3) - 根据按钮的CommandName设置其客户端属性
  12. 《晨间日记的奇迹》内容概要及读书心得
  13. R的可视化以及ggplot2
  14. Bootstrap下拉菜单失效的解决方法+使用Bootstrap制作响应式网页
  15. java String、Json对象与byte数组转换
  16. Java implement意思_详解JAVA中implement和extends的区别
  17. C++ open函数
  18. Pytorch中DistributedSampler()中的随机因素
  19. 基于WEB 的实时事件通知
  20. R:parse函数和eval函数解析字符串为命令并运行

热门文章

  1. SeetaFace编译使用 中科院人脸识别SDK
  2. wsappx关不掉_wsappx是什么进程?Win10系统下如何关闭wsappx?
  3. 浏览器访问Linux上部署的jar包
  4. 摄像头安全隐患大安防层级待提升
  5. Android4.4 Telephony流程分析——去电(MO)流程
  6. 小程序这13大新能力,将对你产生什么影响?
  7. CoreData用法三: NSPredicate在CoreData中的使用
  8. VOD(视频点播技术)基本原理
  9. 排球分组循环交叉编排_如果一届排球赛中有20个参赛队,分别是ABC……T队,第一阶段将参赛队分成四组,用分组单循环的方法...
  10. (分代)垃圾回收的过程