/ 编程

好好写 Shell 脚本

shell 种类众多,并且语法各异,如果自己又不熟悉任何一种 shell 的话,就会经常感觉语法怪异,而且似乎不够严谨,甚至有时候要边搜边写,这就使得一些脚本成为了一些勉强可用的语句的拼凑,几乎不可维护。即便是一些所谓的“技术大牛”,各种高大上的词都能吹得天花乱坠的,也写不了像样的脚本,这是个蛮尴尬的事情,固然是术业有专攻,但是写个 Linux Shell 脚本,应该算是个基础(其实有可能他们连链表怎么实现都不知道)。这里主要是说最为通用的 bash,以下是几条 bash “代码规范”。

1. 及早退出

脚本的开头,#! 语句之后,加上这几行

set -o errexit
set -o pipefail

第一行语句的作用是,在脚本执行过程中,如果有错误,就退出脚本,不再继续下去。如果一条语句执行完,返回值不是 0,就是错误。

第二行语句的作用是,如果脚本中有一行命令是由一个或多个管道连起来的多个命令,如果其中有一条或者多条命令出现错误(返回非 0 值),这一整行命令返回的结果就是最后那条返回失败结果的命令的返回值。

如果预期一条语句可能会产生失败,就自己做好判断以及之后的处理,可以使用 if 语句或者 &&||。例如:

nc -z 8.8.8.8 53 && echo "OK" || echo "FAIL"

如果能连上 8.8.8.8 的 53 端口,则结果为真,输出 OK,如果连接不上,也就是最开始的语句结果为假,echo "OK" 就被“短路”了,最后输出了 FAIL。

2. 使用函数

在 bash 里这样定义和调用函数

func_name()
{
    echo "do something"
}

func_name

在函数中 $1, $2 等等就是第一个参数,第二个参数等。

  • func_name { ... } 跟上面的区别是函数名后直接是函数内容,这种定义函数的方法已经被废弃
  • 一个脚本要有一个 main 函数,直接写 main 进行调用,或者 main "$@" 把命令行参数传给它

使用函数的好处跟其他语言中使用函数的好处一样,但是很长时间以来,都不是很习惯在 shell 脚本里用函数,或许是最开始受“shell 脚本就是命令的堆积”这样一个指示的影响。

3. 更好地使用全局变量、局部变量

这跟使用函数一样是放之四海皆准的规范:尽量避免使用全局变量。除非是些全局用的常量,定义这种变量时,用 readonly 对变量进行修饰。在函数内部使用局部变量,用 local 修饰变量。

为什么要显式地去声明一个变量是局部变量?注意一个天大的误会,不像 C 语言,在一个函数里的变量就是局部变量,在 bash 里,函数里的变量如果没有用 local 修饰,那么它是一个全局变量!或许这是认为 bash 比较怪异的一个原因。

如果要在一个函数内定义一个只读的全局变量,使用 declare -r var_name 或者 local -r var_name,在函数内通过 declarelocal 声明的变量都是局部变量,-r 参数使变量只读。

declarelocal 作用非常类似,不过 local 只能在函数内使用,declare 在函数内外都可以使用。在函数内用 declare 声明的是局部变量,在函数外使用 declare 的变量自然是全局变量。

4. 其他细节

  • 使用 [[ ... ]] 而不是使用 [ ... ] 或者 test 进行判断
  • 使用 $( ... ) 代替丑恶的反引号 ```
  • 不要有光秃秃的 $ 符号,把变量都放进双引号,例如 "$var"
  • 大多数情况下都使用双引号,除非有强有力的原因
  • 简单的条件判断不需要用 if,用 &&|| 即可,就像开头 nc -z 8.8.8.8 53 && echo "OK" || echo "FAIL" 这个例子
  • then, do 等等,放到 if, for 等关键字的同一行