Linux探索之旅 | 第五部分第二课:一入Shell深似海,酷炫外壳惹人爱
上一课是 Linux探索之旅 | 第五部分第一课:Vim岂是池中物,宝剑锋从磨砺出 。 这一课我们可以进入第五部分的重心了:Shell 编程
1. 前言
什么是 Shell 呢?
首先,shell 是英语「壳,外壳」的意思。你可以把它想象成嵌入在 Linux 这样的操作系统中的一个「微型编程语言」。
因此这一课的标题中的「酷炫外壳」的名字的灵感来自于 IT 界大牛陈皓的博客「酷壳」(Cool Shell):http://coolshell.cn/ 。
Shell 不像 C语言,C++,Java 等编程语言那么完整,但是 Shell 这门语言可以帮我们完成很多自动化任务,例如:保存数据,监测系统的负载,等等。
我们当然也可以写一个 C 语言的程序来完成以上提到的任务。但是 Shell 相比 C 语言的优势在于它是完全嵌入在 Linux 中的:不需要安装,不需要编译,而且我们不需要学习太多新的东西。
实际上,我们到目前为止在 Linux 中用的那些命令,之后我们也可以用在 Shell 语言中。例如:
cd
grep
等等。
既然我们第五部分要聊很多 Shell 的事,那么 Shell 到底是什么呢?
我们首先要回答这个问题,然后来写我们的第一个 Shell 脚本。接下来的几课我们会深入 Shell 编程的学习。
脚本(Script)是批处理文件的延伸,是一种纯文本保存的程序,一般来说的计算机脚本程序是确定的一系列控制计算机进行运算操作动作的组合,在其中可以实现一定的逻辑分支等。
2. Shell是什么?
从这个系列课程的一开始,我们就把 Linux 中的两个不同的环境分开来看:
终端命令行环境
图形界面环境
在大多数的时候,我们使用的是图形界面环境,因为更加直观。不同的图形界面环境的差异总是那么容易辨认,例如菜单项不一样,图标不一样,配色不一样,等等。
然而,在终端命令行环境中我们可以实现很多在图形界面环境中不能完成的复杂任务。
我们之前的课程中说到,Linux 有不少图形界面环境:Unity,KDE,GNOME,XFCE 等等,但是终端命令行环境「貌似」长得一样,只有一种。
其实这说法不准确。终端命令行环境也有好多种,对应的就是不同的 Shell。
不同终端命令行之间的区别不像图形界面那么明显,因为终端命令行一般都是黑底白字。但是根据 Shell 不同,命令行所能提供功能也不同。
因此,我们可以把不同的终端命令行环境称为不同的 Shell 咯?
是这样的。
以下列出几种主流的 Shell:
sh : Bourne Shell 的缩写。可以说是目前所有 Shell 的祖先。
bash : Bourne Again Shell 的缩写, 可以看到比 Bourne Shell 多了一个 again,again 在英语中是「又,再,此外」的意思,因此说明 bash 是 sh 的一个进阶版本,比 sh 更优。bash 是目前大多数 Linux 发行版和苹果的 Mac OS X 操作系统的默认 Shell。
ksh : Korn Shell 的缩写。一般在收费的 Unix 版本上比较多见,但也有免费版本的。
csh : C Shell 的缩写。 此 Shell 的语法有点类似 C 语言。
tcsh : Tenex C Shell 的缩写。csh 的优化版本。
zsh : Z Shell 的缩写。比较新近的一个 Shell,集 bash,ksh 和 tcsh 各家之大成。我在公司之前用的是 bash,现在也换成 zsh 了(Github 上有一个 zsh 的配置叫作 oh-my-zsh ( https://github.com/robbyrussell/oh-my-zsh ),值得拥有)。
当然了,还有不少其他的 Shell,但我们列出了最主要的一些。
关于这些品类繁多的 Shell 我们需要知道些什么呢?
首先,sh(Bourne Shell)是所有这些 Shell 的祖先。这是最古老的 Shell,被安装在几乎所有发源于 Unix 的操作系统上。比之于它的后代们,这位「老者」显得有点「技艺不足」。
bash(Bourne Again Shell)是非常有名的 Shell,是大多数 Linux 发行版的默认 Shell,也是苹果的 Mac OS X 操作系统的默认 Shell。很有可能你目前在你的 Linux 中使用的 Shell 就是 bash。
我们可以用下图来展示各个 Shell 的演化关系:
既然 bash 已经是大多数 Linux 发行版的默认 Shell 了,那么「老古董」 sh还有什么用呢?
实际上,sh 始终比 bash 使用面更广。在本课程的第一课里,我们就介绍了 Linux 是有点模仿 Unix 而创建的。
几乎所有源自于 Unix 的操作系统(Linux 也算)都有 sh,但不是每一个都有 bash。比如 AIX 和 Solaris 这样的源自于 Unix 的收费操作系统就用着其他的 Shell。
Shell 可以做什么呢?
Shell 是管理命令行的程序。因此,其实是 Shell 这个程序在等待你输入那些命令。如下图:
可以看到一个光标一直在一闪一闪的,你就可以输入命令了。
Shell 还可以帮你做很多事情,例如:
记住你之前在终端里输入过的命令:用键盘上的向上键可以回退到你之前执行过的命令。
用组合键 Ctrl + R 在终端的历史记录中搜索执行过的命令:
Ctrl + R 快速输入历史记录中的命令
上图中,我们可以看到,在按下 Ctrl+R 这个组合键时,就会提示我们输入命令,如果我们输入的命令的起始的一些字符和历史记录中已经运行过的命令一致,那么会补全命令。
例如这里我们输入了 find . -name "." ,Shell就用历史记录中已经运行过的 find . -name "." -exec grep "kDiscStatusCalleeNotExist" {} -nHIr \; 这个命令来补全了。这样,很多很长的命令我们就不必再重新输入一遍了。
用 Tab 键自动补全我们要输入的命令:
Tab 键自动补全命令
上图中,我们输入 gi,然后按一下 Tab 键,Shell 就会提示我们可用的候选项。用 Tab 键也可以一点点自动补全文件的路径。
控制进程(把进程放到后台,暂停进程(用之前的课 【Linux探索之旅】第三部分第四课:后台运行及合并多个终端 中我们学过的 Ctrl + z 就可以做到),等等)。
重定向命令(用到 <,>,| ,等等符号。在我们之前的课 【Linux探索之旅】第三部分第二课:流、管道、重定向,三管齐下 中学过)。
定义别名:例如可以将很长的一串命令定义为很短的一个别名(alias)。例如 ll 可以被定义为 ls -al 的别名。
简而言之,Shell 提供了所有可以让你运行命令的基础功能。
还记得在之前的一课 【Linux探索之旅】第二部分第六课:Nano,初学者的文本编辑器 中,我们曾经修改过一个 .bashrc 的文件吗?
这个 .bashrc 其实就是 bash 这个 Shell 的配置文件。每个 Linux 用户都可以自定义自己的 .bashrc 来配置 bash。它可以指定 bash 的命令提示符样式,定义别名,等等。
以后我们经常会在 Linux 系统中碰到一些以 rc 结尾的文件,比如 .bashrc,.zshrc,.init.rc,等等。那么大家不禁要问:这里的 rc 是什么意思啊?
"rc" 是取自 "runcom", 来自麻省理工学院在 1965 年发展的 CTSS 系统。相关文献曾记载这一段话 : 「具有从档案中取出一系列命令来执行的功能」,这称为 "run commands" (run 是英语「运行」的意思,command 是英语「命令」的意思,因此 run commands 就是「运行命令」的意思),又称为 "runcom",而这种档案又称为一个 runcom (a runcom)。
一般以 rc 结尾的多为配置文件,里面包含了软件运行前会去读取并运行的那些初始化命令。
上一课我们提到 Vim 时,它的配置文件叫 .vimrc,也是类似的。
安装一个新的Shell
目前,你的 Linux 系统中大概只安装了 sh 和 bash 这两个 Shell,是安装 Ubuntu 时预安装的程序。
如果你想要安装另一个 Shell,比如 ksh,你可以这样安装:
一旦你安装好了 ksh,你还需要将你当前的 Shell(一般来说是 bash )切换成 ksh,才能生效。
为了切换 Shell,需要用到以下命令:
chsh 是 Change Shell 的缩写,是英语「 切换Shell 」的意思。
运行 chsh 命令
chsh 命令会提示你在冒号后面输入你要切换成的 Shell 的程序路径。你可以输入 /bin/ksh (如果要切换成 ksh),或 /bin/sh(如果要切换成 sh)或 /bin/bash(如果要切换成 bash),等等。可以看到我的 Ubuntu 中目前的 Shell 是 zsh( /bin/zsh )。
为什么切换 Shell 对于我们写 Shell 脚本至关重要呢?
因为你的 Shell 脚本需要依赖于某一个 Shell。简单来说,你在使用不同的 Shell(sh,bash,zsh,ksh,等等)时,语法其实是不一样的。
比如,我们可以写 sh 的脚本。我们可以保证 sh 的脚本基本能运行在大多数系统上,但是 sh 的语法却并不那么「亲民」。
那么,我们将基于哪种 Shell 来编写我们的 Shell 脚本呢?
在本套课程中,我们学习 bash 这种 Shell,因为:
在大部分 Linux 系统和苹果的 Mac OS X 系统中,bash 是默认的 Shell。
bash的脚本比sh更容易编写。
比起 ksh 和 zsh ,bash 更常用。虽然这两个比bash要高级一些。
3. 我们的第一个Shell脚本
介绍了这么多 Shell 的相关知识点,我们就来写一个 Shell 脚本程序咯。这个脚本程序会很简单,但是已足够使我们了解创建脚本和运行脚本的基本常识。这对于我们之后的课程是一个基础。
创建脚本文件
我们用Vim这个我们上一课中学习的文本编辑器来创建一个 Shell 脚本文件:
如果 test.sh 这个文件不存在,那么会被创建。
这里,我们给这个 Shell 脚本文件的后缀是 .sh。这已经成为一种约定俗成的命名惯例了 (sh 就是 shell 的缩写),其实 Shell 脚本文件和普通的文本文件并没有什么区别。我们给它加上 .sh 以强调这是一个 Shell 脚本文件。我们大可以给这个文件起名叫 test (不带 .sh 后缀)。
但这个课程中,我们还是沿用惯例,把它起名为 test.sh 吧。
指定脚本要使用的Shell
按下回车,上述命令被执行。Vim 就被打开了,在我们眼皮底下的是一个空文件。
在写一个 Shell 脚本时,第一要做的事就是指定要使用哪种 Shell 来 「解析/运行」 它。因为 sh,ksh,bash 等等 Shell 的语法不尽相同。
因为我们的课程中要使用 bash,因此我们在这个 Shell 脚本的第一行写上
上面这句代码中, /bin/bash 是 bash 程序在大多数 Linux 系统中的存放路径,而最前面的 #! 被称作 Sha-bang,或者 Shebang。
在计算机科学中,Shebang(也称为 Hashbang )是一个由井号和叹号构成的字符串行 #! ,其出现在文本文档的第一行的前两个字符。
在文档中存在 Shebang 的情况下,类 Unix 操作系统的进程载入器会分析 Shebang 后的内容,将这些内容作为解释器指令,并调用该指令,并将载有 Shebang 的文档路径作为该解释器的参数。
这一行( #!/bin/bash )其实并不是必不可少的,但是它可以保证此脚本会被我们指定的 Shell 执行。
如果你没有写这一行,那么此脚本文件会被用户当前的 Shell 所执行。这就可能产生问题:假如你的脚本是用 bash 的语法来写的,而运行这个脚本的用户的 Shell 是 ksh,那么这个脚本就应该不能正常运行了。
所以记得在写 Shell 脚本文件时,先把 Shebang 开头的第一行写好。
运行命令
在以 Shebang 开头的第一行之后,我们就可以正式编码了。
原则很简单:只需要写入你想要执行的命令。暂时和之前我们在命令行提示符里写的命令没差。
例如:
ls :用于列出目录中的文件。
cd :用于切换目录。
mkdir :用于创建目录。
grep : 用于查找字符串。
sort :用于排序字符串。
等等。
简而言之,你在之前的课程中所学的命令,现在都可以用了。
好了,我们就以一个非常简单的命令开始吧。
我们输入 ls 这个命令。是的,这个脚本文件的目的暂时就是列出目录中文件。
ls
这就是全部了,很简单吧?
注释
就跟其他编程语言的程序一样,我们也可以在 Shell 脚本文件中加入注释。注释是不会被执行的行,但是可以用于解释我们的脚本做了什么。
Shell 脚本的注释以 # (井号)开头。例如:
# 列出目录的文件
ls
4. 运行Shell脚本
我们刚才写了一个非常简单的 Shell 脚本文件,就几行代码。那么接下来,我们只需要运行它即可。
首先,我们保存刚才的文件并退出 Vim。
希望你还记得如何做,忘了的话请去复习 Linux探索之旅 | 第五部分第一课:Vim岂是池中物,宝剑锋从磨砺出 。
是的,只需要用 Esc 键从插入模式进入交互模式,然后按下冒号键进入命令模式,输入 wq 或者 x,回车即可。
或
我们就重新找回了我们的命令提示符了。
给脚本文件添加可执行的权限
如果我们在当前目录运行 ls -l 命令:
就可以看到刚刚创建的脚本文件了:
可以看到此文件的权限是
如果你还记得我们在 【Linux探索之旅】第二部分第五课:用户和权限,有权就任性 中所学习的关于权限的知识,那么你应该知道这个文件它没有可执行的权限(因为没有 x )。
因此,我们给它加上可执行权限:
再用 ls -l 命令来查看,发现已经有了可执行权限了:
运行脚本
我们输入以下命令来运行这个脚本文件:
回车之后,脚本文件被我们系统中安装的 bash 运行,显示结果如下:
test.sh 运行结果
这个脚本做了什么呢?
它仅仅是执行了 ls 这个基本的 Linux 命令而已。因为这个脚本文件位于我的家目录 (/home/exe),因此,它列出了我的家目录中的所有文件(截图并没有包含所有,因为很多)。
看上去,这个脚本还真是鸡肋。为什么我们不直接在命令行运行 ls 命令,而要大费周章写一个 Shell 脚本呢?
相信我,Shell 脚本肯定是有用的,因为它可以一次性自动化执行很多很多命令,而不需要你一个个输入了。暂时我们的脚本中没什么东西而已,之后的课程你将见识到 Shell 的强大。
我们可以在刚才的 test.sh 这个 Shell 脚本中再加入一些命令,使之能做更多事情。比如,我们添加 pwd 命令,让它打印出当前目录的路径。
pwd
ls
保存文件,并再次运行,可以看到结果:
这次先是调用 pwd 命令打印出了当前目录的路径 (/home/exe),接着才运行 ls 命令,打印出当前目录中的文件。
以调试模式运行
随着我们渐渐深入 Shell 编程,你也许会写出很长的 Shell 脚本,代码一多很可能就会有 Bug。
bug 是英语单词,本意是臭虫、缺陷、损坏、犯贫、窃听器、小虫等意思。现在人们将在电脑系统或程序中,隐藏着的一些未被发现的缺陷或问题统称为 bug(漏洞) 。
因此,我们需要学习如何调试一个脚本程序。用法如下:
我们直接调用 bash 这个 Shell 程序,并且给它一个参数 -x (表示以调试模式运行),后面再跟上要调试运行的脚本文件。
如此一来,Shell 就会把我们的脚本文件运行时的细节打印出来了,在出现错误时可以帮助我们排查问题所在。
调试模式运行
创建属于自己的命令
目前,我们的 Shell 脚本文件须要这样运行:
而且我们需要位于正确的目录中。
那么其他的一些程序(命令其实都是程序),比如 git,pwd,ls,等等,为什么可以直接从不论哪个目录执行(不需要在前面加上 ./ 这样的路径)呢?
秘密就在于这些程序存放的目录是在 PATH 这个环境变量中的。
PATH 是英语「小路,路; 路线,路程; 途径」的意思。PATH 是 Linux 的一个系统变量。这个变量包含了你系统里所有可以被直接执行的程序的路径。
如果我们在终端输入
我们就可以看到目前自己系统里的那些「特殊」的目录了。
例如我的情况:
每一个路径之间是用冒号( : )来分隔的。
可以看到,我的 PATH 路径中有不少路径是重复的,估计跟我从 bash 换到 zsh 有关。有些路径被重复添加了。
不过没事,路径重复并不太要紧。以后我再删除重复的好了。
因此,只要你把 test.sh 这个文件拷贝到上述路径列表的任意一个目录(例如 /usr/local/bin ,/usr/bin,等等)中,你就可以在随便什么目录中运行你的 Shell 脚本了。
试试吧!
5. 总结
原先我们以为终端命令行只有一种形式,其实有不少类型的终端 :这就对应了不同的 Shell(外壳程序)。Shell 可以管理命令提示符,还有多种功能,例如命令的历史记录(用 Ctrl+R 来查找),命令的自动补全,等等。
在 Ubuntu 中,默认的 Shell 是 bash,但是也可以安装其他的 Shell,例如 ksh,zsh,等等。
我们可以用 Shell 来自动化一系列命令。首先需要创建一个文件,包含要运行的命令的列表,称之为 Shell 脚本。这也称之为 Shell 编程。
根据使用的 Shell 种类不同,我们有不同的工具来处理 Shell 脚本。我们在本课中使用 bash 来演示,因此在 Shell 脚本文件的开头需要写上:#!/bin/bash
在 Shell 脚本文件里,一般来说只需要把我们要执行的命令一行一行地写入文件。
为了运行 Shell 脚本(也就是运行其中包含的那些命令),需要先给脚本文件添加可执行属性(chmod +x script.sh),然后这样运行: ./script.sh
6. 第五部分第三课预告
今天的课就到这里,一起加油吧!
下一课我们学习:变量在手,Shell不愁
推荐阅读