Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive),Shell还有一种执行命令的方式称为批处理(Batch),用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。
由于历史原因,UNIX系统上有很多种Shell:
sh(Bourne Shell):由SteveBourne开发,各种UNIX系统都配有sh。
csh(C Shell):由Bill Joy开发,随BSD UNIX发布,它的流程控制语句很像C语言,支持很多BourneShell所不支持的功能:作业控制,命令历史,命令行编辑。
ksh(Korn Shell):由David Korn开发,向后兼容sh的功能,并且添加了csh引入的新功能,是目前很多UNIX系统标准配置的Shell,在这些系统上/bin/sh往往是指向/bin/ksh的符号链接。
tcsh(TENEX C Shell):是csh的增强版本,引入了命令补全等功能,在FreeBSD、Mac OS X等系统上替代了csh。
bash(Bourne Again Shell):由GNU开发的Shell,主要目标是与POSIX标准保持一致,同时兼顾对sh的兼容,bash从csh和ksh借鉴了很多功能,是各种Linux发行版标准配置的Shell,在Linux系统上/bin/sh往往是指向/bin/bash的符号链接[36]。虽然如此,bash和sh还是有很多不同的,一方面,bash扩展了一些命令和参数,另一方面,bash并不完全和sh兼容,有些行为并不一致,所以bash需要模拟sh的行为:当我们通过sh这个程序名启动bash时,bash可以假装自己是sh,不认扩展的命令,并且行为与sh保持一致。
用户在命令行输入命令后,一般情况下Shell会fork并exec该命令,但是Shell的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程。以前学过的cd、alias、umask、exit等命令即是内建命令,凡是用which命令查不到程序文件所在位置的命令都是内建命令,内建命令没有单独的man手册,要在man手册中查看内建命令,应该
$ man bash-builtins
内建命令虽然不创建新的进程,但也会有Exit Status,通常也用0表示成功非零表示失败,虽然内建命令不创建新的进程,但执行结束后也会有一个状态码,也可以用特殊变量$?读出。
首先编写一个简单的脚本,保存为script.sh:
#! /bin/sh cd ..
ls
Shell脚本中用#表示注释,相当于C语言的//注释。但如果#位于第一行开头,并且是#!(称为Shebang)则例外,它表示该脚本使用后面指定的解释器/bin/sh解释执行。如果把这个脚本文件加上可执行权限然后执行:
$ chmod +x script.sh
$ ./script.sh
Shell会fork一个子进程并调用exec执行./script.sh这个程序,exec系统调用应该把子进程的代码段替换成./script.sh程序的代码段,并从它的_start开始执行。然而script.sh是个文本文件,根本没有代码段和_start函数,怎么办呢?其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。因此,执行上述脚本相当于执行程序
$ /bin/sh ./script.sh
以这种方式执行不需要script.sh文件具有可执行权限。再举个例子,比如某个sed脚本的文件名是script,它的开头是
#! /bin/sed -f
执行./script相当于执行程序
$ /bin/sed -f ./script.sh
以上介绍了两种执行Shell脚本的方法:
$ ./script.sh
$ sh ./script.sh
这两种方法本质上是一样的,执行上述脚本的步骤为:
1、交互Shell(bash)fork/exec一个子Shell(sh)用于执行脚本,父进程bash等待子进程sh终止。
2、sh读取脚本中的cd ..命令,调用相应的函数执行内建命令,改变当前工作目录为上一级目录。
3、sh读取脚本中的ls命令,fork/exec这个程序,列出当前工作目录下的文件,sh等待ls终止。
4、ls终止后,sh继续执行,读到脚本文件末尾,sh终止。
5、sh终止后,bash继续执行,打印提示符等待用户输入。
如果将命令行下输入的命令用()括号括起来,那么也会fork出一个子Shell执行小括号中的命令,一行中可以输入由分号;隔开的多个命令,比如:
$ (cd ..;ls -l)
和上面两种方法执行Shell脚本的效果是相同的,cd ..命令改变的是子Shell的PWD,而不会影响到交互式Shell。然而命令
$ cd ..;ls –l
则有不同的效果,cd ..命令是直接在交互式Shell下执行的,改变交互式Shell的PWD,然而这种方式相当于这样执行Shell脚本:
$ source ./script.sh
或者
$ . ./script.sh
source或者.命令是Shell的内建命令,这种方式也不会创建子Shell,而是直接在交互式Shell下逐行执行脚本中的命令。