在过去几十年中所出现的UNIX和类UNIX操做系统家族已经成为现在最为流行、使用最普遍的操做系统之一,这都算不上什么秘密了。对于使用了多年UNIX的程序员而言,一切都瓜熟蒂落:UNIX系统为程序开发提供了既优雅又高效的环境。这正是Dennis Ritchie和Ken Thompson在20世纪60年代晚期在贝尔实验室开发UNIX时的初衷。程序员
在本书中,咱们使用的术语UNIX泛指基于UNIX的操做系统你们族,其中包括像Solaris这样真正的UNIX操做系统以及像Linux和Mac OS X这样的类UNIX操做系统。shell
UNIX系统最重要的特性之一就是各式各样的程序。超过200个基本命令会随着标准操做系统发行,Linux还对标准命令数量作了扩充,一般能达到700~1000个!这些命令(也称为工具)从统计文件行数、发送电子邮件到显示特定年份的日历,可谓无所不能。编程
不过UNIX真正的威力并不是来自数量庞大的命令,而在于你能够很是轻松、优雅地将这些命令组合在一块儿完成很是复杂的任务。数组
UNIX的标准用户界面是命令行,其实就是Shell,它的角色是做为用户和系统最底层之间(内核)的缓冲带。Shell就是一个程序,读入用户输入的命令,将其转换成系统更易于理解的形式。它还包括了一些核心编程构件,能够作出判断、执行循环以及为变量储值。安全
从AT&T发行版(源自Stephen Bourne在贝尔实验室编写的第一版)开始,标准Shell就是同UNIX系统捆绑在一块儿的。自那时起,IEEE根据Bourne Shell以及后续的一些其余Shell制订了标准。该标准目前的(本书写做之时)版本是Shell and Utilities volume of IEEE Std 1003.1-2001,也称为POSIX标准。本书余下的内容都离不开Shell。
在本章中,你将学习到什么是UNIX的Shell,Shell可以作什么,以及为何说它是每一个高级用户工具箱中不可或缺的一部分。20分钟轻松学会shell很容易,不过若是想要全面掌握还须要专业的书籍来深度学习。
本文摘自《UNIX/Linux/OS X中的Shell编程(第4版)》bash
UNIX系统在逻辑上被划分为两个不一样的部分:内核和实用工具(Utility),如图2.1所示。或者你也能够认为是内核和其余部分,一般来讲,全部的访问都要经由Shell。服务器
图2.1 UNIX系统网络
内核是UNIX系统的核心所在,当打开计算机并启动(booted)以后,内核就位于计算机的内存中,直到关机为止。ssh
组成完整的UNIX系统的各类实用工具位于计算机磁盘中,在须要的时候会被加载到内存中并执行。实际上你所知道的全部UNIX命令都是实用工具,所以这些命令所对应的程序也都在磁盘上,仅在须要时才会被载入内存。举例来讲,当你执行date
命令时,UNIX系统会将名为date
的程序从磁盘上载入到内存中,读取其代码来执行特定的操做。编程语言
Shell也是一个实用工具程序,它做为登陆过程的一部分被载入到内存中执行。实际上,有必要了解当终端或终端窗口中的第一个Shell启动时所发生的一系列事件。
在早期,终端是一个物理设备,经过线缆链接到安装了UNIX系统的硬件上。而现在,终端程序可以让你停留在Linux、Mac或Windows环境内部,在受控窗口(managed window)中同网络上的设备交互。一般来讲,你会启动如Terminal或xterm这类程序,而后在须要的时候利用ssh
、telnet
或rlogin
链接到远程系统。
对于系统上的每一个物理终端,都会激活一个叫做getty
的程序,如图2.2所示。
图2.2 getty
进程
只要系统容许用户登陆,UNIX系统(更准确地说,应该是叫做init
的程序)就会在每一个终端端口自动启动一个getty
程序。getty
是一个设备驱动程序,可以让login
程序在其所分配的终端上显示login:
,等待用户输入内容。
若是你是经过ssh
这类程序来链接的,会分配到一个伪终端或伪tty
。这就是为何在输入who
命令时会看到有相似于ptty3
或pty1
这样的条目。
在这两种状况下,会有程序读取帐户和密码信息,对这些信息进行验证,若是没有问题的话,就调用登陆所需的登陆程序。
只要输入相应字符并敲下Enter键,login
程序就完成了登陆过程(见图2.3)。
当login
开始执行时,它会在终端上显示字符串Password:
,而后等待用户输入密码。完成输入并按下Enter键后(出于安全性的考虑,你在屏幕上看不到输入的内容),login
会比对文件/etc/passwd
中相应的条目来验证登陆名和密码。每一个用户在该文件中都有对应的条目,其中包括了登陆名、主目录以及用户登陆后要启动的程序。最后一部分信息(登陆Shell)存储在每行最后一个冒号以后。若是这个冒号后面没有内容,则默认使用标准Shell,即/bin/sh
。
图2.3 用户sue
终端上启动的login
若是是经过终端程序登陆,数据交换也许会涉及系统上的程序(如ssh
)和服务器上的程序(如sshd
),要是你在本身的UNIX计算机上打开了窗口,可能不须要再次输入密码就可以马上登入。很是方便!
把话题转回密码文件。下面3行展现了/etc/passwd
文件内容的典型形式,对应着系统用户:sue
、pat
和bob
。
sue:*:15:47::/users/sue:
pat:*:99:7::/users/pat:/bin/ksh
bob:*:13:100::/users/data:/users/data/bin/data_entry复制代码
待login
将所输入密码的加密形式与特定帐户保存在/etc/shadow
中的加密形式进行比对以后,若是没有问题,它会检查要执行的登陆程序的名称。在绝大多数状况下,这个登陆程序会是/bin/sh
、/bin/ksh
或/bin/bash
。在少数状况下,可能会是一个特殊的定制程序或者/bin/nologin
,后者用于不能进行交互式访问的帐户(经常使用于文件全部权管理)。其背后的理念就是你能够为登陆帐户进行设置,使其登陆到系统以后可以自动运行指定的程序。大多数时候指定的程序都是Shell,毕竟它是一种通用的实用工具,不过这并不是是惟一的选择。
来看用户sue。一旦该用户经过验证,login
会结束掉自身,将控制权转交给sue的终端链接,该链接与标准Shell相连,而后login就从内存中消失了(见图2.4)。
按照以前/etc/passwd
文件中显示的其余条目,pat
获得的是存储在/bin
下的ksh
(这是Korn Shell),bob获得的是一个名为data_entry
的指定程序(见图2.5)。
图2.4 login
执行/usr/bin/sh
图2.5 3个登陆的用户
以前提到过,init
程序会针对网络链接运行相似于getty
的程序。例如,sshd
、telnetd
和rlogind
会响应来自ssh
、telnet
和rlogin
的链接请求。这些程序并无直接和特定的物理终端或调制解调器线路联系在一块儿,而是将用户的Shell链接到伪终端上。你能够在X Window系统的窗口中或使用who
命令查看是否已经经过网络或联网的终端链接登陆到了系统中:
$ who phw pts/0 Jul 20 17:37 使用rlogin登陆 $复制代码
当Shell启动时,它会在终端中显示出一个命令行提示符,一般是美圆符$
,而后等待用户输入命令(图2.6中的第1步和第2步)。每次输入命令并按Enter键(第3步),Shell就会分析输入的内容,而后执行所请求的操做(第4步)。
若是你要求Shell调用某个程序,Shell会搜索磁盘,查找环境变量PATH中指定的全部目录,直到找到指定的程序。找到了该程序后,Shell会将本身复制一份(称为子Shell),让内核使用指定的程序替换这个子Shell,接着登陆Shell就会“休眠”,等待被调用的程序执行完毕(第5步)。内核将指定程序复制到内存中并开始执行。这个复制过来的程序称为进程。程序和进程之间是有区别的,前者是保存在磁盘上的文件,然后者位于内存中并被逐行执行。
若是程序将输出写入到标准输出中,那么输出内容会出如今终端里,除非你将其重定向或经过管道导向其余命令。与此相似,若是程序从标准输入中读取输入,那么它会等着你输入内容,除非输入被重定向到了另外一个文件或经过管道从其余命令导入(第6步)。
当命令执行完毕后,就会从内存中消失,控制权再次交给登陆Shell,它会提示你输入下一条命令(第7步和第8步)。
图2.6 命令执行周期
注意,只要你没有登出系统,这个周期就会周而复始下去。若是登出系统,Shell就会终止执行,系统将会启动一个新的getty
(或者rlogind
等)并等待其余用户登入,如图2.7所示。
重要的是要认识到Shell就是一个程序而已。它在系统中没有什么特权,也就是说,只要有足够的专业技术和热情,任何人均可以建立本身的Shell。这就是为何现在会有这么多不一样风格的Shell,其中包括由Stephen Bourne开发的古老的Bourne Shell、由David Korn开发的KornShell、主要用于Linux系统的Bourne again Shell以及由Bill Joy开发的C Shell。这些Shell都旨在应对特定的需求,各自都有本身独特的功能和特点。
图2.7 登陆周期
如今你知道了Shell会分析(用计算机行话来讲,就是解析)输入的每一行命令,而后执行指定的程序。在解析期间,文件名中的特殊字符(如*
)会被扩展,就像第一章讲到的那样。
Shell还有其余的职责,如图2.8所示。
图2.8 Shell的职责
Shell负责执行你在终端中指定的全部程序。
每次输入一行内容,Shell就会分析该行,而后决定执行什么操做。就Shell而言,每一行都遵循如下基本格式:
program-name arguments复制代码
说得更正式些,输入的这一行叫作命令行。Shell会扫描该命令行,肯定要执行的程序名称及所传入的程序参数。
Shell使用一些特殊字符来肯定程序名称及每一个参数的起止。这些字符统称为空白字符(whitespace characters),它们包括空格符、水平制表符和行尾符(更正式的叫法是换行符)。连续的多个空白字符会被Shell忽略。若是你输入命令
mv tmp/mazewars games复制代码
Shell会扫描该命令行,提取行首到第一个空白字符之间的全部内容做为待执行的程序名称:mv
。随后的空白字符(多余的空格)会被忽略,直到下一个空白字符之间的字符做为mv
的第一个参数:tmp/mazewars
。再到下一个空白字符(在本例中是换行符)之间的字符做为mv
的第二个参数:games
。解析完命令行以后,Shell就开始执行mv
命令,其中包括两个指定的参数:tmp/mazewars
和games
(见图2.9)。
图2.9 执行带有两个参数的mv
命令
刚才提到过,多个空白字符会被Shell忽略。这意味着当Shell处理下面的命令行时:
echo when do we eat? 复制代码
会向echo
程序传递4个参数:when
、do
、we
和eat?
(见图2.10)。
图2.10 执行带有4个参数的echo
命令
echo
会提取命令参数并将其显示在终端中,所以在输出的参数之间加上一个空格会使得命令输出变得更易读:
$ echo when do we eat?
when do we eat?
$复制代码
结果证实echo
命令彻底看不到这些空白字符,它们都被Shell给“没收”了。等到第5章讲引用的时候,你就知道该如何把空白字符包含到程序参数中了,不过,一般来讲,去掉这些多余的空白字符正是咱们想要的作法。
咱们以前讲到过,Shell会搜索磁盘,直到找到须要执行的程序为止,而后由UNIX内核负责程序的执行。在大多数时候,的确如此。但有些命令其实是内建于Shell自身中的。这些内建命令包括cd
、pwd
和echo
。Shell在磁盘中搜索命令以前,它首先会判断该命令是否为内建命令,若是是的话,就直接执行。
不过在调用命令以前,Shell还有点事须要处理,所以,让咱们先来讨论一下这方面的内容。
和比较正式的编程语言同样,Shell容许将值赋给变量。只要你在命令行中将某个变量放在美圆符号$以后,Shell就会将该变量替换成对应的变量值。咱们会在第4章中详细讨论这个话题。
除此以外,Shell还会在命令行中执行文件名替换。实际上Shell,在肯定要执行的程序及其参数以前,会扫描命令行,从中查找文件名替换字符*
、?
或[...]
。
假设当前目录下包含这些文件:
$ ls
mrs.todd
prog1
shortcut
sweeney
$复制代码
如今让咱们在echo
命令中使用文件名替换(*
):
$ echo `*``` 列出全部文件
mrs.todd prog1 shortcut Sweeney
$复制代码
咱们给echo
程序传入了几个参数?1个仍是4个?由于Shell会执行文件名替换,因此答案是4个。当Shell分析下列命令行时
echo *复制代码
它识别出了特殊字符*
,将其替换成当前目录下的全部文件名(甚至还会将这些文件名依字母顺序排列):
echo mrs.todd prog1 shortcut sweeney复制代码
而后Shell决定将哪些参数传给实际的命令。所以,echo
根本不知道星号*
的存在,它只知道命令行上有4个参数(见图2.11)。
图2.11 执行echo
Shell还要负责处理输入/输出重定向。它会扫描每个命令行,从中查找特殊的重定向字符<
、>
或>>
(若是你以为好奇的话,还有一个重定向序列<<
,你会在第12章中学到相关的内容)。
若是你输入命令
echo Remember to record The Walking Dead > reminder复制代码
Shell会识别出特殊的输出重定向字符>
,而后将命令行中的下一个单词做为输出重定向所指向的文件名。在本例中,这个文件名为reminder
。若是reminder
已经存在且用户具备写权限,那么文件中已有的内容会被覆盖掉。若是没有该文件或其所在目录的写权限,Shell会产生错误信息。
在Shell执行程序以前,它会将程序的标准输出重定向到指定的文件。在大多数状况下,程序根本不知道本身的输出被重定向了。它仍照旧向标准输出中写入(这一般是终端),意识不到Shell已经将信息重定向到了文件中。
让咱们来看两个几乎同样的命令:
$ wc -l users
5 users
$ wc -l < users
5
$复制代码
在第一个例子中,Shell解析命令行,肯定要执行的程序名称是wc
并为其传入两个参数:-l
和users
(见图2.12)。
图2.12 执行wc -l users
当wc
执行时,会看到传入的两个参数。第一个参数是-l
,告诉它须要统计行数。第二个参数指定了待统计行数的文件。所以wc
会打开文件users
,统计行数,而后打印出结果及对应的文件名。
第二个例子中的wc
操做略有不一样。Shell在扫描命令行时发现了输入重定向字符<
,其后的单词就被解释成从中重定向输入的文件名。从命令行中提取出了“< users
”以后,Shell就开始执行wc
程序,将其标准输入重定向为文件users
并传入单个参数-l
(见图2.13)。
图2.13 执行wc -l < users
此次当wc
执行时,它会看到传入的单个参数-l
。由于没有指定文件名,wc
会转而去统计标准输入中内容的行数。所以wc -l
在统计行数时,并不知道它其实是在对文件users
进行统计。最后的显示结果和平时同样,可是缺乏了文件名,由于咱们并无为wc
指定。
要理解两条命令在执行上的不一样,这一点很是重要。若是还不太清楚,那么在继续阅读以前复习一下上面的内容。
Shell在扫描命令行时,除了重定向符号以外还会查找管道字符|。每找到一个,就会将以前命令的标准输出链接到以后命令的标准输入,而后执行这两个命令。
若是你输入
who | wc -l复制代码
Shell会查找分隔了命令who
和wc
的管道符号。它将上一个命令的标准输出链接到下一个命令的标准输入,而后执行二者。who
命令执行时会生成已登陆用户列表并将结果写入标准输出,它并不知道输出内容并无出如今终端而是进入了另外一个命令。
当wc
命令执行时,它发现并无指定文件名,所以就对标准输入内容进行统计,并无意识到标准输入并不是来自终端,而是来自于who
命令的输出。
随着本书内容的深刻,你会看到管道中并不只限于有两条命令,你能够在复杂的管道中将3条、4条、5条甚至更多的命令串联在一块儿。这多少有点很差理解,但倒是UNIX系统强大威力的所在。
Shell提供了一些可以定制我的环境的命令。我的环境包括主目录、命令行提示符以及用于搜索待执行程序的目录列表。咱们会在第10章中对此展开详述。
Shell有本身内建的编程语言。这种语言是解释型的,也就是说,Shell会分析所遇到的每一条语句,而后执行所发现的有效的命令。这与C++及Swift这类编程语言不一样,在这些语言中,程序语句在执行以前一般会被编译成可由机器执行的形式。
相较于编译型语言,由解释型语言所编写的程序通常要更易于调试和修改。然而,所花费的时间要比实现相同功能的编译型语言程序更长。
Shell编程语言提供了可在大多数其余编程语言中找到的其余特性。它有循环结构、决策语句、变量、函数,并且是面向过程的。基于IEEE POSIX标准的现代Shell还有许多其余特性,包括数组、