首页 Shell 脚本入门
文章
取消

Shell 脚本入门

什么是 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 脚本的执行性,建议始终使用 /bin/bash。

最后说一下 zsh,zsh 基本与 bash 兼容,且提供更强大、更灵活的功能,但是为什么不建议使用 zsh 作为 shell 脚本的解释器呢?

因为 zsh 并不是默认的 shell,很多 Linux 上都没有自带 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 之间是父子进程关系,子 shell 会继承父 shell 的环境变量(env 可查看),但是它们之间是不会互相影响的。

  • 当前进程中运行,这个就比较简单了,相当于我把一条一条的命令保存到了文件中,然后执行source来载入它,让 shell 来运行,这个和手打来执行命令完全没区别,比较符合”批处理”的概念。正因如此,脚本中的命令可以改变当前 shell 的环境变量、普通变量;任何语句带来的影响都会在当前 shell 中体现出来。

#!/bin/env bash#!/bin/bash区别

  • #!/bin/env bash:从$PATH环境变量中查找 bash 命令的位置,并启动它;
  • #!/bin/bash,直接根据绝对路径启动 bash 解释器(不存在则报错,然后退出)。

理论上,前者移植性更好,因为有些系统的 bash 可能不在 /bin 目录(但实际上不必太过担心)。

shell 注释

#号开头的行就是注释,它会被解释器忽略。

shell 不支持多行注释,只能在每行前面添加#

如果需要临时注释一大段代码,过一会又要取消注释,怎么办呢?

我们可以利用 function 函数,将这段代码放在函数中,要用的时候调用就行了。

本文由作者按照 CC BY 4.0 进行授权

Xshell monokai 主题

Shell 变量