一、执行脚本的现象
为了测试一个小的功能,写了一个小脚本,类似的内容如下:
#!/bin/sh
echo "start api test ......"
for((i=1;i<=10;i++));
do
echo "cur id :" $i;
done
echo "end."
执行一下,"./testEx.sh",结果报一个错误"sh: 5: Syntax error: Bad for loop variable"。说实话对脚本的编写还是相对少很多的,这个简单的错误勾起了探索一下的想法 。于是把执行的方式改变了下,"bash testEx.sh",正常运行。然后把文件内容中的"#!/bin/sh"修改为"#!/bin/bash",即使使用"./testEx,sh",也可以正常执行。下面就分析一下原因,脚本大佬可以滑走了。
二、原因和解决
通过上述的执行,发现其实问题的产生就在于是使用sh还bash。那么系统是如何区别这两种脚本的呢?首先执行一个命令看看当前默认的脚本执行解释器:
#echo $0
bash
再执行一下命令,看看当前支持哪些脚本的解释器:
tests$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/usr/bin/sh
/bin/dash
/usr/bin/dash
也可能通过命令来查看具体的执行解释器位置:
tests$ ls /bin/*sh -l
-rwxr-xr-x 1 root root 27016 5月 31 2023 /bin/avahi-publish
-rwxr-xr-x 1 root root 1396520 1月 7 2022 /bin/bash
-rwsr-xr-x 1 root root 44808 11月 24 2022 /bin/chsh
-rwxr-xr-x 1 root root 6963 10月 13 2023 /bin/c_rehash
-rwxr-xr-x 1 root root 125688 3月 23 2022 /bin/dash
-rwxr-xr-x 1 root root 5188 3月 25 2022 /bin/gettext.sh
-rwxr-xr-x 1 root root 2181 2月 7 2022 /bin/gvmap.sh
-rwxr-xr-x 1 root root 4373 5月 24 2023 /bin/instmodsh
-rwxr-xr-x 1 root root 39460 6月 7 2023 /bin/nvidia-bug-report.sh
-rwxr-xr-x 1 root root 900 6月 7 2023 /bin/nvidia-sleep.sh
-rwxr-xr-x 1 root root 14648 2月 16 2023 /bin/pax11publish
lrwxrwxrwx 1 root root 4 5月 5 2023 /bin/rbash -> bash
lrwxrwxrwx 1 root root 21 5月 5 2023 /bin/rsh -> /etc/alternatives/rsh
lrwxrwxrwx 1 root root 4 5月 5 2023 /bin/sh -> dash #在本机为dash,而不是bash
-rwxr-xr-x 1 root root 846888 8月 24 2023 /bin/ssh
lrwxrwxrwx 1 root root 7 5月 5 2023 /bin/static-sh -> busybox
lrwxrwxrwx 1 root root 8 5月 5 2023 /bin/tclsh -> tclsh8.6
-rwxr-xr-x 1 root root 281 5月 8 2023 /bin/vmware-license-check.sh
-rwxr-xr-x 1 root root 288 5月 8 2023 /bin/vmware-license-enter.sh
-rwxr-xr-x 1 root root 14728 3月 25 2022 /bin/xrefresh
通过上述的命令可以发现,在系统中存在着多个脚本的解释器,默认的使用是bash。但随即又出来一个问题,不同写法和不同的执行方式,到底调用了哪种解释器?把这个弄清楚了,就可以解决问题了。仍然以最开初的例子为例,这里使用以下几种方式来编辑并执行:
1、使用bash testEx.sh执行:
start api test ......
cur id : 1
......
cur id : 10
end.
2、在脚本中指定#!/bin/bash,然后执行,结果同上。
3、默认执行,有可能需要增加执行权限,执行结果:
Syntax error: Bad for loop variable
4、脚本中不指定任何解释器,执行bash testEx.sh,正常执行并显示结果同1。
5、脚本中不指定任何解释器,默认执行,正常执行,结果同上。
上面的5种方式只有3这种情况会出异常,而在前面的命令中发现它调用的是sh->dash。明确使用bash的,正常执行,显示结果;未明确在脚本中指定的,使用系统默认的bash,所以结果均一致。
通过上述例子就可以明白 :如果使用bash执行脚本或者在脚本中指定bash脚本解释器(#!/bin/bash),则始终以bash脚本解释器来处理脚本;如果脚本 指定为"#!/bin/sh",则这个标准的解释器会调用其真正链接的解释器,在本文中它链接的是dash。使用默认执行则看系统默认的脚本解释器是如个即可。
三、扩展
"#!"体现在文件中就是一个2字节魔数,魔数开发者应该都清楚,就是代表文件类型的特殊标记,在此处即为可执行的脚本。在"#!"之后,一般是一个脚本解释器的路径,当然这个解释器可以是一个普通认知的解释器,如bash,sh,csh,dash等等,也可以是一个程序。这就看具体的应用方式了。比如下面的例子:
#!/bin/cat
echo "Used cat!"
这个脚本使用Cat命令来处理,直接执行脚本"./testCat.sh",执行结果为(如果使用bash testCat.sh看看结果是什么?):
#!/bin/cat
echo "Used cat!"
那么bash和sh在实际的应用中有什么不同呢?一个简单的说法是前者包括后者;或者说后者是前者的一个子集。从前面命令执行也可以看出,bash提供了对posix的支持。所以才可以将循环写成和C语言类似的方式。如果执行"/bin/bash --posix"则会发现它和"/bin/sh"执行的结果一致,这下就明白了吧,sh是bash的posix支持版。
四、总结
脚本用处真得很大,尤其在一些监控、测试的情况下,一个简单的小脚本就可以达到控制程序的目的。特别是粘连一些相关工具如Jenkins和部署时,可以极大的减少代码的工作量并迅速构建迅速部署。当然脚本的缺点也不少,最显著的就是不好调试,对一些复杂的函数和变量往往需要死记硬背。
建议大家都要好好学习一下脚本相关的知识,这样在服务端开发中,会大提高工作效率。原来总以为脚本是运维的事儿,后来才发现大谬。好好学习,犹未晚尔!