20分钟轻松学会shell编程,80%人看过这篇就收藏了

在过去几十年中所出现的UNIX和类UNIX操作系统家族已经成为如今最为流行、使用最广泛的操作系统之一,这都算不上什么秘密了。对于使用了多年UNIX的程序员而言,一切都顺理成章:UNIX系统为程序开发提供了既优雅又高效的环境。这正是Dennis Ritchie和Ken Thompson在20世纪60年代晚期在贝尔实验室开发UNIX时的初衷。

在本书中,我们使用的术语UNIX泛指基于UNIX的操作系统大家族,其中包括像Solaris这样真正的UNIX操作系统以及像Linux和Mac OS X这样的类UNIX操作系统。

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版)》


试读地址:
http://www.epubit.com.cn/book/onlinechapter/59824

2.1 内核和实用工具

UNIX系统在逻辑上被划分为两个不同的部分:内核和实用工具(Utility),如图2.1所示。或者你也可以认为是内核和其他部分,通常来说,所有的访问都要经由Shell。

图2.1 UNIX系统

内核是UNIX系统的核心所在,当打开计算机并启动(booted)之后,内核就位于计算机的内存中,直到关机为止。

组成完整的UNIX系统的各种实用工具位于计算机磁盘中,在需要的时候会被加载到内存中并执行。实际上你所知道的所有UNIX命令都是实用工具,因此这些命令所对应的程序也都在磁盘上,仅在需要时才会被载入内存。举例来说,当你执行date命令时,UNIX系统会将名为date的程序从磁盘上载入到内存中,读取其代码来执行特定的操作。

Shell也是一个实用工具程序,它作为登录过程的一部分被载入到内存中执行。实际上,有必要了解当终端或终端窗口中的第一个Shell启动时所发生的一系列事件。

2.2 登录Shell

在早期,终端是一个物理设备,通过线缆连接到安装了UNIX系统的硬件上。而如今,终端程序能够让你停留在Linux、Mac或Windows环境内部,在受控窗口(managed window)中同网络上的设备交互。通常来说,你会启动如Terminal或xterm这类程序,然后在需要的时候利用sshtelnetrlogin连接到远程系统。

对于系统上的每个物理终端,都会激活一个叫作getty的程序,如图2.2所示。

图2.2 getty进程

只要系统允许用户登录,UNIX系统(更准确地说,应该是叫作init的程序)就会在每个终端端口自动启动一个getty程序。getty是一个设备驱动程序,能够让login程序在其所分配的终端上显示login:,等待用户输入内容。

如果你是通过ssh这类程序来连接的,会分配到一个伪终端或伪tty。这就是为什么在输入who命令时会看到有类似于ptty3pty1这样的条目。

在这两种情况下,会有程序读取账户和密码信息,对这些信息进行验证,如果没有问题的话,就调用登录所需的登录程序。

只要输入相应字符并敲下Enter键,login程序就完成了登录过程(见图2.3)。

login开始执行时,它会在终端上显示字符串Password:,然后等待用户输入密码。完成输入并按下Enter键后(出于安全性的考虑,你在屏幕上看不到输入的内容),login会比对文件/etc/passwd中相应的条目来验证登录名和密码。每个用户在该文件中都有对应的条目,其中包括了登录名、主目录以及用户登录后要启动的程序。最后一部分信息(登录Shell)存储在每行最后一个冒号之后。如果这个冒号后面没有内容,则默认使用标准Shell,即/bin/sh

图2.3 用户sue终端上启动的login

如果是通过终端程序登录,数据交换也许会涉及系统上的程序(如ssh)和服务器上的程序(如sshd),要是你在自己的UNIX计算机上打开了窗口,可能不需要再次输入密码就能够立刻登入。非常方便!

把话题转回密码文件。下面3行展示了/etc/passwd文件内容的典型形式,对应着系统用户:suepatbob

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的程序。例如,sshdtelnetdrlogind会响应来自sshtelnetrlogin的连接请求。这些程序并没有直接和特定的物理终端或调制解调器线路联系在一起,而是将用户的Shell连接到伪终端上。你可以在X Window系统的窗口中或使用who命令查看是否已经通过网络或联网的终端连接登录到了系统中:

who
phw      pts/0    Jul 20 17:37          

2.3 在Shell中输入命令

当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 登录周期

2.4 Shell的职责

现在你知道了Shell会分析(用计算机行话来说,就是解析)输入的每一行命令,然后执行指定的程序。在解析期间,文件名中的特殊字符(如*)会被扩展,就像第一章讲到的那样。

Shell还有其他的职责,如图2.8所示。

图2.8 Shell的职责

2.4.1 程序执行

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/mazewarsgames(见图2.9)。

图2.9 执行带有两个参数的mv命令

刚才提到过,多个空白字符会被Shell忽略。这意味着当Shell处理下面的命令行时:

echo            when   do        we      eat? 

会向echo程序传递4个参数:whendoweeat?(见图2.10)。

图2.10 执行带有4个参数的echo命令

echo会提取命令参数并将其显示在终端中,因此在输出的参数之间加上一个空格会使得命令输出变得更易读:

$ echo           when   do        we    eat? 
when do we eat? 
$

结果证明echo命令完全看不到这些空白字符,它们都被Shell给“没收”了。等到第5章讲引用的时候,你就知道该如何把空白字符包含到程序参数中了,不过,通常来说,去掉这些多余的空白字符正是我们想要的做法。

我们之前讲到过,Shell会搜索磁盘,直到找到需要执行的程序为止,然后由UNIX内核负责程序的执行。在大多数时候,的确如此。但有些命令实际上是内建于Shell自身中的。这些内建命令包括cdpwdecho。Shell在磁盘中搜索命令之前,它首先会判断该命令是否为内建命令,如果是的话,就直接执行。

不过在调用命令之前,Shell还有点事需要处理,因此,让我们先来讨论一下这方面的内容。

2.4.2 变量及文件名替换

和比较正式的编程语言一样,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

2.4.3 I/O重定向

Shell还要负责处理输入/输出重定向。它会扫描每一个命令行,从中查找特殊的重定向字符<>>>(如果你觉得好奇的话,还有一个重定向序列<<,你会在第12章中学到相关的内容)。

如果你输入命令

echo Remember to record The Walking Dead > reminder

Shell会识别出特殊的输出重定向字符>,然后将命令行中的下一个单词作为输出重定向所指向的文件名。在本例中,这个文件名为reminder。如果reminder已经存在且用户具有写权限,那么文件中已有的内容会被覆盖掉。如果没有该文件或其所在目录的写权限,Shell会产生错误信息。

在Shell执行程序之前,它会将程序的标准输出重定向到指定的文件。在大多数情况下,程序根本不知道自己的输出被重定向了。它仍照旧向标准输出中写入(这通常是终端),意识不到Shell已经将信息重定向到了文件中。

让我们来看两个几乎一样的命令:

wc -l users
      5 users
$ wc -l 

2.4.4 管道

Shell在扫描命令行时,除了重定向符号之外还会查找管道字符|。每找到一个,就会将之前命令的标准输出连接到之后命令的标准输入,然后执行这两个命令。

如果你输入

who | wc -l

Shell会查找分隔了命令whowc的管道符号。它将上一个命令的标准输出连接到下一个命令的标准输入,然后执行两者。who命令执行时会生成已登录用户列表并将结果写入标准输出,它并不知道输出内容并没有出现在终端而是进入了另一个命令。

wc命令执行时,它发现并没有指定文件名,因此就对标准输入内容进行统计,并没有意识到标准输入并非来自终端,而是来自于who命令的输出。

随着本书内容的深入,你会看到管道中并不仅限于有两条命令,你可以在复杂的管道中将3条、4条、5条甚至更多的命令串联在一起。这多少有点不好理解,但却是UNIX系统强大威力的所在。

2.4.5 环境控制

Shell提供了一些能够定制个人环境的命令。个人环境包括主目录、命令行提示符以及用于搜索待执行程序的目录列表。我们会在第10章中对此展开详述。

2.4.6 解释型编程语言

Shell有自己内建的编程语言。这种语言是解释型的,也就是说,Shell会分析所遇到的每一条语句,然后执行所发现的有效的命令。这与C++及Swift这类编程语言不同,在这些语言中,程序语句在执行之前通常会被编译成可由机器执行的形式。

相较于编译型语言,由解释型语言所编写的程序一般要更易于调试和修改。然而,所花费的时间要比实现相同功能的编译型语言程序更长。

Shell编程语言提供了可在大多数其他编程语言中找到的其他特性。它有循环结构、决策语句、变量、函数,而且是面向过程的。基于IEEE POSIX标准的现代Shell还有许多其他特性,包括数组、

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值