写shell必备辅助神器ShellCheck


ShellCheck 的目标是

  • 指出并澄清导致 shell 给出神秘错误消息的典型初学者语法问题。
  • 指出并澄清导致 shell 行为异常和违反直觉的典型中级语义问题。
  • 指出可能导致高级用户的其他工作脚本在未来情况下失败的细微警告、极端情况和陷阱。

以上来自于官网github文档的谷歌机翻,地址 https://github.com/koalaman/shellcheck

我遇到了什么shell问题?

我直接贴一下当时用的代码吧。

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

注意了,必须 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登场

为了解决这个奇怪的语法报错问题,我只好下载了知名工具 ShellCheck,我给这个工具的定位是,语法检查 + 代码质量检查工具。

如何安装 ShellCheck?

和大多数软件的安装方法一样,也是这三种方法:

  • yum/rpm 法
  • 二进制法
  • 源码法 无论哪种方法,都很简单。

apt法:

# 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!

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/281221.html

(0)
上一篇 2022年8月20日
下一篇 2022年8月20日

相关推荐

发表回复

登录后才能评论