Linux 外壳的演变之旅

系统
对于大多数的日常计算任务来说,鼠标的点击操作就可以满足要求了,但要真正利用到Linux相比于其他环境的 优势的话,则最终还是需要弄懂系统的外壳程序来输入命令行才行。可用的命令外壳程序有很多,从Bash和Korn到C shell外壳,以及各种各样有着异域风情的和奇怪的外壳程序等不一而足。我们来了解一下哪一种外壳程序是适用于你的。

对于大多数的日常计算任务来说,鼠标的点击操作就可以满足要求了,但要真正利用到Linux相比于其他环境的 优势的话,则最终还是需要弄懂系统的外壳程序来输入命令行才行。可用的命令外壳程序有很多,从Bash和Korn到C shell外壳,以及各种各样有着异域风情的和奇怪的外壳程序等不一而足。我们来了解一下哪一种外壳程序是适用于你的。

外壳或外壳程序(shell)就像是编辑器:每个人都有自己的喜好,并且会为自己的选择进行强烈的辩护(并会告诉你为什么你应该换用)。诚然,一些外壳程序可以提供不同的功能,但它们都实现了几十年前就已经形成的核心理念。

关 于现代的外壳程序,我的***使用体验发生在1980年代,当时我正在SunOS上开发软件。一旦了解了把一个程序的输出用作另一个程序的输入(甚至可在命 令链中多次这样做)这种能力之后,我就拥有了一种简单有效的创建过滤器和转换的方式。这一核心思想提供了一种构建简单工具的方式,这些工具足够灵活,能够 以一种有益的组合来和其他工具一起使用。通过这种方式,外壳程序不仅提供了一种与内核和设备交换的方式,而且整合了多种服务(比如说管道和过滤器),这类 服务现在在软件开发中已是常见的设计模式了。

我们先从现代外壳程序的简单历史开始,然后探讨Linux目前提供的一些有用的以及一些奇异的外壳程序。

外壳程序的历史

外壳程序——或称作命令行解释器——有着一个很长的历史,但这里的讨论从***个 UNIX®外壳程序开始。(贝尔实验室的)Ken Thompson在1971年开发了名为V6 shell的 ***UNIX外壳程序。与其在Multics上的前身相类似,这个外壳程序(/bin/sh)是一个独立的用户程序,在内核的外部执行。诸如通配符(参数 扩展的模式匹配,比如说*.txt)一类的概念被放在一个名为glob的单独的实用程序中实现,就像是if命令计算条件表达式一样。这种分割维持了外壳程 序的短小精悍,只有不到900行的C源代码(参阅参考资料获得到初始源代码的链接)。

外壳程序为重定向(<>和>>)和管道(|或^)引入了一种紧凑的语法,这些语法仍然在现代的外壳程序中使用。你也依然能够找到对调用顺序命令(使用;分隔)和异步命令(使用&分隔)的支持。

Thompson的外壳程序所缺少的是编写脚本的功能,它的唯一目就是作为一种交换式外壳(命令解释器)来调用命令然后查看结果。

自1977年以来的UNIX外壳程序

在Thompson外壳之后,我们从1977年的现代外壳程序来开始这一了解过程,Bourne外壳在这一年被引入。Bourne 外壳是AT&T贝尔实验室的Stephen Bourne为V7 UNIX创建的,至今还保留了一个可用的外壳程序(在某些情况下,作为默认的根用户执行外壳(root shell))。该作者是在进行了ALGOL68编译器方面的工作之后才开发Bourne外壳的,所以你会发现其语法比其他外壳程序更类似于算法语言 (Algorithmic Language ,ALGOL),而源代码本身,尽管是用C来开发的,甚至使用了宏来赋予它一种ALGOL68的味道。

Bourne外壳有两个主要的目的:作为一个命令解释器,以交互方式执行操作 系统的命令;以及用来编写脚本(编写可通过外壳调用的可重用脚本)。除了取代Thompson外壳的功能之外,Bourne外壳还提供了一些超越其前任的 优势。Bourne引入了控制流、循环和变量,提供了一种更函数化的语言来与操作系统交互(对 话式的或是非对话式的都可以)。该外壳程序还允许你把外壳脚本当成过滤器使用,为处理信号提供集成的支持,不过其缺乏定义函数的功能。***一点是,该外壳 程序纳入了一些我们今天还在使用的功能,其中包括了命令替换(使用反引号),以及在脚本内部嵌入保留的串字面量的HERE文档。

Bourne外壳不仅是前进道路上的很重要的一步,而且是多种派生出来的外壳 程序的基石,这些派生外壳中的许多今天仍然用在一些典型的Linux系统上。图1说明了一些重要的外壳程序的传承关系,Bourne外壳带来了Korn外 壳(ksh)、Almquist外壳(ash)流行的Bourne Again Shell(或称Bash)的发展;而当Bourne外壳发布时,C shell外壳程序(csh)已在开发之中。图1说明了主要的传承关系,但并未包含了所有的影响,一些跨多个外壳的显著贡献在这里并未标注出来。

图1. 自1977年以来的Linux外壳


 

我们稍后会探讨其中的一些外壳程序,并例举出一些对它们的发展有贡献作用的语言和功能。

基本的外壳程序架构

设想中的外壳程序的基础架构很简单(已由Bourne外壳证明),正如你在图2中见到的那样,基本的架构看起来类似一个管道,其中的输入是分析和 解析,接着是符号的扩充(使用各种各样的方法,比如说括号、波浪线、变量和参数的扩展和替换,以及文件名生成等),以及***的命令执行(使用外壳内置的命 令或是外部命令)。

图2. 假想外壳程序的简单架构


你可在参考资料一节找到找到有关链接,了解开源的Bash外壳的架构。

探讨Linux的外壳程序

现在我们来探讨一下几个这样的外壳程序,回顾它们所做出的贡献,并在每个外壳程序中检验一个脚本例子。要查看的外壳程序包括了C shell、Korn 外壳和Bash。

Tenex C shell外壳

1978年,当Bill Joy还是加州大学伯克利分校的在校学生时,他为Berkeley Software Distribution (BSD) UNIX系统开发了C shell。五年之后,该外壳引入了Tenex系统(在DEC PDP系统上很流行)上的功能。除了命令行编辑功能之外,Tenex还引入了文件名称和命令的补全功能。Tenex C shell(tcsh)保持了对csh的向后兼容,但提升了其整体的交互功能。tcsh是Ken Greer在卡内基 - 梅隆大学开发出来的。

C shell的一个主要设计目标是创建一种看上去类似于C语言的脚本语言,鉴于C当时是在用的主要语言(加之操作系统绝大部分都是使用C来开发的),所以这是一个很实用的目标。

Bill Joy带到C shell中的一个实用功能是命令的历史记录,这一功能维持之前执行过的命令的一个历史,并允许用户查看并轻松地选择前面的命令来执行。例如,输入命令 history就会显示出之前执行过的命令,使用上下箭头按键来选择命令,或是使用!!来执行前面的一个命令。引用前一个命令的所有参数也是可以的,比如 说,!*引用前一个命令的所有参数,而!$则是引用前一个命令的***一个参数。

看一下一个简短的tcsh脚本例子(清单1),该脚本用到了一个参数(目录名称),给出该目录下的所有可执行文件和找到的文件的数目。我在每个例子中都重用了这一脚本,以此来说明一些不同之处。

该tcsh脚本被分成了三个基本的部分,首先,需要注意的是,我是使用了shebang或称作hashbang的符号(#!)来声明这一文件是可 被外壳执行程序(在本例中是tcsh二进制执行文件)解释的,这就可以让我把该文件当成一个普通的可执行文件来执行,而不需要在它之前加上解释器的二进制 文件名。脚本维持了一个找到的可执行文件的计数,所以我把这一计数初始化为零。

清单1. 用tcsh编写的查找所有可执行文件的脚本

  1. #!/bin/tcsh  
  2. # find all executables  
  3.     
  4. set count=0 
  5.     
  6. # Test arguments  
  7. if ($#argv != 1) then  
  8. echo "Usage is $0  
  9.     
  10. "  
  11. exit 1  
  12. endif  
  13.     
  14. # Ensure argument is a directory  
  15. if (! -d $1) then  
  16. echo "$1 is not a directory."  
  17. exit 1  
  18. endif  
  19.    
  20. # Iterate the directory, emit executable files  
  21. foreach filename ($1/*)  
  22. if (-x $filename) then  
  23. echo $filename  
  24. count = $count + 1  
  25. endif  
  26. end  
  27.     
  28. echo  
  29. echo "$count executable files found."  
  30.     
  31. exit 0 

***部分内容测试用户传递进来的参数,变量#argv代表了传递进来的参数个数(不包括命令名称自身)。你可以通过指定它们的索引来访问这些 参数。例如,#1指向***个参数(这是argv[1]的简写)。该脚本预期有一个参数,如果没有找到该参数的话,就发出一条错误消息,使用$0来表示在控 制台中输入的命令(argv[0])。

第二部分内容确保传递进来的参数是一个目录, 如果参数是一个目录的话,运算符-d返回True。不过要注意的一点是,我先指定了一个!符号,其代表的意思是否定。通过这种方式,表达式要说的是,如果参数不是一个目录,则发出一条错误消息。

***一部分内容遍历了目录中的文件,测试它们是否是可执行的。我使用了便捷的foreach这一遍历器,其遍历括号(本例中是一个目录)中的每个 条目,然后在循环体中对每个条目进行检查,该步骤使用了运算符-x来检查文件是否是可执行的,如果是的话,输出该文件名称并且计数加一。在脚本的末尾,我 输出可执行文件的数目。

Korn外壳

Korn外壳(Korn shell,ksh)由David Korn设计,其差不多是和Tenex C shell同一时期引入的。Korn外壳最吸引人的功能之一是被当成脚本语言使用,与此同时还向后兼容最初的Bourne外壳。

Korn外壳原来是专有软件,直到2000年的时候,它才(遵照通用公共许可协议)作为开源软件发布。除了提供很强的向后兼容Bourne外壳的 功能之外,Korn外壳还包含了一些来自其他外壳的功能(比如说csh的历史记录功能)。该外壳还提供了一些更先进的功能,这些功能可以在诸如Ruby和 Python一类的现代脚本语言中找到——比如说,关联数组和浮点运算。Korn外壳在许多操作系统上都是可用的,这些系统中就包括了IBM® AIX® and HP-UX;并且尽力去支持 Portable Operating System Interface for UNIX(POSIX)外壳语言的标准。

Korn外壳是从Bourne外壳派生而来的,因此其看上去更类似于Bourne外壳和Bash而不是C shell。我们来看一个Korn外壳的查找可执行文件的例子(清单2)。

清单2. 用ksh编写的查找所有可执行文件的脚本

  1. #!/usr/bin/ksh  
  2. # find all executables  
  3.  
  4. count=0 
  5.  
  6. # Test arguments  
  7. if [ $# -ne 1 ] ; then  
  8. echo "Usage is $0  
  9.    
  10. "  
  11. exit 1  
  12. fi  
  13.     
  14. # Ensure argument is a directory  
  15. if [ ! -d "$1" ] ; then  
  16. echo "$1 is not a directory."  
  17. exit 1  
  18. fi  
  19.     
  20. # Iterate the directory, emit executable files  
  21. for filename in "$1"/*  
  22. do  
  23. if [ -x "$filename" ] ; then  
  24. echo $filename  
  25. count=$((count+1))  
  26. fi  
  27. done  
  28.    
  29. echo  
  30. echo "$count executable files found."  
  31.     
  32. exit 0 

在清单2中你首先会注意到的一件事情是,其和清单1相类似。就结构上来说,脚本几乎就是相同的,主要的不同体现在条件语句、表达式和遍历的执行方式上。ksh并未采用类C的测试运算符,其采用了典型的Bourne式的运算符(-eq、-ne、-lt等)。

Korn外壳在遍历方面也有些不同,在korn外壳中,所用的是for in结构,其使用了命令替换来表示文件列表,该文件列表通过命令ls '$1/*的标准输出来创建,而该命令则代表了指定名字的子目录中的内容。

除了前面明确了的其他功能之外,Korn还支持别名功能(使用用户定义的串来替代一个词)。Korn有许多其他功能在默认情况下是禁用的(比如说文件名称的补全),不过这些功能可由用户来启用。

Bourne-Again Shell外壳

Bourne-Again Shell,或称作Bash,是一个开源的GNU项目,其目标是取代Bourne外壳,Bash是由Brian Fox开发出来的,其已成为最常提供的外壳之一(在Linux、Darwin、Windows®、Cygwin、Novell、Haiku等等之上都有它 的身影)。顾名思义,Bash是Bourne外壳的一个超集,大多数的Bourne脚本都可不做修改就能执行。 

【编辑推荐】

  1. Linux人才求职宝典 技能缺口及区域分布
  2. 三英战吕布 Windows 7与Linux三版本厮杀
  3. 2011年度gnu linux 发行版本大事记
  4. 《Linux运维趋势》第15期:虚拟化管理软件选型
  5. 从Unix到Linux的迁移部署:移植、升级和测试
责任编辑:张浩 来源: yeeyanyeeyan
相关推荐

2011-07-29 09:56:23

2020-08-28 07:00:00

WSLLinuxWindows 10

2012-03-15 20:56:23

iPad

2015-11-16 15:20:30

微软开源Linux

2010-08-16 09:09:40

Linux安全

2019-07-02 16:57:20

混合云技术Linux

2023-11-28 13:21:52

2009-12-11 16:48:11

VS 2008的外壳

2013-11-11 13:25:31

2022-07-11 08:20:49

DDoS攻击网络攻击

2023-05-17 15:38:55

2010-01-05 13:56:37

2010-04-01 16:17:37

Oracle复制

2011-12-13 14:19:32

iPhone信息图

2021-06-22 13:52:13

综合布线数据中心智能建筑

2012-02-23 09:24:15

Lumia聚碳酸酯

2015-11-19 14:48:01

LinuxLinux容器虚拟机

2012-10-30 09:48:46

Linux用户Windows体验

2015-11-18 19:03:27

开发者Linux容器

2016-02-01 13:48:53

容器容器技术
点赞
收藏

51CTO技术栈公众号