什么是 shell 脚本
shell-script 可以理解为 Windows 下的 bat 批处理文件,它们的作用是类似的。
shell-script 是通过 shell 解释器来运行的,称为 解释型语言,即没有 编译 环节。
shell 解释器有哪些
sh
(Bourne Shell):UNIX 最初使用的 shell,而且在每种 UNIX 上都可以使用。Bourne Shell 在 shell 编程方面相当优秀,但在处理与用户的交互方面做得不如其他几种 shell。bash
(Bourne Again Shell):Linux 默认 shell,它是 Bourne Shell 的扩展。Bourne Again Shell 与 Bourne Shell 完全兼容,并且在 Bourne Shell 的基础上增加了很多特性。bash 可以提供命令补全,命令编辑和命令历史等功能,bash 包含了 csh、ksh 的很多优点,以及友好的交互界面。zsh
(Z Shell):是一种 Unix Shell,它可以用作为交互式的登录 shell,也是一种强大的 shell 脚本命令解释器。Zsh 可以认为是一种 Bourne shell 的扩展,带有数量庞大的改进(因此配置比较复杂),包括一些 bash、ksh、tcsh 的功能。
使用哪种 shell 解释器
先给出结论:
- 编写 shell 脚本,建议使用 bash
- 使用 shell 交互环境,建议使用 bash 或 zsh(推荐)
在 Linux 中,/bin/sh
通常是一个指向 /bin/bash
的软链接文件,所以在脚本中使用 sh 或 bash 都是调用的同一个程序 /bin/bash。
那是不是就可以认为它们完全一样呢?不是,如果使用 /bin/sh,那么 bash 会以 sh 兼容的方式运行(不支持 bash 的特性,如高级重定向)。
例子,我想比较本地的 /usr/local/ss-tproxy 与 github 上的 ss-tproxy 的区别,但又不想下载 github 上的文件,就可以使用 bash 的 <(command)
重定向,将 command 的输出作为一个虚拟的文件参数传递给 diff:
1
2
3
4
5
6
7
8
9
10
11
$ diff /usr/local/bin/ss-tproxy <(curl https://raw.github.com/zfl9/ss-tproxy/v2-master/ss-tproxy)
--- /usr/local/bin/ss-tproxy 2018-07-13 09:15:01.848014646 +0800
+++ /proc/self/fd/11 2018-07-13 09:15:40.470761996 +0800
@@ -1,7 +1,5 @@
#!/bin/bash
-export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
-
main_cfg='/etc/tproxy/ss-tproxy.conf'
if [ -f $main_cfg ]; then
source $main_cfg
但如果使用 /bin/sh 来执行上面的命令,就会提示语法错误。为了提高 shell 脚本的执行性,以及充分利用 bash 的特性,建议始终使用 /bin/bash。
然后说一下 zsh,zsh 基本与 sh/bash 兼容(但某些情况下,具有不同的语法),zsh 提供了比 bash 更多、更强大、更灵活的功能和特性,那为什么不建议使用 zsh 作为 shell 脚本的解释器?
因为 zsh 并不是 Linux 的默认 shell,很多发行版都没有自带 zsh,必须自己安装。为了可移植性,不建议使用 zsh 作为脚本解释器,不过作为 shell 交互环境还是非常不错的,尤其是 oh-my-zsh,不要太爽。
hello world 程序
编写和运行 shell 脚本非常简单,只需要一个文本编辑器(推荐 vim)和一个 shell 解释器(推荐 bash)。
hello.sh,扩展名建议用.sh
:
1
2
#!/bin/bash
echo "Hello World !"
添加可执行权限,然后运行脚本:
1
2
3
4
$ chmod +x hello.sh
$ ./hello.sh
Hello, World !
#!/bin/bash
是告诉系统,启动什么解释器来运行该脚本,这是一个约定标记,必须位于文件首行!
chmod +x hello.sh
则是设置文件的可执行权限,否则在运行./hello.sh
的时候会提示没有执行权限。
当然,也可以显式的指定解释器来运行脚本,这样的话就不需要执行权限,也不需要首行的约定标记,如:bash hello.sh
。
无论是./hello.sh
还是bash hello.sh
,其实都是启动了一个全新的 shell 进程来解释运行脚本语句。
也可以直接让当前 shell 读取脚本内容,并解释执行,使用source
或.
即可,如:source hello.sh
。
那么这两种方式有什么区别呢?
子进程中运行,当前 shell 执行 fork() 系统调用,创建一个新(子)进程,在子进程中执行 exec() 系统调用,载入解释器(/bin/bash),解释运行给定脚本。这两 shell 之间是父子进程关系,每个进程都有自己的环境变量副本(通常是复制父进程的)、命令行参数副本,argv 和 envp 存放在主线程的 stack 上。
当前进程中运行,相当于把一条一条的命令保存到了文件,然后执行
source
来载入它,在当前 shell 执行,和手敲来执行命令没区别。正因如此,脚本中的命令和语句所产生的影响,都会在当前 shell 体现出来,比如修改环境变量值,修改 shell 变量值。
#!/bin/env bash 和 #!/bin/bash 区别
#!/bin/env bash
:从$PATH
环境变量中查找 bash 命令的位置,并启动它;#!/bin/bash
,根据绝对路径启动 bash 解释器,如果路径不正确,则报错退出。
理论上,前者移植性更好,因为有些系统的 bash 可能不在 /bin 目录,但实际不必担心。
shell 注释
以#
号开头的行就是注释,它会被解释器忽略。
shell 不支持多行注释,只能在每行前面添加#
。
如果需要临时注释一大段代码,过一会又要取消注释,怎么办呢?
可以利用 function 函数,将这段代码放在函数中,要用的时候调用就行了。