1. 语法错误

一类常见的错误和语法有关,即语法错误。语法错误包括输错了某些Shell语法元素。如果碰到此类错误,Shell会停止执行脚本。

1.1 缺少引号

[sysadmin@ansible bin]$ cat error
#!/bin/bash#演示语法错误number=1
if [ $number = 1 ]; thenecho "Number is equal to 1.
elseecho "Number is not equal to 1."
fi
[sysadmin@ansible bin]$ error
/home/sysadmin/bin/error: line 9: unexpected EOF while looking for matching `"'
/home/sysadmin/bin/error: line 11: syntax error: unexpected end of file

值得注意的是,错误信息中所报告的行号并非缺少引号的第7行位置,而是第9行。因为系统把第9行的第一个双引号当作是第7行引号的闭合了。而第9行第2个引号则因为未闭合,而报错。

1.2 缺少词法单元

另一种常见错误是没有把符合或命令(如if或while)写完整。让我们来看一看如果去掉if命令中的test命令之后的分号会出现什么情况:

[sysadmin@ansible bin]$ cat error
#!/bin/bash#演示语法错误number=1
if [ $number = 1 ] thenecho "Number is equal to 1."
elseecho "Number is not equal to 1."
fi
[sysadmin@ansible bin]$ error
/home/sysadmin/bin/error: line 8: syntax error near unexpected value `else'
/home/sysadmin/bin/error: line 8: `else'

本例是缺少了if命令之后的分号,但错误信息缺指向了实际问题之后的一个错误。

1.3 出乎意料的扩展

脚本可能会出现间歇性地出现错误,有时候脚本执行正常。有时候又会因为扩展结果而出错。将number的值修改为空,再来演示一下。

[sysadmin@ansible bin]$ cat error
#!/bin/bash#演示语法错误number=
if [ $number = 1 ]; thenecho "Number is equal to 1."
elseecho "Number is not equal to 1."
fi
[sysadmin@ansible bin]$ error
/home/sysadmin/bin/error: line 6: [: =: unary operator expected
Number is not equal to 1.

问题在于test命令内的number变量的扩展,当执行下列命令时[ $number = 1 ],$number经过扩展后,结果为空,test命令变成了

[ = 1 ]

这种形式显然是不合法的,因而产生了错误。=是一个二元操作符(要求操作符两侧都要有值),但现在少了一第一个值,所以test命令只能寄望于一元操作符(例如,-z)。由于test执行失败,if命令得到的非0退出状态,因此执行了第二个echo命令。给test命令的第一个参数加上引号就能解决这个问题.

[ "$number" = 1 ]

这样一来,参数数量就没错了。除了空串,双引号还可用于某个值会被扩展成多单词字符串的情况,因为文件名中是可以包含空格的。

始终坚持将变量和命令替换放入双引号中,除非需要单词分割。把这句话作为一条规则记住。

1.4 逻辑错误

和语法错误不同,逻辑错误不会妨碍脚本执行,脚本照样可以执行,但因为逻辑有问题,无法产生理想的结果。常见的包括以下几种:

  • 条件表达错误 if/then/else表达式很容易写错,从而造成逻辑错误,错误表达的逻辑与正确表达的逻辑南辕北辙,或者压根没表达完整。
  • ”差一“错误,在使用计数器的循环中,可能会忽略循环计数需要从0而不是从1开始,才能在正确的点完成计数。这种错误要么导致循环超出终点(计数过多),要么导致少了最后一次迭代(循环提前结束)。
  • 非预期情况。大多数逻辑错误是由于程序遇到了程序员没预想到的数据或情况引起的。非预期扩展也包括在内,例如包含空格符的文件名被扩展成了多个命令参数,而非单个文件名。

1.5 防御式编程

编程时的各种假设很重要,意味着要仔细评估程序的退出状态以及脚本中用到的命令。下面举一个例子

cd $dir_name
rm *

看上述代码,只要dir_name变量的目录存在,以上两行代码就没有什么本质性的错误。但是,如果目录不存在呢?这种情形下,cd命令执行失败,脚本接着执行下一行,删除当前工作目录中的所有文件。这可完全不是期望的结果。由于设计的问题,销毁了服务器中一部分重要文件。下面看几个改进措施

cd "$dir_name" && rm *

按照上述方法,如果cd命令执行失败,rm命令并不会执行。脚本有所改进,但存在变量dir_name不存在或为空的可能性,这会导致用户主目录内的文件全部被删除。可以通过检查dir_name是否存在来解决这个问题。

[[ -d "$dir_name" ]] && cd "$dir_name" && rm *

通常要加入终止脚本的逻辑,并在发生上述情况时报告错误:

#删除目录$dir_name中的文件
if [[ ! -d "$dir_name" ]]; thenecho "No such directory: '$dir_name'" >&2exit 1
fi
if  ! cd "$dir_name" ; thenecho "Cannot cd to '$dir_name'" >&2exit 1
fi
if ! rm *;  thenecho "File deletion failed.Check results" >&2exit 1
fi

1.6 小心文件名

事实上,Linux中只有两个字符不能出现在文件名中,一个是/,因为该字符用于分割路径名中的各个部分;另一个是空字符,该字符用于在内部标示字符串结束。除此之外的所有字符都是合法的,其中包括空格符、制表符、换行符、前导连字符,回车符等。
尤其是前导连字符,例如,把文件命名为-rf完全没有任何问题。想想如果将该文件名作为参数传给rm会有什么后果。
为了防范这个问题,把文件检测脚本中的rm命令由:

rm *

修改为

rm ./*

这就避免了以连字符开头的文件名被误解为命令选项。作为通用规则,始终坚持在通配符(如和?)之前加上./,以免命令误解,例如.pdf和???.mp3

可移植的文件名,有一个叫做POSIX可移植文件名字符集的标准,最大程度上提高文件名跨系统使用的可能。标准非常建单,它允许的字符仅包括大写字母A-Z,小写字母a-z,数字0-9,点号,连字符-,下划线。此外,还建议文件名不要以连字符开头。

1.7 核实输入

一个良好的编程习惯,如果程序需要接受输入,那么必须能够应对所有的输入内容。通常意味着一定要核实输入,保证只对有效输入做进一步处理。比如前面在讲read命令时,见过类似的例子。

[[ $REPLY =~ ^[0-3]$ ]]

测试非常具体,如果用户输入的字符串是在0~3范围内的数字,则返回退出状态0值,其他输入概不接受。这种测试有时候写起来有难度,但要想得到高质量的脚本,努力是必不可少的。

如果脚本要作生产之用,也就是说,会重复用于重要任务或被多个用户使用,在开发的时候程序员可就要加倍小心了。

1.8 测试

测试是包括脚本在内的所有软件开发中的重要步骤,下面介绍如何利用脏代码核实程序流程,在脚本开发的早期,脏代码是检查工作进展的重要技术手段,如下例:

if [[ -d $dir_name ]]; thenif cd $dir_name; thenecho rm * # 易测试性elseecho "Cannot cd to '$dir_name'" >&2exit 1fi
elseecho "no such directory: '$dir_name'" >&2exit 1
fi
exit

最重要的改动是在rm命令之前放置了echo命令,使rm命令及其扩展后的命令参数得以显示出来,而不是执行删除操作。在代码片段的末尾,添加了exit命令来结束测试,避免执行脚本的其他部分。

1.9 测试用例

要想执行有效的测试,重要的是开发和应用高质量的测试用例。这要求仔细选择能反映出边角情况的输入数据和操作条件。需要测试以下3种特定条件下的执行情况。

  • dir_name包含的是已存在的目录名
  • dir_name包含的是不存在的目录名
  • dir_name为空

2.调试

2.1 查找问题区域

有些脚本中,尤其是长脚本,有时候将其中与问题相关的部分隔离出来还是有必要的,这部分未必就是问题所在,但是往往能帮助我们发现实际原因。一种隔离代码的技术是“注释掉”一部分脚本。例如,我们可以修改文件删除代码片段,确定去掉的部分是否与错误有关。

if [[ -d $dir_name ]]; thenif cd $dir_name; thenecho rm * # 易测试性elseecho "Cannot cd to '$dir_name'" >&2exit 1fi
#else
#   echo "no such directory: '$dir_name'" >&2
#   exit 1
fi
exit

通过将注释符放置在脚本逻辑片段内的各行之前,就可以阻止这部分代码被执行。再次执行测试,看一看去掉这部分代码对Bug有没有什么影响。

2.2 跟踪

一种跟踪技术是通过在脚本中添加能够显示执行位置的提示信息

echo "preparing to delete files" >&2
if [[ -d $dir_name ]]; thenif cd $dir_name; thenecho "deleting files" >&2rm *elseecho "Cannot cd to '$dir_name'" >&2exit 1fi
elseecho "no such directory: '$dir_name'" >&2exit 1
fi
echo "file deletion complete" >&2

我们将信息发送至标准错误,以便与正常输出结果区分开。另外,我们也没有对包含消息的代码进行缩进,这样在需要删除这些行的时候,更容易查找。

bash还通过-x选项和set命令的-x选项提供了另一种跟踪技术。在前文的trouble脚本的第一行加入-x选项,激活整个脚本的跟踪功能:

[sysadmin@ansible bin]$ cat trouble
#!/bin/bash -xnumber=1if [ $number = 1 ]; thenecho "Number is equal to 1."
elseecho "Number is not equal to 1."
fi
[sysadmin@ansible bin]$ trouble
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number is equal to 1.'
Number is equal to 1.

启用跟踪后,我们可以查看命令经过扩展之后的执行情况。行首的加号表示此行是跟踪显示的结果,以区别于一般的输出结果。加号是用于跟踪输出的默认字符,由Shell变量PS4设定的。修改PS4,加入当前所跟踪代码的行号。注意,单引号用于将扩展推迟到真正使用提示符的时候。

[sysadmin@ansible bin]$ export PS4='$LINENO + '
[sysadmin@ansible bin]$ trouble
3 + number=1
5 + '[' 1 = 1 ']'
6 + echo 'Number is equal to 1.'
Number is equal to 1.

如果只是想对部分脚本进行跟踪,可以使用带有-x选项的set命令:

#!/bin/bash -xnumber=1
set -x #打开跟踪
if [ $number = 1 ]; thenecho "Number is equal to 1."
elseecho "Number is not equal to 1."
fi
set +x #关闭跟踪

2.3 在执行过程中检查值

在跟踪过程中,显示变量的值,以此了解脚本执行时的内部工作状态,往往能派上大用场,可以通过添加echo语句来实现。

#!/bin/bash -xnumber=1
echo "number=$number"  #调试
set -x #开启跟踪
if [ $number = 1 ]; thenecho "Number is equal to 1."
elseecho "Number is not equal to 1."
fi
set +x #关闭跟踪

Shell脚本故障诊断相关推荐

  1. jar包部署shell脚本编写,在服务器上部署jar包,在Linux服务器上部署服务,设置编码格式,设置内存管理

    准备步骤: 1.安装java环境,知道java安装目录 2.将jar包拖放或发送至服务器中(目录自定义) 一.编写shell脚本,将以下代码放在shell脚本中,将shell脚本放在jar包同级目录下 ...

  2. 快速给shell脚本加上使用提示

    我们只需通过在shell脚本前面加上如下的代码即可: #!/bin/bash ### ### my-script - does one thing well ### ### Usage: ### my ...

  3. Ubuntu系统执行shell 脚本的方法

    使用前了解 Shell: Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁 Shell: Shell 脚本(shell script),是一种为 shell 编写的脚本程序. 下面 ...

  4. Windows 系统执行Shell 脚本的方法

    使用前了解 Shell: Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁 Shell: Shell 脚本(shell script),是一种为 shell 编写的脚本程序. 下面 ...

  5. shell监控java接口服务_Linux系统下Java通过shell脚本监控重启服务

    简介 最近运维人员提出需求,增加一个运维页面, 查询当前的业务进程信息包括:进程名称.启动命令.启动时间.运行时间等,可以通过页面点击重启按钮,可以重启后端的一系列系统进程. 思路 java程序获取l ...

  6. linux 脚本 alias,在shell脚本中使用alias别名

    本文最后更新于2015年7月12日,已超过 1 年没有更新,如果文章内容失效,还请反馈给我,谢谢! 缘由: 经常用Linux进行操作的同学一般都会有各种各样的技巧来提升工作效率,而添加/改写shell ...

  7. linux shell脚本攻略_(python)Linux下shell脚本监控Tomcat的状态并实现自动启动步骤...

    今天为大家带来的内容是:(python)Linux下shell脚本监控Tomcat的状态并实现自动启动步骤 本文内容主要介绍了Linux下shell脚本监控Tomcat的状态并实现自动启动的步骤,文章 ...

  8. linux重启sh脚本,Linux 之shell脚本系列之服务启动/关闭/重启/状态

    一 问题 自己开发的程序,如何用shell 脚本实现 启动,关闭,重启,查看状态? 二 方案 myshell.sh #!/bin/sh SERVICE="fm_tuoguan_shell&q ...

  9. linux按文件名排序ls,linux – 如何使用shell脚本按名称对文件进行排序

    我想用 Shell脚本按日期排序所有文件. 例如,在/ Users / KanZ / Desktop / Project / Test /中有文件M1.h,A2.h和F4.h. 每个文件都有不同的时间 ...

最新文章

  1. 图解 Attention
  2. 服务器控件调用JS方法
  3. 在Core WebApi中使用Swagger
  4. 动态规划 0-1背包问题 二维数组
  5. 查看一个进程对应的端口号
  6. 【已解决】ModuleNotFoundError: No module named ‘web’的解决办法:
  7. mysql企业版安装_mysql企业版怎么安装图解
  8. nginx工作原理与配置
  9. 多媒体文件格式全解说(下)--图片
  10. 一款支持mqtt协议的数据网关
  11. content-box和boder-box的区别
  12. unapp Error: Unbalanced delimiter found in string
  13. 企业微信群:机器人实现定时提醒功能
  14. 数据库常用字段、列属性、表类型与SQLyog工具的使用
  15. excel表格内容拆分_3个动图,教你学会如何让excel表格自动拆分,学会它,小白变大神...
  16. pythongui库推荐_八款常用的 Python GUI 开发框架推荐
  17. 腾讯云服务器到期未续费,CVM实例被释放还能找回文件吗?
  18. Boost.Locale 之字符转换 gbk utf8 big5 string wstring等
  19. 使用webrtc-streamer查看实时监控
  20. woo语言实现 m3u8流媒体视频文件 下载并播放

热门文章

  1. 数据分析面试【概率题】-----笔试/面试部分题型解答
  2. ExpRe[6] 云服务器[0] 基础使用,ssh连接
  3. 服务器部署——配置hadoop集群
  4. 【BZOJ1116】[POI2008]CLO【BFS】
  5. LabVIEW下拉列表和枚举控件
  6. 用小米手环装逼,你不可不知的五个姿势
  7. 简单计算器(YZOJ-1047)
  8. nohup日志使用cronolog按日切分
  9. 管理格言集锦(收集)
  10. greendao连接mysql_GreenDao深入