ShellCheck 的目标是
以上来自于官网github文档的谷歌机翻,地址 https://github.com/koalaman/shellcheck
我直接贴一下当时用的代码吧。
echoError() { echo -e "\033[[email protected]\033[0m" } # red echoWarning() { echo -e "\033[[email protected]\033[0m" } # yellow echoSuccess() { echo -e "\033[[email protected]\033[0m" } # green echoInfo "\n---------------------检查是否正确挂载/database盘---------------------------" database_mount=$(df -hT | awk '$7~/^\/database$/{print $7}') if [[ "database_mount" == "/database" ]];then echoSuccess "找到独立挂载的 /database" #找到/database盘后打印盘的文件系统类型 filesystem_type=$(df -hT | awk '$7~/^\/database$/{print $2}') if [[ "filesystem_type" == "xfs" ]];then echoSuccess "文件系统格式正确,为 xfs" else echoError "文件系统格式错误,为 ${filesystem_type}" fi lsblk |awk '{print $6 $7}' |grep 'lvm/database$' >/dev/null \ && echoSuccess "/database 采用 lvm 逻辑卷管理,这是建议的,方便扩容" \ || echoWarning "/database 没有采用 lvm 逻辑卷管理,不方便扩容,请确认" else echoError "未找到独立挂载的 /database" fi
代码一开始,先定义 3 个颜色输出函数,红色为 error 级别,黄色为 warning 级别,绿色为 success 级别。
我的代码要实现的功能是,检查服务器磁盘数据目录 /database 的挂载情况,如果 /database 目录未独立挂载则输出 error,如果 /database 目录独立挂载,则继续判断是否是 xfs 文件系统格式挂载,不是的话则输出 error,再判断是否适用了 lvm 逻辑卷管理,不是的话则输出 warning,其余情况输出 success。
执行脚本结果如下:
[[email protected] ~]# sh -x test.sh test.sh: line 22: syntax error: unexpected end of file
报了个语法错误,但我对这个报错一脸懵逼,第 22 行有语法错误?我的脚本压根没有 22 行好吗?(最后一行是 21 行)
我仔细又读了一遍代码,还是没有找到原因,这时,我怀疑是不是因为我脚本是 windows 平台上编写,传上去后有特殊字符导致的?
注意了,必须 cat -A 才能看得到肉眼看到换行符、制表符,直接 vi 或 vim 是分辨不出来的
Unix 系统的换行符为 \n,而 Windows 的换行符是 \r\n,把 Windows 记事本编辑的文件上传到 Unix 系统后,就有可能有换行符兼容问题,在每行末尾多显示个 "^M"
如图,test.sh 脚本没有 windows 的特殊换行符"^M",因为我当时是用 notepad++ 编写的,选择了 Unix 模式。
作为对比,下面这个是 windows 上用记事本开发的 test2.sh 脚本,带 windows 的特殊换行符"^M"
"^M"也可以理解为 windows 的特殊换行符了,^I 表示 tab 制表符(倒数第二行,蓝色方框,为了演示我这里故意偷偷替换了一处空格为制表符)。
在 shell 中制表符不影响运行,而 ansible playbook 则影响运行。而 "^M"则对 shell 或 ansible playbook 都影响。
所以,带 "^M" 的 test2.sh 脚本运行的报的是另外一种错误。
[[email protected] ~]# sh -x test2.sh test2.sh: line 13: syntax error near unexpected token `else' 'est2.sh: line 13: ` else
我们可以使用 dos2unix 命令很方便的把 windows 换行符转换为 unix 换行符
[[email protected] ~]# dos2unix test2.sh dos2unix: converting file test2.sh to Unix format ... [[email protected] ~]# sh -x test2.sh test2.sh: line 22: syntax error: unexpected end of file
现在 test2.sh 执行报错和 test.sh 一样了。
为了解决这个奇怪的语法报错问题,我只好下载了知名工具 ShellCheck,我给这个工具的定位是,语法检查 + 代码质量检查工具。
和大多数软件的安装方法一样,也是这三种方法:
# apt 安装 shellcheck sudo apt install shellcheck shellcheck --version #version 0.3.8
wget https://github.com/koalaman/shellcheck/releases/download/v0.8.0/shellcheck-v0.8.0.linux.x86_64.tar.xz tar Jxvf shellcheck-v0.8.0.linux.x86_64.tar.xz cd shellcheck-v0.8.0 ./shellcheck --version #version 0.8.0
没必要源码安装,二进制安装快,并且版本最新,建议用二进制方法安装。
(图片建议点开放大查看)
通过 shellcheck,我发现 line 22 这个报错,和 "}" 有关,但不知道哪个 "}",先不管他。
然后 line1、line2、line3 的 SC1083 报错是一样的,说我可能少了个";" 号。
确实,这里是需要分号的,用 vim 可以证明。
这里可以看出,echoError 后面的 "{" 是天蓝色的,而最后的 "}" 居然是红色的,也就是 vim 不认为他们是一组括号的关系。这个 vim 的语法高亮是可以看出来的,但 notepad++ 的语法高亮功能看不出!
Notepad++ 截图:
添加 ";" 号修正后,括号颜色正常了。
我们继续调试,有趣的是,line1、line2、line3 冒出来新的报错。
报错的编号是SC2145,有红色的简单的文字说明,而最底下有个链接,可以去官网上看详细的原因说明。
其实我这里用 "或者*" 效果是一样的。
但 shellcheck 误以为我用了数组,如下图:
行吧,既然等价,我就按他的提示修改为 "$*" 吧。
修改后再执行 shelllcheck 如图:
我依然优先处理红字部分,这些都是严重问题,是脚本认为你脚本有问题的部分,黄色是警告,是脚本怀疑你有错的部分,而绿色是脚本提醒你用法不太规范提醒你可能会踩坑。
这里的红色部分发生在 line 1 的头部,也就是 line 1 前的上一行,文字也提醒我了,要添加 shebang,也就是 "#!/bin/bash"
添加完 shebang 后,我们开始处理黄色的部分。
黄色的部分提醒我,database_mount 变量没有使用到,提醒我 "database_mount" == "/database" 和 "filesystem_type" == "xfs" 两个静态值做比较,是不是其实搞错了,应该是有一个是变量?
确实是这样的,于是我修改了这两行代码
if [[ "database_mount" == "/database" ]];then 改为 if [[ "${database_mount}" == "/database" ]];then if [[ "filesystem_type" == "xfs" ]];then 改为 if [[ "${filesystem_type}" == "xfs" ]];then
这个提醒非常之好,shell 脚本绝对不会提醒你,因为语法没有错误,shell 是脚本测试时才能发现有逻辑错误,我们在测试之前就提前发现问题了,非常棒!
最后,我只剩下这个问题要修改了。shellcheck 的提示是:
Note that A && B || C is not if-then-else. C may run when A is true.
这个很好理解,他是建议我把 A && B || C 的语法修改为 if-then-else 形式,因为他们不是等价的。
我举个例子:
ls && echo ok || echo notok
我这句命令的意思是,执行 ls 成功的话,就会执行 echo ok,在屏幕打印 ok,否则 echo notok,在屏幕打印 notok
一般情况他是运作良好的。但假设,我这里代码(黄底字)出错了,则会有意料之外的输出结果
ls && echook || echo notok
这里,我 echo 和 ok 之间忘记添加空格了,导致这个逻辑段执行失败。
这样的话,虽然执行 ls 成功了,他也能执行echook逻辑,但由于echook 逻辑有问题,最后他还走了第二个逻辑,我们称为"否则"的逻辑,输出了 not ok,这不是我们要的,因为 ls 实际上是执行成功的。
如果我们改为 if-then-else 形式,则没有这个问题。
走了 echook 逻辑,虽然这个逻辑有问题,但这就结束了。虽然没有成功 echo ok,但至少不会走"否则"那个逻辑。
lsblk |awk '{print $6 $7}' |grep 'lvm/database$' >/dev/null \ && echoSuccess "/database 采用 lvm 逻辑卷管理,这是建议的,方便扩容" \ || echoWarning "/database 没有采用 lvm 逻辑卷管理,不方便扩容,请确认"
由于我能确保我的第一个逻辑(黄底字)语法正确,并且我的否则逻辑只是输出告警文本,而不是执行一些变更性的命令,影响不大,我不打算修改调整了。但我确实从 shellcheck 学会和记住了这个写 shell 时要注意的坑。
Enjoy Shell!