欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Shell 中的一些好的实践

程序员文章站 2022-07-12 12:43:11
...

1. 项目中使用统一的 Shebang

不知道大家见没见过下面两种 Shebang 的写法呢?大概率是见过的!而我也是在项目上见过这两种不同的写法后,才去研究了一下。

#!/usr/bin/env bash
#!/bin/bash

那么,以上两种 Shebang 的写法,到底哪种比较好呢?
仁者见仁智者见智!

  • 第一种方式从环境变量 PATH (利用 env 获取到的)中挨个目录地寻找,知道找到 bash
  • 第二种方式直接指定 bash 的位置

这两种方式各有优劣:

  • 第一种方式可以不必知道解释器的具体位置,可移植性好,但有安全隐患;
  • 第二种方式直接指定解释器的位置,安全性较好,但需要知道解释器的具体位置

关于这一点,可以参考我之前写的一篇博客:#!/bin/bash 和 #!/usr/bin/env bash 的区别
Note!我本人是比较喜欢第二种方式,直接指定解释器具体位置!????


2. 选择合适的 Options

Bash Options,怎么解释呢?在我的理解中,这就相当于是脚本的 option 一样,和命令的 option 是一样的,不同的 option 有不同的作用!
而比较正式的定义是这样说的:

Options are settings that change shell and/or script behavior.(Options是更改 Shell 和/或 脚本 行为的设置)

确实也很有道理哈!那么,废话不多说,我们来看看都有哪些 Options!
Shell 中的一些好的实践
可以看到,Options 还是非常多的,但是这么多的 Option,估计很难全部记住!
我平时比较常用的有 3 个,分别是:

  • -e,在脚本遇到第一个错误后终止脚本(以失败退出)
  • -u,在脚本中使用未定义的变量时,终止脚本(失败)
  • -x,回显脚本中的每一条命令

当然了,有用的 Options 还有很多,大家可以多研究研究,遇到有用的也给我推荐下哈????!
但,最重要的是,针对你的脚本,选择合适的 options!
如果你的脚本中没有用到任何一个变量,那么,你给脚本加一个 -u options,那就会显得很多余!

Note!上面的表格及一些细节性的东西在这里:➡️ Options


3. 脚本命名不带后缀

关于脚本到底要不要加后缀名这个问题呢,算是有比较大的争议,有人认为加上好,有人认为不加好,各有各的理由!
但是我个人是比较倾向于不加后缀的!(不同意勿喷,仅代表个人观点)

Note!关于带后缀或不带后缀各自的优缺点,可以参考这个提问的回答:Unix Shell脚本的文件扩展名
相信看完所有的回答,即便还是不确定到底要不要带后缀,但最起码两个观点各自的优缺点都会弄明白。

个人倾向于脚本不带后缀的理由是什么呢?主要有三点:

  1. 类 Unix 系统识别文件类型不是利用文件后缀,只有 Windows 系统是利用文件后缀识别文件类型的

  2. 脚本一般都是些相对较为简单的操作,使用者并不关心它到底是用哪种脚本语言,或者哪种 shell 去写的,使用者关心的只有一个——脚本的行为

  3. 虽然带后缀名的脚本会容易区别(或者说识别),但是,我们假设一种情况:

    脚本 A 带有后缀 .bash,是采用 bash 写的,同时脚本 B 中调用了脚本 A,调用方式为 bash A.bash
    某一天,脚本 A 的需求变了,变得更复杂了,需要用 python 重写脚本 A,这时,脚本 A 的后缀变为了 .py
    于是,脚本 B 中调用脚本 A 的地方也得改变为 python A.py
    但是,这真的是理想的吗?要知道,脚本 B 根本不关心脚本 A 怎么实现,脚本 B 关心的只有一点,那就是脚本 A 正确完成了需求,但是,你脚本 A 的重写居然还要我脚本 B 也随之改变,开什么玩笑,老子不用你了!

    所以,我认为,脚本带后缀会使得脚本强依赖于命令解释器

Note!仁者见仁智者见智,不喜勿喷


4. 新建脚本立即加执行权限

这条其实严格来说不算是知识点方面的,只能算是一个提醒吧!
因为我本人干过很多次写好脚本后直接 push 上去的经历????,结果,显而易见,Pipeline 直接红了????!
所以呢,为了以防忘掉,新建脚本后的第一时间就给它加上执行权限


5. 脚本中有风险的操作要有交互模式进行确认

假设我有个需求,需要删掉一些生产环境中没人使用的 API TOKEN,于是我准备用 shell 脚本来实现!
经过一番努力,我确实写好了脚本,但是,运行之后,出 Incident 了????????????????!

把人家线上还在用的 API TOKEN 给删掉了,这事大了!

所以,如果我们在执行删除操作前可以有一个交互性的确认按钮,同时将需要删除的资源列出来进行确认,是不是会更安全一些呢?

Shell 中的一些好的实践


6. 命令回显一定要在提交 push 前去掉

开发过程中,为了更方便调试,我们往往会喜欢为 shell 脚本加上一个 -x 的 options,以便每执行一条命令都将这条命令回显出来,查看运行过程中到底执行的是不是我们期望的命令!

但是,假设一种情况:

A 脚本调用了一个命令 C,并给 C 命令传入了一个解密后的 token(调用命令 C 前解密的),本来这个 token 并没有被输出出来(毕竟敏感信息都是被禁止的,大家一般都会注意),脚本也没有什么隐患!
但是,由于疏忽,脚本 A 上待了一个 -x options,并且没有取消就直接 push 了

可以猜想一下会出现什么!
显而易见,解密后的明文 token 被输出到 CI/CD 工具的控制台日志中了!—— 妥妥的 Incident!????????

因此,-x 仅限于 debug 期间,push 前一定要记得去掉 -x


7. 命令太长要换行

Shell 中的一些好的实践
可以很明显的注意到,我的第一条命令甚至都没有截全????????
相信,不用多说了吧!


8. 脚本中的命令使用长格式 option

Shell 中的一些好的实践
这条应该也很简单吧,短格式的 option 会让人一头雾水,不熟悉命令时根本不知道什么意思!
如果写成长格式 option 的话,即便没使用过对应的命令,但最起码从完整的 option 中也能大概猜出命令的意图!


9. 不要吝啬输出

假设:

一个文件有 500 行,我需要循环读取该文件,并对每一行内容进行处理,但是,我的脚本没有任何输出!

会出现什么情况呢?

运行脚本后,只能看见终端被阻塞了,但是,什么输出也没有,于是,5 分钟后,在脚本处理到文件第 499 行的时候,我按了个 Control + C????????

#!/bin/bash -e

cd "$(dirname "$0")"/..

rm -f data/less-code-tmp.txt

while read -r line; do
  # echo "Processing ${line}" # 尝试该行有注释和没注释两种情况下的状态,data/less-code.txt 无所谓了,自己随便搞一个稍大点的文件就行
  sleep 1
  echo "${line//;/;}" >> data/less-code-tmp.txt
done < data/less-code.txt # 500 lines

如果脚本有友好的输出,会给用户一个信号:操作正在进行,只是需要稍等一会儿!

就好比,转圈圈比大白页更友好一样的道理!


10. 代码要简短、高效

Shell 中的一些好的实践
代码、命令,要简短,高效!

能一次删整个文件夹,你非得把所有的文件一个一个删掉,再退出去把空文件夹删掉?


11. 不要吝啬引号(千万不要觉得引号可有可无)

#!/bin/bash -eu

function print_triangle() {
  char=$1
  for i in {1..5}; do
  for ((j=0; j<5-i; j++))
  do
    echo -n " "
  done
  for ((j=0; j<i; j++))
  do
    echo -n ${char}
    echo -n ${char}
  done
  echo
done
}

print_triangle .
print_triangle *

猜猜这个脚本的执行结果是什么?
两个三角形,一个由 . 组成,一个由 * 组成?

那你可大错特错了!
结果是这样的:
Shell 中的一些好的实践
这就是没带引号导致的!
可以尝试加上引号后的结果!

所以,不要吝啬你的引号,多加个引号又不会少块肉!
该加引号的地方就加上,谁要是因为这个骂你,你就来找我,我为你做主!我说的!
Shell 中的一些好的实践


12. 巧用 heredocs

Shell 中的一些好的实践
漂亮,没别的,就单纯漂亮!
这个理由,还不够吗?


13. 当脚本逻辑过于复杂时,请放过 Shell,去用 node、ruby、python 吧

Shell 中的一些好的实践


14. 当你的业务必须用 Shell 来实现,那么,请加上测试(是的,Shell 也有测试框架)

虽说这听起来很令人难以置信,shell 居然还要写测试!????
但是,如果你的业务真的是用 shell 写的,那么,为什么不加测试呢?
要知道,shell 还是高级语言呢!不要瞧不起 shell!

而 shell 的测试框架就有:shUnit2
同时,交互模式的测试也可以用 expect 去实现!

总之,如果真的业务是 shell 写的,而且较为复杂,那么,请加上测试!如果你接受不了 shell 写测试,可以参考博客的上一条


15. 使用新写法,拒绝一成不变

Shell 中的一些好的实践
时代在进步,编程语言也在不断演进!如果你一直用旧的写法,拒绝改变,那么,你终将被时代所抛弃!

Note!这里只列出了几个新写法而已,新的写法还有很多很多,可以自行探究!


16. 安装 Shellcheck 插件

大部分的 IDE 都有 Shellcheck 插件,包括 VS Code、IDEA、Webstorm等。

Shellcheck 可以帮助你检查你的 shell 脚本是不是最佳的写法,有没有什么过时、out 的写法,会给你提示!

而我觉得Shellcheck 最赞的地方还不是这个,而是 Shellcheck 提供了一个非常全的 wiki 文档,里面不仅列出了错误写法与正确写法的示例,而且还指明了为什么这么写的原因!

例:↓
Shell 中的一些好的实践
完整的 Shellcheck 文档列表在这里:→ https://gist.github.com/nicerobot/53cee11ee0abbdc997661e65b348f375#file-_shellcheck-md


Note!当时写下这些实践的时候,也参考了一些资料:

同时,针对这些实践,我当时还搞了个 PPT,比这篇文章早一些,不是很全,有需要的可以去看一下:shell 中的一些好的实践