作为一枚Linux嵌入式程序猿,写shell脚本也是经常碰到的工作,在这个过程中或多或少踩过一些坑,也积累了一些经验,在此分享给大家,希望能对大家有点帮助。

1. 指定bash

我们知道在shell 脚本的第一行,都应该指定bash,那#!之后到底应该是什么呢?

这个问题估计不同的人的回答可能都不一样。我见过/usr/bin/env bash,也见过/bin/bash,还有/usr/bin/bash,还有/bin/sh,还有/usr/bin/env sh。我自己也用过其中过的几个,其实在很多情况下,以上几种写法效果都是相同的。但是,坑我们的往往就是少数情况~

如果恰好碰到系统的默认shell不是bash怎么办?比如某Linux发行版的某个版本,默认的 sh 就不是 bash。如果系统的bash不是在/usr/bin/bash怎么办?

还有关于bash和sh的一些小区别:

假设我们写一小段脚本,看看运行结果!

#!/bin/shsource mlryj.shecho "hello world!"

执行结果:

d4064aca0c3739e08b9ee835723772dc.png

然后我们将脚本改成这样

#!/bin/bashsource mlryj.shecho "hello world!"

执行结果:

402d71c253f53a0047bf7049a59e5901.png

从结果看,在#!/bin/sh的情况下,在mlryj.sh这个脚本不存在的情况下,source不成功,不会运行source后面的代码。

而在#!/bin/bash的情况下,虽然source不成功,但是还是运行了source后面的echo语句。

为什么会这样呢?

接下来我们看一看/bin/sh是个什么东西。

876da1e29dabe6ac080aec308835fa64.png

从上面可以看到,sh只是bash的一个软链接,在一般的linux系统当中,sh调用执行脚本相当于打开了bash的posix模式,也就是说 /bin/sh 相当于 /bin/bash --posix。posix的特定规范之一是,当某行代码出错时,便不继续往下解释。

把脚本改成如下图所示的格式,我们得到运行结果和#!/bin/sh是一样的。

#!/bin/bash --posixsource mlryj.shecho "hello world!"

推荐大家使用 /usr/bin/env bash 和 /bin/bash。前者通过env添加一个中间层,让env在PATH中搜索bash;后者毕竟是有官方背书的,约定俗成的bash位置。但是当脚本出现了错误怎么办呢?请看Tip2。

2. set -e 和 set -x

好了,关于指定bash已经完成了。接下来该开始写shell脚本第二行、第三行。

小编建议:在你开始构思并写下具体的代码逻辑之前,先插入一行"set -e"和一行"set -x"。

set -x会在执行每一行shell脚本时,把执行的内容输出来。它可以让你看到当前执行的情况,里面涉及的变量也会被替换成实际的值。

set -e会在执行出错时结束程序,就像其他语言中的“抛出异常”一样。

这两个组合在一起,可以在debug的时候替自己节省许多时间。出于防御性编程的考虑,有必要在写第一行具体的代码之前就插入它们。扪心自问,写代码的时候能够一次写对的次数有多少?大多数代码,在提交之前,通常都经历过反复调试修改的过程。与其在焦头烂额之际才引入这两个配置,不如一开始就给调试留下余地。在代码终于可以提交之后,再考虑是否保留它们也不迟。

#!/bin/shset -xset -esource mlryj.shecho "hello world!"

运行结果如下:

d740cfe9fdb464ee03b5e46cfab31767.png

3. shellcheck

加了set -x和set -e后,现在我已经有了shell开始的三行代码,但是具体的业务逻辑一行都没写。是不是该开始写了?

且慢!工欲善其事,必先利其器。小编先给各位介绍一个shell脚本编写神器:shellcheck

很惭愧,虽然这几年写了一些shell脚本,但是真正写起来的时候还是有很多语法记不清楚。这时候就要依仗shellcheck了。

shellcheck除了可以提醒语法问题以外,还能检查出shell脚本编写常见的错误代码。相信我,使用shellcheck会给我们的shell编写能力带来了巨大的飞跃的。

安装方法如下图:

be23a1e79579cb0aa1887cf8227d64de.png

安装完成后可以通过shellcheck -V查看当前安装成功的版本号。如下图:

0f67dc829ce2822cb5baef5c5adc2364.png

虽然我们技能不如别人,但是我们可以升级装备,在装备上赶上并超过对方啊!有了shellcheck加持就好比真三中诸葛亮带上了飞鞋,郭嘉买了孙子兵法,哈哈哈~~

4. 注意local

首先我们来看一下下面两段代码及运行结果,代码段1:

#!/bin/bashset -xset -efunction wgytest(){ a=$1 echo a}wgytest shellecho "hello world!"echo $a

运行结果如下图所示:

1940beda28afbd7f43c88a99057644c1.png

代码段2:

#!/bin/bashset -xset -efunction wgytest(){ local a=$1 echo a}wgytest shellecho "hello world!"echo $a

运行结果如下图所示:

b64d74bf565d8d49c90c165352d8e5c6.png

从上面的代码可以看出,我们只是将函数wgytest中的变量a加了local限定词,运行结果可以看出,第二段代码最后的echo并没有输出变量a的内容。

这是因为在bash,如果不加local限定词,变量默认都是全局的。在顶级作用域里,是否是全局变量并不重要。但是在函数里面,声明一个全局变量可能会污染到其他作用域,尤其在你根本没有注意到这一点的情况下。当我们开始把重复的逻辑提炼成函数,这时就有可能会掉到bash的这一个坑里。所以,对于在函数内声明的变量,小编的建议是记得加上local限定词。

5. 写在最后

以上几条都是具体的建议,这条来点虚的。

虽然使用shell可以方便快捷地实现各种复杂的功能,但也还是不得不依靠grep、sed、awk等各种工具把它们粘合在一起。实际上由于缺乏完善的数据结构以及一致的API,shell脚本在处理复杂的逻辑上还是显得力不从心。如果你的任务包含较为复杂的逻辑,而且数据结构复杂,那么建议使用python之类的语言编写脚本。

程序猿无论写什么代码,都要三思而行,切忌粗心大意。毕竟许多时候,我们的一个粗心就会给整个系统带来一个悲剧,其实复杂的脚本也是发端于几行小小的命令。一开始写脚本的人,也许以为它只是一次性任务。代码里难免对一些外部条件有些假定,在当时也许是正常的,但是随着外部环境的变化,这些就成了隐藏的雷。这就需要在编写的时候辨清哪些是会变的依赖、哪些是脚本正常运行所不可或缺的。同时要有防御性编程的意识,给自己的代码一道护城河。

Logo

更多推荐