一个bash shell脚本的第一行是什么_编写Linux Shell脚本的一些经验分享
作为一枚Linux嵌入式程序猿,写shell脚本也是经常碰到的工作,在这个过程中或多或少踩过一些坑,也积累了一些经验,在此分享给大家,希望能对大家有点帮助。1. 指定bash我们知道在shell 脚本的第一行,都应该指定bash,那#!之后到底应该是什么呢?这个问题估计不同的人的回答可能都不一样。我见过/usr/bin/env bash,也见过/bin/bash,还有/usr/bin/bash,还
作为一枚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!"
执行结果:
然后我们将脚本改成这样
#!/bin/bashsource mlryj.shecho "hello world!"
执行结果:
从结果看,在#!/bin/sh的情况下,在mlryj.sh这个脚本不存在的情况下,source不成功,不会运行source后面的代码。
而在#!/bin/bash的情况下,虽然source不成功,但是还是运行了source后面的echo语句。
为什么会这样呢?
接下来我们看一看/bin/sh是个什么东西。
从上面可以看到,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!"
运行结果如下:
3. shellcheck
加了set -x和set -e后,现在我已经有了shell开始的三行代码,但是具体的业务逻辑一行都没写。是不是该开始写了?
且慢!工欲善其事,必先利其器。小编先给各位介绍一个shell脚本编写神器:shellcheck
很惭愧,虽然这几年写了一些shell脚本,但是真正写起来的时候还是有很多语法记不清楚。这时候就要依仗shellcheck了。
shellcheck除了可以提醒语法问题以外,还能检查出shell脚本编写常见的错误代码。相信我,使用shellcheck会给我们的shell编写能力带来了巨大的飞跃的。
安装方法如下图:
安装完成后可以通过shellcheck -V查看当前安装成功的版本号。如下图:
虽然我们技能不如别人,但是我们可以升级装备,在装备上赶上并超过对方啊!有了shellcheck加持就好比真三中诸葛亮带上了飞鞋,郭嘉买了孙子兵法,哈哈哈~~
4. 注意local
首先我们来看一下下面两段代码及运行结果,代码段1:
#!/bin/bashset -xset -efunction wgytest(){ a=$1 echo a}wgytest shellecho "hello world!"echo $a
运行结果如下图所示:
代码段2:
#!/bin/bashset -xset -efunction wgytest(){ local a=$1 echo a}wgytest shellecho "hello world!"echo $a
运行结果如下图所示:
从上面的代码可以看出,我们只是将函数wgytest中的变量a加了local限定词,运行结果可以看出,第二段代码最后的echo并没有输出变量a的内容。
这是因为在bash,如果不加local限定词,变量默认都是全局的。在顶级作用域里,是否是全局变量并不重要。但是在函数里面,声明一个全局变量可能会污染到其他作用域,尤其在你根本没有注意到这一点的情况下。当我们开始把重复的逻辑提炼成函数,这时就有可能会掉到bash的这一个坑里。所以,对于在函数内声明的变量,小编的建议是记得加上local限定词。
5. 写在最后
以上几条都是具体的建议,这条来点虚的。
虽然使用shell可以方便快捷地实现各种复杂的功能,但也还是不得不依靠grep、sed、awk等各种工具把它们粘合在一起。实际上由于缺乏完善的数据结构以及一致的API,shell脚本在处理复杂的逻辑上还是显得力不从心。如果你的任务包含较为复杂的逻辑,而且数据结构复杂,那么建议使用python之类的语言编写脚本。
程序猿无论写什么代码,都要三思而行,切忌粗心大意。毕竟许多时候,我们的一个粗心就会给整个系统带来一个悲剧,其实复杂的脚本也是发端于几行小小的命令。一开始写脚本的人,也许以为它只是一次性任务。代码里难免对一些外部条件有些假定,在当时也许是正常的,但是随着外部环境的变化,这些就成了隐藏的雷。这就需要在编写的时候辨清哪些是会变的依赖、哪些是脚本正常运行所不可或缺的。同时要有防御性编程的意识,给自己的代码一道护城河。
更多推荐
所有评论(0)