You are on page 1of 530

Linux Shell 实例精解

Ellie Quigley 著

中国电力出版社
书  名:Linux Shell实例精解
 
作  者:Ellie Quigley
 
译 者:吴雨浓 
 
出版社:中国电力出版社
 
出版日期: 2003  
 
字数:766千字  开本:16 
 
ISBN  7-5083-1306-2/TP.420
 
定 价: 59.00  
 
2 Linux Shell 循序渐进

前 言

在 UNIX 下进行 shell 编程是一件非常有趣的事情,现在在 Linux 下更是这样。在出版


了《UNIX shell 》以后,Mark Toud 建议我写一本关于在 Linux 下进行 shell 编程的书。开
始我们认为这很简单,至少我是这样认为,因为毕竟 Bourne 和 Bourne Again Shell 以及 C 和
TC shell 之间没有太大的不同。你也许认为它只是添加了一些简单的新特性,但事实上并非
如此。这就像是一本崭新的书跟草稿不同一样。
虽然 UNIX、Gnu 工具,以及 shell 都提供了很多扩展和新特性,但 Linux 提供的不仅是
Gnu 的工具,还有很多功能完善的 shell。我已经在《UNIX shell by Example》中详细地讲述
过 Korn shell 了,所以在本书中将把注意力集中在最流行的两个 Linux shell 上——Borne
Again Shell(bash)和 TC Shell(tcsh)。
由于新特性、增加和内建的插件等等因素,讲解 shell 的章节将不得不被剖开。在以前
需要两章讲解的问题,现在则需要 4 章。那是一个漫长枯燥的过程,当我差不多完成 bash 这
一章的时候,却发现自己没有使用最新的版本,于是又被迫重写了稿子。因为读者未必使用
相同版本的 bash,所以本书的内容将覆盖新的版本和老的版本。
书中将介绍了成功编写 shell 程序所必不可少的 Gnu 工具——gawk、grep 和 sed。它们
是样板、操作、文本编辑以及从文件和管道中提取数据的理想工具。
在学习过程中,shell 首先是一个交互程序,所有的事情都是由命令行实现,其次作为一
种程序语言,程序结构在 shell 脚本中得到描述和示范。
事实证明,简单的例子更利于理解。随着每一行程序的解释和输出,在小例子当中理解
每一个概念。学习过我的第一本书《Perl By Example》和《UNIX Shell By Example》的人会
发现,这种方法被广泛应用。本书就是要使你在完全了解 shell 前就开始逐渐具备读写和理
解 shell 程序的能力。

PDF created with pdfFactory Pro trial version www.pdffactory.com


目 录

前 言

第1章 Linux Shell 介绍 ..............................................................................................................1


1.1 为什么要使用 Linux .........................................................................................................1
1.2 Shell 的定义和功能 ..........................................................................................................2
1.3 系统启动和登录 Shell ......................................................................................................5
1.4 Shell 和进程 ......................................................................................................................7
1.5 环境与继承......................................................................................................................12
1.6 从脚本执行命令..............................................................................................................22

第 2 章 Linux 工具箱 ..................................................................................................................31


2.1 正则表达式(regular expression) ................................................................................31
2.2 正则表达式元字符的组合..............................................................................................37

第3章 grep 家族 ............................................................................................................. 43


3.1 grep 命令 .........................................................................................................................43
3.2 扩展 grep(grep-E 或者 egrep)....................................................................................53
3.3 固定 grep(grep-F 或者 fgrep).....................................................................................59
3.4 递归 grep(rgrep) .........................................................................................................59
3.5 grep 与管道 .....................................................................................................................59
3.6 grep 与选项 .....................................................................................................................60

第4章 流线式编辑器——sed........................................................................................... 71
4.1 什么是 sed .......................................................................................................................71
4.2 sed 版本 ...........................................................................................................................71
4.3 sed 怎样工作? ...............................................................................................................72
4.4 定址 .................................................................................................................................72
4.5 命令和选项......................................................................................................................72
4.6 错误信息和退出状态......................................................................................................74
4.7 sed 实例 ...........................................................................................................................76
4.8 sed 脚本 ...........................................................................................................................89

PDF created with pdfFactory Pro trial version www.pdffactory.com


第 5 章 gawk 实用程序:Linux 工具——gawk ........................................................................95
5.1 什么是 awk,什么是 nawk,什么是 gawk?...............................................................95
5.2 awk 的格式......................................................................................................................96
5.3 格式输出........................................................................................................................100
5.4 文件中的 awk 命令.......................................................................................................103
5.5 记录和域........................................................................................................................104
5.6 模式与动作....................................................................................................................108
5.7 正则表达式....................................................................................................................109
5.8 脚本文件中的 awk 命令............................................................................................... 112
5.9 复习 ............................................................................................................................... 113

第 6 章 gawk 功能:给表达式赋值.........................................................................................123
6.1 比较表达式....................................................................................................................123
6.2 复习 ...............................................................................................................................127

第7章 gawk 功能:gawk 编程 ..............................................................................................137


7.1 变量 ...............................................................................................................................137
7.2 重新定向和管道............................................................................................................141
7.3 管道 ...............................................................................................................................143
7.4 关闭文件和管道............................................................................................................144
7.5 回顾 ...............................................................................................................................145
7.6 条件语句........................................................................................................................156
7.7 循环 ...............................................................................................................................158
7.8 程序控制语句................................................................................................................159
7.9 数组 ...............................................................................................................................160
7.10 awk 内建函数..............................................................................................................167
7.11 自定义函数..................................................................................................................174
7.12 复习 .............................................................................................................................175
7.13 其他细节......................................................................................................................180
7.14 回顾 .............................................................................................................................187

第 8 章 交互使用 bash Shell ...................................................................................................195


8.1 介绍 ...............................................................................................................................195
8.2 命令行快捷键................................................................................................................217
8.3 变量 ...............................................................................................................................242

第 9 章 bash Shell 编程 ...........................................................................................................281


9.1 介绍 ...............................................................................................................................281
9.2 读取用户输入................................................................................................................282
9.3 数学计算........................................................................................................................285

PDF created with pdfFactory Pro trial version www.pdffactory.com


9.4 位置参量与命令行参数................................................................................................288
9.5 条件结构和流控制........................................................................................................292
9.6 循环命令........................................................................................................................308
9.7 函数 ...............................................................................................................................326
9.8 陷阱信号........................................................................................................................332
9.9 调试 ...............................................................................................................................336
9.10 用 getopts 处理命令行选项 ........................................................................................337
9.11 eval 命令与命令行解析 ..............................................................................................341
9.12 bash 选项 .....................................................................................................................342
9.13 Shell 内建命令 ............................................................................................................346

第 10 章 交互式 TC Shell ........................................................................................................355


10.1 简介 .............................................................................................................................355
10.2 TC Shell 环境 ..............................................................................................................357
10.3 命令行快捷方式..........................................................................................................366
10.4 作业控制......................................................................................................................389
10.5 元字符 .........................................................................................................................393
10.6 重新定向和管道..........................................................................................................397
10.7 变量 .............................................................................................................................404
10.8 数组 ............................................................................................................................. 411
10.9 特殊变量和操作符......................................................................................................413
10.10 命令替换....................................................................................................................416
10.11 引用............................................................................................................................418
10.12 内建命令....................................................................................................................424

第 11 章 用 TC Shell 编程 ...............................................................................................439
11.1 创建 Shell 脚本的步骤................................................................................................439
11.2 读取用户输入..............................................................................................................441
11.3 计算..............................................................................................................................443
11.4 调试脚本......................................................................................................................444
11.5 命令行参数..................................................................................................................447
11.6 流程控制和条件语句..................................................................................................448
11.7 循环..............................................................................................................................464
11.8 中断处理/操作.............................................................................................................471
11.9 setuid 脚本 ...................................................................................................................472
11.10 储存脚本....................................................................................................................472
11.11 内置命令....................................................................................................................473

PDF created with pdfFactory Pro trial version www.pdffactory.com


附录 A Shell 程序员的实用工具..............................................................................................479

附录 B Shell 比较 .....................................................................................................................521
B.1 tcsh 与 csh .....................................................................................................................521
B.2 bash 与 sh......................................................................................................................522

附录 C 正确使用引用的步骤 ...................................................................................................527
C.1 反斜线(参考 表 C.1) ............................................................................................527
C.2 单引号(参考 表 C.1) ............................................................................................527
C.3 双引号(参考 表 C.2) ............................................................................................527
C.4 联合引用.......................................................................................................................528
C.5 例子...............................................................................................................................529

PDF created with pdfFactory Pro trial version www.pdffactory.com


第1章

Linux Shell 介绍
1.1 为什么要使用 Linux
1991 年,刚刚大学毕业的 Linux Torvalds 在芬兰的赫尔辛基大学开发了一种类 UNIX 的
操作系统内核,这种操作系统被设计为在 PC 上运行的 UNIX 系统,从一开始作为个人的兴
趣爱好到现在发展为被安装到全世界大约 1 千万计算机上的功能完善的 32 位操作系统, 其使
用者的数量还在显著增长。首先,Linux 向黑客提供了一种可以自由下载的,并在内核层面
上同样可以自由修改和测试代码的操作系统,它被 20 世纪 80 年代早期的 UNIX 黑客狂热推
崇。现在,跟 UNIX 相比,Linux 不仅为大学黑客和“geek”们所喜爱的,在更广泛的范围
内被个人和专业级用户所使用,通常为拥有大量计算机的网络系统提供服务。在很多情况下,
Linux 都作为 Windows 的替代选择,在 PC 世界中得到了参与反对微软霸权的新革命团体、
会议和出版物的支持。
在许多程序员和开发人员的帮助下,Linux 迅速成长为今天类 UNIX 的与 POSIX 兼容的
32 位的功能完善的操作系统。1992 年,自由软件基金会把 Gnu 软件加入到 Linux 内核中,使
它成为一个意义上完整的操作系统,并把 Linux 内核源代码置于 GPL(General Public License)
许可证之下。 自由软件基金会提供上百个 Gnu 实用软件,它们包括 UNIX 下的标准 Bourne Shell
的加强。Linux 下的默认 Shell——Bourne Again Shell 是 Bourne Shell 的加强版,不仅在编程方
式方面,而且在交互方式方面,它都允许用户通过裁减他们的工作环境和建立快捷方式来提高
其工作效率。其他的 Gnu 工具,如 grep、sed 和 gawk 都跟 UNIX 下的同名工具功能类似,但
是也都被加强并设计为与 POSIX1 兼容的了。内核和 Gnu 工具的结合,以及 Linux 可以在 PC 上
运行这样一个事实,使 Linux 成为可以替代以前的 UNIX 和微软操作系统的一个良好选择。何
况 Linux 对任何想得到它的人来说,包括源代码、办公套件和软件包在内都是自由的,无论你
从互连网上下载还是购买发行套件的 CD,Linux 都是可移植的、稳定的和安全的。它可以使你
的 PC 强大得像一个工作站。

1.1.1 POSIX 是什么


为了给不同的程序和操作系统提供相同的软件标准,IEEE 和 ISO(国际标准化组织)

1.Shell 功能的标准定义是 Posix1003.2。

PDF created with pdfFactory Pro trial version www.pdffactory.com


2 第1章

共同提出了 POSIX 标准[可查阅开放系统标准(Open Systems Standards)]。其作用就是为


应用软件在不同的平台之间移植提供与 UNIX 类似的标准。也就是在一台机器上写的软件在
另外一台硬件配置不同的计算机上同样可以编译和运行。例如,在 FreeBSD 上写的软件可以
运行在 Solaris、Linux 和 HPUX 机器上。1988 年第一个标准出炉了,被称为 POSIX1003.1。
它的目的是提供 C 语言标准库。1992 年,POSIX 组为 Shell 和实用程序建立了标准,为开发
可移植的 Shell 脚本提供了一组定义,被称为 IEEE1003.2 POSIX Shell 标准。虽然没有强制
要求,但是多数 UNIX 系统买主都遵守 POSIX 标准。在讨论 Shell 和它们标准的 UNIX 实
用程序的时候,遵守 POSIX 也就意味着在写新的实用程序或者强化旧的实用程序的时候,
尽量遵守由 POSIX 委员会提出的标准。例如,Bourne Again Shell 就是几乎 100%兼容的
Shell、gawk 是一个可以在严格的 POSIX 模式下运行的用户实用程序。

1.2 Shell 的定义和功能


Shell 是提供操作系统核心(称为 kernel)与用户之间交互的特殊程序,参见图 1.1。这
个 kernel 在启动时被装入内存,并管理系统直到关机为止。它负责建立和控制进程,管理内
存、文件系统、通信等等。其他的实用程序,包括 Shell 在内都存储在硬盘上。kernel 把程序
从硬盘中装入内存,运行它们,并在程序运行结束后回收被程序占用的系统资源。Shell 是从
你登录就开始运行的实用程序,它允许用户通过 Shell 脚本或者命令行的方式输入命令,并
通过翻译这些命令完成用户与 kernel 的交互。

Shell

图 1.1 kernel、Shell 以及用户的关系示意图

当你登录到计算机上的时候,一个交互 Shell 会被启动并提示你输入。当你输入一条命


令以后,Shell 会以如下的方式响应:①解析命令行;②处理通配符、重新定向、管道和作业
控制;③搜索外部命令,如果找到就执行它。若第一次接触 Linux,你将不得不花费大量的
时间用于学习如何从提示符执行命令,因而你将非常喜欢使用这种交互式的 Shell。
如果经常需要输入相同的一组命令,你就会希望系统能够自动执行这些任务。为了实
现这个目的,将所有的命令放在一个可执行文件中,该文件称为 Shell 脚本。Shell 脚本跟
批处理文件相似,复杂的 Shell 脚本包括判断、循环、文件操作等等程序结构。编写脚本不
仅需要学习程序结构和编写技巧,还需要很好地理解 Linux 实用程序以及它们是如何工作
的。有一些实用程序,例如 grep、sed 和 gawk,被用于在脚本中控制文件命令输出和文件

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 3

的时候极为强大。当熟悉这些工具和一些特定的 Shell 程序结构以后,你就可以开始编写有


用的脚本了。当在脚本内执行命令时,Shell 被当作一种编程语言使用。

1.2.1 三种主要的 UNIX Shell


大多数 UNIX 系统都支持的优秀的 Shell 有 Bourne Shell (AT&T Shell)、C Shell(Berkeley
Shell)和 Korn Shell(超级 Bourne Shell) 。这三种 Shell 在交互方式下的行为极为相似,只是
在作为脚本语言的时候在语法和执行效率上有差别。
Bourne Shell 是 UNIX 下的标准 Shell,被用于系统管理。大多数系统管理脚本,例如 rc
start 和 stop 脚本,还有 shutdown 脚本都是 Borne Shell 脚本。在单用户模式下,它通常被管
理员在 root 权限下运行。它是在 AT&T 被写成的,并以简洁、紧凑和迅速著称。默认的 Bourne
Shell 提示符是美元符号$。
C Shell 在伯克利(Berkeley)开发成功,加入了大量的特性,如命令行历史、别名、内
建算法、文件名补全和作业控制。跟 Bourne Shell 相比,使用交互 Shell 的用户更喜欢 C Shell,
但系统管理员更喜欢用 Bourne Shell 来写脚本,因为在相同情况下,Bourne Shell 比 C Shell
更简单,执行更快。默认的 C Shell 的提示符是%。
Korn Shell 是 David Korn 在 AT&T 完成的 Bourne Shell 的超级版本, 它比 C Shell 更强大,
有很多新特性被加入。Korn Shell 的新特性包括:可编辑的历史、别名、函数、内建算法、
正则表达式通配符、 作业控制、协处理和指定排错。 Bourne Shell 几乎完全向上兼容 Korn Shell,
所以老版本的 Bourne Shell 在 Korn Shell 下仍可以正常地运行。Korn Shell 的默认提示符是美
元符号$。

1.2.2 主要的 Linux Shell


Linux 下的 Shell 并不是专属于 Linux 操作系统。它们在 UNIX 系统下也可以自由地使用
和编译。在你安装 Linux 的时候就已经初步认识了 Gnu 的 Shell 和工具,它们不是 UNIX 下
的标准 Shell 和工具。虽然 Linux 支持很多 Shell,但是 Bourne Again Shell(bash)和 TC Shell
(tcsh)是最流行的。Z Shell 是从 Bourne Again Shell、TC Shell 及 Korn Shell 发展来的集合
了很多新特性的 Shell。Public Domain Korn Shell(pdksh)是 Korn Shell 的克隆,你可以买到
合法的 AT&T(它是很多无名的小 Shell 的所有者)的 Korn Shell。
通过查看文件/etc/shell,能够得知在你的 Linux 版本下可以运行哪个 Shell,如实例 1.1
所示。

实例 1.1
$ cat /etc/shell
/bin/bash
/bin/sh
/bin/ash
/bin/bsn
/bin/tcsh
/bin/csh
/bin/ksh
/bin/zsh

PDF created with pdfFactory Pro trial version www.pdffactory.com


4 第1章

说明
1 /bin/shell 文件包含了在你的 Linux 版本下可以运行的 Shell 程序的列表。最常用的版本是 bash
(Bourne Again Shell)、tcsh(TC Shell)和 ksh(Korn Shell)

切换到一个/bin/shell 列表中的 Shell,可以使用 chsh 命令和 Shell 名字。例如,


若想把 Shell
切换为 TC Shell,可以在命令行输入:
chsh /bin/tcsh

1.2.3 回顾 Shell 的历史


第一个有意义的标准 UNIX Shell 出现在 1979 年年末的 V7(AT&T 第七版)UNIX 中,
并以其作者的名字 Stephon Bourne 命名。Bourne Shell 基于 Algol 编程语言,主要用于系统管
理任务的自动执行。虽然它的简单和快速使得它很流行,但是它缺少在交互环境下使用所需
的许多特性,例如历史、别名和作业控制。到了 bash,就是 Bourne Again Shell,它是由自由
软件基金会的 Brian Fox 在 Copyleft 的许可证下开发的,现在已经成为流行的 Linux 操作系统
的默认 Shell。它被特意设计成遵守 IEEE POSIX 1003.2/ISO9945.2 Shell 和工具标准。bash 给
交互方式和脚本方式都提供了一些 Bourne Shell 没有的新特性, 但是 Bourne Shell 的脚本依然
可以不加修改就在 bash 下运行,它还合并了 C Shell 和 Korn Shell 许多有用的特性。它很大,
且加强了 Bourne Shell 如下的方面:命令行历史和编辑、目录堆栈、作业控制、别名、函数、
阵列、基于 2~64 的整数计算以及 Korn Shell 的特性,例如扩展元字符、菜单选择循环、let
命令等等。
C Shell 是 20 世纪 70 年代末期由美国加州(California)大学伯克利分校开发的,并作
为 2BSD UNIX 的一部分发布,主要作者是 Bill Joy。C Shell 提供大量标准 Bourne Shell 没
有提供的额外特性。C Shell 基于 C 语言,在用作编程语言时,使用跟 C 语言类似的语法。
它也为交互使用而强化过,例如命令行历史、别名和作业控制。由于被设计为在大型机上
使用并加入了大量的特性,所以 C Shell 在小型机器上运行显得有些慢,即使在大型机器上
跟 Bourne Shell 相比也显得迟缓。
TC Shell 是 C Shell 的扩展。它的新特性是:命令行编辑(emacs 和 vi) 、滚动历史列表、
高级文件名、变量、命令补全、拼写检查、作业规划、自动锁定和退出登录、历史列表中的
时间印章等等,所以它的尺寸也很大。
Bourne Shell 和 C Shell 的同时存在给 UNIX 的用户提供了选择,同时也引起了关于哪个
Shell 更好的争论。AT&T 的 David Kourne 在 20 世纪 80 年代中期,发明了 Kourne Shell。它
在 1986 年发布, 并且在 1988 年正式成为 SRV4 UNIX 的一部分。 Kourne Shell 作为 Bourne Shell
的超级版本不仅运行在 UNIX 上,也运行在 OS/2、VMS 和 DOS 上。它向 Bourne Shell 提供
向上兼容的能力,加入了很多 C Shell 的流行特性,迅速而高效。Korn Shell 先后有过多次修
订。应用最广泛的版本是 1988 年的版本,虽然 1993 版本更受欢迎。Linux 的用户可能会发
现他们使用的是 Korn Shell 的免费版本,被称为 Public Domain Korn Shell,或者简称 pdksh,
是 David Korn 1988 年版本的克隆。它现在是免费和可移植的,并逐渐完全兼容在 UNIX 下的
同名 Shell 和 POSIX 标准中。还有一个 Z Shell,是 Korn Shell 带有 TC Shell 特性的另一个克
隆,作者是 Paul Falsted,在很多的站点上都可以免费得到它。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 5

1.2.4 本书介绍什么 Shell?


由于 Bourne Again Shell 和 TC Shell 提供了最多的新特性,所以本书将集中介绍这两种
流行的 Shell。在《UNIX Shell by Example》2 中介绍过的 Korn Shell、Bourne Shell 和 C Shell
在这里还将被讲到。Bourne Again Shell 2.0 版本在作为程序语言时跟 Korn Shell 非常的相似,
在交互使用时有很多类似 C Shell 的特性。同样,在作为程序语言的时候,TC Shell 跟 C Shell
基本相同,但是在交互使用的时候加入了许多新特性。

1.2.5 Shell 的使用


Shell 的一个主要用途是翻译提示符后面的命令。Shell 解析命令行,把它拆成由空格分
隔的单词(称为“token”) 。所谓的空格是指制表符、空格或者新的一行。如果命令行包含特
殊的元字符,Shell 就给它们赋值。Shell 控制文件 I/O 和后台运行。在命令行完成处理以后,
就开始搜寻命令并开始它的执行。
Shell 的另一个重要功能是通过设置 Shell 初始化文件,使用户的工作环境个性化。这些
文件包括终端键盘设置和窗口字符的定义,设置终端类型、权限、提示和搜索路径变量的值,
设置特定的应用所必需的变量,例如:windows、字处理程序和编程语言库。Bourne Shell 和
TC Shell 提供更多的特性进一步使用户环境个性化,这些特性包括历史和别名、文件名和命
令补全、拼写检查、帮助、内建变量防止用户数据丢失、意外退出,以及登录,当作业完成
以后通知用户等等。
Shell 也可以作为解释型的程序语言。Shell 程序也称为脚本,由文件中的命令行组成,
在编辑器或在命令行中创建。这些命令通过程序结构组织在一起,这些结构包括:变量赋值、
环境检测、循环等等。然而你并不需要编译脚本,因为它们在从键盘输入的时候就被逐行解
释了。因为 Shell 负责脚本命令解释,所以对于用户来说理解这些命令还是相当必要的。在
本书的附录 A 中列出了可用的命令以及它们的用法。

1.2.6 Shell 的响应


Shell 负责最终保证所有通过命令行输入的命令都被正确执行,这个过程包括:
(1)读取输入并解析命令行。
(2)给特殊字符赋值。
(3)建立管道、重新定向和后台进程。
(4)处理信号。
(5)建立可执行程序。
在后面,我们将针对每一个不同的 Shell 讨论以上每个项目的细节。

1.3 系统启动和登录 Shell


当你启动系统时,
第一个进程称为 init。每一个进程都有其专用的进程 ID 号码,
称为 PID。

2.Quigley, Ellie. UNIX Shells by Example, 2nd Edition. Upper Saddle River, NJ: Prentice Hall, 1999.

PDF created with pdfFactory Pro trial version www.pdffactory.com


6 第1章

因为 init 是第一个进程,所以它的 PID 是 1。init 进程初始化系统,并启动另外一个进程打开


命令行终端,建立命令行终端所必需的标准输入(stdin)、标准输出(stdout)和标准错误
(stderr)。标准输入通常来自键盘,标准输出和标准错误显示在监视器上。从这个角度来看,
一个登录提示符出现在你的控制台上。在你输入用户名以后,提示你输入密码(你应该有一
个不超过 10 个字符的密码) 。/bin/login 程序将通过核对/etc/passwd 的第一个域验证你的 ID。
如果你的用户名确实存在,下一步就是对你的密码运行加密程序来验证你的密码是否正确。
一旦密码得到验证,login 程序就把环境变量传送给 Shell,以定义一个工作环境。并在
/etc/passwd 文件中提取 HOME、SHELL、USER、LOGNAME 等变量值。HOME 值表示的是
你的主目录(Home Directory)。SHELL 变量中的值是你登录 Shell 的名字,被记录在 passwd
文件的最后一项里。USER 和(或)LOGNAME 表示你的登录名。PATH 变量帮助 Shell 找到
常用的实用程序所在的目录。它是一个独立列表的克隆,这个列表的初始值是/usr/local/bin:
/bin:/usr/bin。当登录完成时,在/etc/passwd 文件最后一项所记录的程序将被执行。通常情况
下,这个程序是一个 Shell。如果/etc/passwd 文件的最后一项是/bin/tcsh 或者/bin/csh,TC Shell
程序就将被启动。如果/etc/passwd 文件的最后一项是/bin/bash、/bin/sh 或者什么都不是则
Bourne Again Shell 程序就被启动。如果/etc/passwd 文件的最后一项是/bin/pdksh,Public
Domain Korn Shell 程序就被启动。这个 Shell 就称为 Login Shell。
当 Shell 启动以后,Shell 就寻找系统的初始化文件,并在你的主目录下寻找特定的 Shell
初始化文件。如果这样的文件存在,就执行它们。这些初始化文件的作用就是进一步地定制
用户的环境。文件中的命令执行完以后,一个 Shell 提示符将出现在你的控制台上。如果你
使用的是窗口系统,这个时候出现的将是一些 xterm(X 终端)或者可视化的 Shell 窗口。当
你看见 Shell 提示符时,不论你在控制台上、xterm 还是其他的桌面窗口中,都表示 Shell 程
序在等待你的输入了。

1.3.1 解析命令行
当你在提示符下输入一条命令后,Shell 读取行输入并解析命令行,把命令行拆成单
个的字,称为令牌。令牌之间依靠空格和制表符分隔,以换行符作为命令行结束的标志。3Shell
首先判断命令行的第一个字是内建命令还是存储在磁盘上的。如果是内建命令,Shell 就
执行该命令。否则,Shell 就在 PATH 变量指定的目录列表中查找这个程序。如果找到,
就启动一个新的进程来执行这个程序,而 Shell 就休眠(或者等待)直到程序执行完毕,
如果必要,还将报告程序的退出状态。跟着出现提示符,而整个进程都将被重新启动。命
令行的处理顺序如下所示:
(1)历史记录替换(需要设置) 。
(2)命令行拆分为 token(单词) 。
(3)历史更新。
(4)处理引用。
(5)定义别名替换和函数(如果需要) 。
(6)建立重新定向、后台和管道。
(7)变量替换($user、$name 等等)。

3.把命令行拆分为单词的过程称为“词汇分析”(lexical analysis)。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 7

(8)命令替换(echo for today is 'date')。


(9)文件名替换。
(10)程序执行。

1.3.2 输入命令
别名、函数、内建命令和磁盘上的可执行程序都是可执行的命令。别名是现存命令的缩
写,要依靠 C、TC、bash 和 Korn Shell 支持,它们都支持函数。所谓函数就是由一组组织在
一起的命令组成的一个例程。别名和函数定义在 Shell 内存里。内建命令是 Shell 内部的例程,
而可执行程序则存贮在磁盘上。Shell 使用 path 变量定位存储在磁盘上的可执行文件的位置,
并在命令执行以前创建子过程。这需要花费一点时间。当 Shell 准备好执行命令的时候,按
照如下的命令类型顺序执行:4
(1)别名。
(2)关键字。
(3)函数(bash) 。
(4)内建命令。
(5)可执行程序。
例如,如果你输入的命令是“xyz”,Shell 则首先检查它不是一个别名。如果不是,再检
查是不是一个内建命令或者函数,如果仍不是,那么就可肯定它是一个存储在磁盘上的可执
行程序。接着,Shell 就搜索该命令所在的目录。

1.4 Shell 和进程

1.4.1 什么是进程
进程是处在执行状态下,并可以用惟一的 PID(进程 ID)标识的程序。进程由核心控制
和管理。一个进程由可执行的程序、数据和堆栈、程序和指针、寄存器和其他程序运行所必
需的信息组成。当你登录时,标准 Shell(bash)5 就启动一个进程,称为登录 Shell(Login Shell) 。
它属于一个用组 PID 标识的进程组。当只有一个进程组控制终端时,它被称为在前台运行。
在 Linux 中,当你登录到系统时,Shell 控制终端会等待你输入命令,通常这时会启动另外一
个进程——xinit,以启动 X Windows 系统。当 X Windows 系统启动后,窗口管理器进程(twm、
fvmw 等)被执行,提供一个虚拟桌面。6 接着你可以从下拉菜单中启动其他进程,例如 xterm
(一个终端) 、xman(提供帮助手册)、emacs(一个文本编辑器) 。多个进程同时被 Linux kernel
监视和运行,且每个进程都分配有一些 CPU 时间片,这个过程通常是不引人注目的。

1.4.2 什么是系统调用
Shell 有启动(创建)其他进程的能力。事实上,当你在提示符下或者通过脚本输入命令

4.第 3 项和第 4 项是为 Bourne 和 Korn 保留的,C Shell 和 TC Shell 不支持第 3 项。


5.Linux 的默认 Shell 是 bash,即 Bourne Again Shell。
6.Linux 下有很多的窗口系统,包括 Gnome、KDE 和 X 等。

PDF created with pdfFactory Pro trial version www.pdffactory.com


8 第1章

的时候,Shell 就以寻找内建命令或者外部可执行程序来响应,接着安排命令运行。这是依靠
呼叫系统(system call)来完成的,称为系统调用。系统调用需要核心服务,这是进程访问硬
件的惟一方法。有很多系统调用允许建立、执行和终止进程(当 Shell 提供重新定向和管道、
命令替换、用户命令的时候,它还可以通过 kernel 提供其他的服务) 。在后面的章节中我们还
将讨论 Shell 利用系统调用产生新进程的问题。参见图 1.2。
显示提示符并读
入下一条命令

Shell 搜索
一条命令

判断该
命令是否是 是 执行该命令
内建命令

不是

派生一
个子进程 父 Shell
等待

判断该命令
是否是一个已编译的 是
可执行语句
kernel 把一个新的
程序装入内存,并
在子进程中执行它
不是

运行及终止新进程

判断该命令是 退出

否是脚本程序
的结尾

唤醒父 Shell
不是

图 1.2 Shell 及命令的执行过程

1.4.3 哪些进程正在运行
ps 命令。ps 有很多的参数选项,以不同的格式列出正在运行的进程清单。实例 1.2 显示
的是在用户的 Linux 系统上运行的进程列表(参考附录 A,能获得有关 ps 的更详细的用法)。

实例 1.2

$ ps au (Linux ps)
USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND
ellie 456 0.0 1.3 1268 840 1 S 13:23 0:00 –bash

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 9

ellie 476 0.0 1.0 1200 648 1 s 13:23 0:00 sh


/usr/XllR6/bin/sta
ellie 478 0.0 1.0 2028 676 1 S 13:23 0:00 xinit
/home/ellie/.xi
ellie 480 0.0 1.6 1852 1068 1 S 13:23 0:00 fvwm2
ellie 483 0.0 1.3 1660 856 1 S 13:23 0:00
/usr/XllR6/lib/Xll/fv
ellie 484 0.0 1.3 1696 868 1 S 13:23 0:00
/usr/XllR6/lib/Xll/fv
ellie 487 0.0 2.0 2348 1304 1 S 13:23 0:00 xclock –bg
#c0c0c0 –p
ellie 488 0.0 1.1 1620 724 1 S 13:23 0:00
/usr/XllR6/lib/Xll/fv
ellie 489 0.0 2.0 2364 1344 1 S 13:23 0:00 xload -
nolabel –bg gr
ellie 495 0.0 1.3 1272 848 p0 S 13:24 0:00 -bash
ellie 797 0.0 0.7 852 484 p0 R 14:03 0:00 ps au
root 457 0.0 0.4 724 296 2 S 13:23 0:00
/sbin/mingetty tty2
root 458 0.0 0.4 724 296 3 S 13:23 0:00
/sbin/mingetty tty3
root 459 0.0 0.4 724 296 4 S 13:23 0:00
/sbin/mingetty tty4
root 460 0.0 0.4 724 296 5 S 13:23 0:00
/sbin/mingetty tty5
root 461 0.0 0.4 724 296 6 S 13:23 0:00
/sbin/mingetty tty6
root 479 0.0 4.5 12092 2896 1 S 13:23 0:01 X :0
root 494 0.0 2.5 2768 1632 1 S 13:24 0:00 nxterm
-ls –sb -fn

pstree 命令。另一个查看正在运行的进程和子进程(child process)的方法是使用 Linux


下的 pstree 命令。pstree 按照树状显示所有的进程,root 进程是第一个运行的进程,称为 init。
如果具体指定了一个用户名,则该用户的进程就将放在树的根位置上。如果一个进程产生了
多个同名进程,pstree 就将直观地把各个分支用直角的树状图显示出来,进程后面的数字所
表示的是,同名进程被重复启动的次数。为了说明这个问题,请看实例 1.3,httpd 服务进程
启动了 10 个子进程(参考附录 A 能获得有关 pstree 的更详细的用法) 。

实例 1.3

pstree
init---4*[getty]
init-+-atd
|-bash---startx---xinit-+-X
| `-fvwm2-+-FvwmButtons
| |-FvwmPager
| `-FvwmTaskBar
|-cardmgr
|-crond
|-gpm
|-httpd---10*[httpd]
|-ifup-ppp---pppd---chat
|-inetd
|-kerneld
|-kflushd

PDF created with pdfFactory Pro trial version www.pdffactory.com


10 第1章

|-klogd
|-kswapd
|-lpd
|-2*[md_thread]
|-5*[mingetty]

|-nmbd
|-nxterm---bash---tcsh---pstree
|-portmap
|-sendmail
|-smbd
|-syslogd
|-update
|-xclock
`-xload

1.4.4 建立和终止进程的系统调用
fork 系统调用。fork 系统调用用于创建一个新进程。fork 系统调用建立的进程是原来进
程的副本(duplicate),新进程叫作子(child)进程,而原来的进程称为父(parent)进程。
在呼叫系统调用 fork 以后,子进程开始运行,它和父进程一起分享 CPU。子进程中有一个父
进程的环境拷贝,包括打开的文件、用户识别、当前工作目录和信号。
当输入一条命令后,Shell 便解析该命令行,并判断其第一个单词是内建命令还是可执行
程序。如果是内建命令则由 Shell 本身进行处理。如果是可执行程序,Shell 就呼叫 fork 系统
调用,建立一个自己的副本(参见图 1.3) 。它的子进程将搜索路径,找到命令,并建立关于
重新定向、管道、命令替换和后台进程的描述文件。当子进程的 Shell 运行的时候,父进程
暂时休眠(参见后面的 wait 系统调用) 。
wait 系统调用。当子进程处理细节的时候如重新定向、管道和后台进程等父进程被设计
为休眠状态(待机)。wait 系统调用使得父进程暂时挂起,直到它的某个子进程终止。如果
wait 调用成功,它将返回死去的子进程的 PID 和退出状态。如果父进程没有休眠而子进程退
出了,子进程将陷入“僵(zombie) ”状态(自动挂起) ,直到父进程成功调用 wait 或者父进
7
程退出。 如果父进程在子进程之前退出,init 就将负责处理所有处于僵状态的“孤”进程。
wait 系统调用不仅使父进程休眠,同时也保证进程的正确退出。

父进程 子进程

ENV ENV

0 标准输入端 0 标准输入端
1 标准输出端 1 标准输出端
2 标准错误端 2 标准错误端

图 1.3 fork 系统调用

7.只有重新启动系统才能删除僵进程。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 11

exec 系统调用。当你在终端输入一条命令,Shell 通常派生出一个新的进程:子进程。


就如前面提到的,子进程的 Shell 负责执行你输入终端的命令,这个过程是通过 exec 系统调
用完成的。注意,用户输入的命令必须是一个可执行的程序。Shell 搜索该新命令程序所在的
路径。如果找到,Shell 就以命令名为参数调用 exec 系统调用。kernel 把程序装入子进程 Shell
所在的内存位置,子进程被新的程序所覆盖。新的程序变成了子进程并开始执行。尽管新进
程有自己的局部变量,但是所有的环境变量、打开的文件、信号和当前工作目录都是从原进
程继承下来的。当程序执行完毕后,退出进程且父进程的 Shell 被唤醒过来。
exit 系统调用。调用 exit 系统调用可以在任何时候终止程序的执行。当一个子进程退出
时会发出一个信号(sigchild) ,并等待父进程接受它的退出状态。退出状态是一个介于 0 和
2558 之间的数字。0 表示子进程成功退出,非 0 状态则意味着退出发生了某种失败。
例如在命令行输入 ls 命令,父进程派生出一个子进程后进入休眠状态。子进程的 Shell
调用 exec 启动程序 ls。ls 在子进程的位置运行,继承了所有的环境变量、打开的文件、用户
信息和状态信息。当 ls 进程执行结束以后,退出子进程而唤醒父进程。提示符将出现在监视
器上并等待新的命令。如果你对命令是如何退出的感兴趣,我可以给你提个醒每个 Shell 都
有一个特别的内建变量用于保存最后一个命令终止时候的状态(在后面专门的章节中我们将
详细讲到这个问题) 。参见图 1.4 以便更直观地了解进程的建立和终止。

实例 1.4

(C and TC Shell)
1 > cp filex filey
> echo $status
0
2 > cp xyz
Usage: cp [-ip] f1 f2; or: cp [-ipr] f1 ... fn d2
> echo $status
1
(Tcsh, Bourne, korn, and Bash Shells)
3 $ cp filex filey
$ echo $?
0
$ cp xyz
Usage: cp [-ip] f1 f2; or: cp [-ipr] f1 ... fn d2
$ echo $?
1

说明
1 在 TC Shell 提示符(>)下后输入命令 cp(copy) 。该命令为 filex 建立了一个副本叫作 filey,然
后退出,出现提示符。tcsh 的 status 包含了最后一个可执行命令退出时候的状态。如果 status 是 0,
说明 cp 退出成功,如果非 0,则说明发生了某种失败。
2 在命令行输入 cp 命令的时候,由于没有输入源文件和目标文件名而发生错误。cp 把错误信息输
出到监视器上然后退出,退出的状态值是 1。这个值存储在 tcsh 的 status 变量中。任何非 0 值都
意味着程序的失败。
3 tcsh、Bourne、bash 和 Korn 的 Shell 处理 cp 命令的方式跟前两个例子中的 TC Shell 一样,所不同
的是 Bourne 和 Korn 把退出状态存储在变量?中,而不是 status 中。

8.如果程序被信号中断,就会返回一个 128+n 的值,其中 n 表示信号的编号。

PDF created with pdfFactory Pro trial version www.pdffactory.com


12 第1章

父 Shell 子 Shell 新程序

第一步 第二步 第三步

环境 环境 环境

0 标准输入 执行
0 标准输入 0 标准输入
(子 Shell
1 标准输出 1 标准输出 上的新程 1 标准输出
2 标准错误 2 标准错误 序) 2 标准错误

退出

图 1.4 fork、exec、wait 以及 exit 的系统调用(见下面的说明)

说明
1 父进程用 fork 系统调用创建自己的一个副本,该副本叫作子进程。
2 子进程是父进程的副本,但是有自己的 PID,且跟父进程一起分享 CPU。
3 kernel 把 grep 装入内存,并在子进程的位置上执行(exec)它。grep 从子 Shell 那里继承了打开的
文件和环境。
4 grep 退出,kernel 释放资源,父 Shell 被唤醒。

1.5 环境与继承
当你登录系统时,Shell 启动并从启动它的/bin/login 程序中继承了多个变量、I/O 流和进
程特征。同样,如果一个子 Shell 是 Login 或者某个父 Shell 产生的,子 Shell 就会从父 Shell
那里继承特定的特征。下列原因可能会启动子 Shell:后台处理、处理整组的命令以及执行脚
本。子 Shell 从父 Shell 那里继承环境,这里的环境包括进程的权限(谁拥有进程) 、工作目
录(work directory)、文件创建掩码、特殊变量、打开的文件和信号。

1.5.1 所有权
当你登录系统时,Shell 会对你进行身份认证。它有一个真用户 ID(UID) ,一个或者多
个组 ID(GID) ,一个有效用户 ID(EUID)和一个有效组 ID(EGID) 。EUID 和 EGID 的初
始值跟 UID 和 GID 是一样的。这些 ID 可以在 passwd 文件中找到,用于系统确认用户身份。
EUID 和 EGID 是用来在进程发生读、写和执行文件时候确认访问权限的。如果基础的 EUID
和文件所有权的 UID 是一致的,这个进程就有权访问这个文件。如果 EGID 和进程的真 GID
一致,那么该进程就拥有所有权组的特权。
在/etc/passwd 文件中可以找到 UID,称为真 UID,它是一个跟你的用户名直接相关的正
整数。真 UID 在 passwd 文件的第三个域里面。当你登录时,你的 Shell 文件就被分配真 UID,
所有这个登录 Shell 所引起的进程都继承相同的权限。 任何 UID 是 0 的进程所有权都属于 root
或者超级用户,有 root 的特权。真 GID 跟你的登录用户名所在的组直接相关,它在 passwd

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 13

文件的第四个域内。
EUID 和 EGID 可以被修改为分配给不同用户的不同数字。通过修改 EUID(或者
EGID9)可以改变进程的所有权,使之属于其他的用户。具有改变 EGID 和 EUID 功能的程
序是 setgid 和 setuid。/bin/passwd 程序是 setuid 赋予用户 root 权限的一个例子。setuid 通常
是系统安全漏洞的根源。Shell 使你能创建 setuid 脚本,同时 Shell 本身也可以是一个 setuid
的脚本。

1.5.2 文件创建掩码
当一个文件被建立的时候,它被赋予一套默认访问权限。这些权限由创建文件的程序决
定。子进程从父进程那里继承默认掩码。用户通过在命令行执行 umask 命令或者修改 Shell
的初始化文件可以改变 Shell 的掩码。umask 命令用于从现存的掩码中删除权限。
初始状态下,umask 的值是 000,默认的目录权限是 777(rwxrwxrwx) ,默认的文件权
限是 666(rw-rw-rw-)。在多数系统中,umask 被/bin/login 或者/etc/profile 程序设置为 022。
umask 的值是默认的目录和文件权限值需要减去的值,例如:
777(目录默认权限) 666(文件默认权限)
-022(umask 值) -022(umask 值)
-------- ------------
755 644

结果:drwxr-xr-x 结果:-rw-r—r—

在设置完 umask 值后,由这个进程建立的目录和文件都将被分配新的默认权限。在本例


子中,目录赋予所有者读、写和执行的权限,赋予本组读和执行的权限,而其他人被赋予读
和执行的权限。文件的所有者被赋予读和写的权限,本组及其他人被赋予读的权限。chmod
命令用来改变目录的权限。

1.5.3 改变所有权和权限

chmod 命令。chmod 命令用于改变目录和文件的权限。所有的 Linux 文件都有一系列控


制对于该文件的读、写和执行的操作的权限。每一个 Linux 文件都有一个所有者,并且只有
所有者或者超级用户才能改变文件或者目录的所有权。组中有很多的成员,文件的所有者可
以通过改变该文件的组权限,使得该组可以享受特殊的权限。若想查看一个文件的权限,可
以通过在命令行输入命令:
ls –l filename

文件的权限由 9 个位组成。前 3 位控制文件所有者的权限,中间 3 位控制所有者所在的


组的权限,最后 3 位控制其他人的权限。权限存储在文件信息节点的模式字段内。只有文件
的所有者才能改变文件的权限。10
表 1.1 列出了权限组合的八种情况。

9.setgid 的权限独立于系统,对一个目录使用 setgid 可以使得在该目录下的新创建文件的所有权跟该目录相同。另外,由


进程的 EGID 决定使用该文件的用户组。
10.EUID 系统调用的呼叫者必须跟文件所有者的 UID 一致,或者文件的所有者是超级用户。

PDF created with pdfFactory Pro trial version www.pdffactory.com


14 第1章

表 1.1

10 进制数字 2 进制数字 权限
0 000 none
1 001 --x
2 010 -w-
3 011 -wx
4 100 r--
5 101 r-x
6 110 rw-
7 111 rwx
注:r 表示读的权限,w 表示写的权限,x 表示执行的权限,u 表示用户(所有者),g 表示组,o 表示其他人,a 表示所有人。

实例 1.5

1 $ chmod 755 file


$ ls –1 file
-rwxr-xr-x 1 ellie 0 Mar 7 12:52 file
2 $ chmod g+w file
$ ls –1 file
-rwxrwxr-x 1 ellie 0 Mar 7 12:54 file
3 $ chmod go-rx file
$ ls -1 file
-rwx-w---- 1 ellie 0 Mar 7 12:56 file
4 $ chmod a=r file
$ ls -1 file
-r--r--r-- 1 ellie 0 Mar 7 12:59 file

说明
1 第一个参数是十进制的 755,赋予用户(所有者)rwx 的权限,赋予组 r 和 x 的权限,赋予其他人
x 的权限。
2 在 chmod 的符号方式下,w 的权限被加给了组。
3 在 chmod 的符号方式下,rx 的权限被从组和其他人的权限中除去。
4 在 chmod 的符号方式下,所有者、组和其他人的权限都被置为 r,=符使得所有的权限都被重置。

chown 命令。chown 改变文件和目录的所有者和组。在 Linux 下,只有超级用户和 root


可以改变文件的所有权。可以借助 help 参数来了解 choun 命令的详细用法,即在命令行下输
入 chown –help,参见实例 1.6。实例 1.7 说明的是如何使用 chown。

实例 1.6

(The Command Line)


# chown --help
Usage: chown [OPTION] ... OWNER[.[GROUP]] FILE...
or: chown [OPTION] ... .[GROUP] FILE...
Change the owner and/or group of each FILE to OWNER and/or GROUP.

-c, --changes be verbose whenever change occurs

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 15

-h, --no-dereference affect symbolic links instead of any


referenced file
(available only on systems with lchown
system call)
-f, --silent, --quiet suppress most error messages
-R, --recursive operate on files and directories recursively
-v, --verbose explain what is being done
--help display this help and exit
--version output version information and exit

Owner is unchanged if missing. Group is unchanged if missing, but


Changed to login group if implied by a period. A colon may replace
the period.

Report bugs to fileutils-bugs@gnu.ai.mit.edu

实例 1.7

(The Command Line)


1 $ ls –1 filetest
-rw-rw-r-- l ellie ellie 0 Jan 10 12:19 filetest
2 $ chown root filetest
chown: filetest: Operation not permitted
3 $ su root
Password:
4 # ls –1 filetest
-rw-rw-r-- l ellie ellie 0 Jan 10 12:19 filetest
5 # chown root filetest
6 # ls –1 filetest
-rw-rw-r-- l root ellie 0 Jan 10 12:19 filetest
7 # chown root:root filetest
8 # ls –1 filetest
-rw-rw-r-- l root root 0 Jan 10 12:19 filetest

说明
1 文件 filetest 的所有者和所有者所在组是 ellie。
2 chown 只在你是超级用户的情况下可用,也就是你必须是 root。
3 用 su 命令改变用户 ID 为 root。
4 文件 filetest 的所有者和所有者所在组是 ellie。
5 只有超级用户才可以改变文件和目录的所有权。文件 filetest 的所有权被 chown 命令改变为 root,
但是组所有权依然是 ellie。
6 ls 显示 root 已经取得文件 filetest 的所有权。
7 冒号(或者点号)表示所有者 root 要把组所有权改变为 root。组的名字在冒号的后面,这里不能
是空白。
8 文件 filetest 的所有人和组所有权都已经改变为 root。

1.5.4 工作目录
当你登录系统时,在文件系统内会分配一个工作目录给你,称为 home directory。工作目
录可以被 Shell 启动的进程所继承。
任何这个 Shell 启动的子进程可以改变它自己的工作目录,

PDF created with pdfFactory Pro trial version www.pdffactory.com


16 第1章

但是不能影响父进程的工作目录。
cd 命令用来改变工作目录,是 Shell 的内建命令。每一个 Shell 都有自己的 cd 命令。内建
命令作为 Shell 代码的一部分,在 Shell 进程内部执行。当运行内建命令的时候,Shell 不执行
fork 和 exec 系统调用。如果 Shell 派生了一个子进程,则在该子进程内部 cd 命令改变的只是子
Shell 的工作目录。当子 Shell 退出以后,父 Shell 的工作目录跟子 Shell 启动以前是一样的。

1.5.5 变量
Shell 可以定义两类变量:局部变量和环境变量。变量包含个性化 Shell 所需的信息,以及
其他进程为正确运行而引用的信息。局部变量是 Shell 私有的,它们在 Shell 进程内部创建,
但是不会传递给由该 Shell 产生的任何子进程。与之相反,环境变量由父进程传递给子进程,
再由子进程传递给孙进程……一直下去的。一些环境变量是从/bin/login 程序启动的登录 Shell
那里继承来的。而其他的则是在用户初始化文件中、脚本中或者命令行中创建的。如果在子
Shell 中给一个环境变量赋值,则该值无法传递回给其父 Shell 的。

实例 1.8

1 > cd /

2 > pwd
/

3 > bash

4 $ cd /home

5 $ pwd
/home

6 $ exit

7 > pwd
/

>

说明
1 >是 TC Shell 的提示符,cd 命令改变当前目录到/,cd 是 Shell 的内建命令。
2 用 pwd 命令显示当前工作目录/。
3 启动 bash Shell。
4 用 cd 命令改变当前工作目录为/home,$是 bash 提示符。
5 用 pwd 显示当前工作目录 /home。
6 退出 bash Shell,启动 TC Shell。
7 在 TC Shell 中当前目录是 /,每一个 Shell 都有自己版本的 cd 命令。

1.5.6 重新定向和管道
文件说明符。所有的 I/O,包括文件、管道和套接字都由 kernel 处理,这个处理机制称
为文件说明符,它是一个没有符号的整数。kernel 处理保存了一个文件说明符检索的表格,

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 17

在 kernel 打开文件和 I/O 数据流的时候参考使用。每一个进程都从其父进程那里继承了自


己的文件说明符表。首先是三个分配给终端的文件说明符,0、1 和 2。文件说明符 0 表示
标准输入(stdin),1 表示标准输出(stdout),2 表示标准错误(stderr)。当你打开一个文件,
下一个可能的文件说明符是 3,它表示一个新文件。如果所有的文件说明符都被使用了,
那么新的文件就无法打开。11
重新定向。当文件说明符被分配给一个非终端的时候,它就被称为 I/O 重新定向。Shell
通过关闭标准输出,并把标准文件说明符 1(终端)分配给文件,来把输出重新定向给文件
(参考图 1.5) 。通过关闭文件说明符 0,把它分配给文件,把标准输入重新定向(参考图 1.6) 。
Bourne Shell 和 Korn Shell 通过分配文件说明符 2 来处理错误(参考图 1.7)。相反,TC Shell
却使用复杂得多的过程来处理相同的事情(参考图 1.8) 。

父 Shell 子 Shell
125

标准输入 0 键盘
标准输出 1 temp 文件
标准错误 2 监视器
标准输入 键盘
标准输出 监视器
标准错误 监视器

标准输入 键盘
标准输出 temp 文件
标准错误
监视器

图 1.5 标准输出的重新定向

父 Shell 子 Shell
233

0 memo文件
1 监视器
标准输入 键盘 2 监视器
标准输出 监视器
标准错误 监视器

memo 文件
监视器
监视器

图 1.6 标准输出的重新定向

11.参考表 8.32 中关于 limit 和 ulimit 命令的讨论。

PDF created with pdfFactory Pro trial version www.pdffactory.com


18 第1章

父 Shell 子 Shell
235

0 键盘
1 监视器
标准输入 键盘 2 错误
标准输出 监视器
标准错误 监视器

键盘
监视器
错误

图 1.7 (Bourne、Bash 和 Korn Shell)的标准错误的重新定向

父 Shell 子 Shell 孙 Shell

237

0 键盘
1 监视器
键盘 键盘 2 错误

监视器 错误
监视器 错误

键盘
监视器
错误

图 1.8 (C 和 TC Shell)的标准错误的重新定向

实例 1.9

1 $ who > file


2 $ cat filel file2 >> file3
3 $ mail tom < file
4 $ find / -name file –print 2> errors
5 > ( find / -name file –print > /dev/tty) >& errors

说明
1 把 who 命令的输出由终端重新定向给文件(所有的 Shell 都以这样的方式重新定向输出) 。
2 把 cat 命令的输出(合并 file1 和 file2)追加到 file3 后面(所有的 Shell 都以这样的方式重新定向
并追加输出)。
3 把文件的输入重新定向给 mail 程序,File 文件的内容将被发送给用户 tom(所有的 Shell 都以这样

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 19

的方式重新定向输入) 。
4 所有 find 命令的错误都重新定向给 errors,并在终端上显示出来(Bourne、bash 及 Korn Shell 都是
以这样的方式重新定向错误)。
5 所有 find 命令的错误都重新定向给 errors,并在终端上显示出来(C 和 TC Shell 都是以这样的方
式重新定向错误) 。

管道。管道为进程之间的通信服务,它是把一个命令的输出作为另外一个命令输入的机
制。Shell 通过打开和关闭文件说明符来建立管道,但不是分配文件说明符而是通过 pipe 系
统调用建立和分配一个管道说明符。在父进程建立一个管道说明符后,它为管道中的每一个
命令启动一个子进程。通过操作管道说明符,一个进程向管道写,其他进程可以从管道读取。
管道只是 kernel 内的一块可以被两个进程同享的缓冲区,这样的好处是不再需要建立用于交
互的临时文件。在说明符建立以后,命令是同步执行的。一个命令的输出被送到缓冲区内,
当命令结束或者缓冲区被装满以后,管道另外一侧的命令从缓冲区内读取。kernel 负责同步
这些活动以保证一个进程在读或者写缓冲区的时候,其他的进程处于等待状态。
管道命令的语法是:
who | wc

如果没有管道,若想完成相同的事情,则需要三个步骤:
who > tempfile
wc tempfile
rm tempfile

通过管道,Shell 把 who 命令的输出作为 wc 命令的输入。换而言之,管道左边命令的输


出被写入管道,然后作为管道右边的命令的输入被右边的命令读出。在命令行的情况下,写
命令的标准输出是到屏幕,读命令则等待从文件、键盘或者管道读取输入。
pipe 系统调用用于建立管道。首先父进程呼叫 pipe 系统调用,而后该系统调用创建两个
管道说明符,一个用于向管道写,一个用于从管道读取。被分配管道说明符的文件是在系统
内核管理(kernel-managed)下,用于存储临时数据的 I/D 缓冲区,管道简化了建立临时文件
的麻烦。参考图 1.9 来了解管道的建立步骤。

父进程 父进程

环境 环境

0 标准输入端 0 标准输入端

1 标准输出端 1 标准输出端

2 标准错误端 2 标准错误端
3 管道读取(pipereader)管道
4 管道写入(pipewriter)管道

图 1.9 父进程呼叫 pipe 系统调用创建管道

PDF created with pdfFactory Pro trial version www.pdffactory.com


20 第1章

(1)父进程调用 pipe 系统调用,返回两个文件说明符:一个用于从管道中读取,一个


用于向管道中写。这两个文件说明符是文件说明符表格(file-descriptor(fd)table)中的后两
个可用说明符 fd3 和 fd4。

父进程 子进程 子进程

环境 环境 环境

0 标准输入端 0 标准输入端 0 标准输入端


1 标准输出端 1 标准输出端 1 标准输出端
2 标准错误端 2 标准错误端 2 标准错误端
3 管道读取(pipereader)管道 3 管道读取(pipereader)管道 3 管道读取(pipereader)管道
4 管道写入(pipewriter)管道 4 管道写入(pipewriter)管道 4 管道写入(pipewriter)管道

图 1.10 父进程为管道两端的命令分别派生两个子进程

(2)无论 who 或者 wc 命令,其父进程都派生出一个子进程。且两个子进程都从父进程


那里取得一个打开文件说明符的拷贝。

第一个 第一个
子进程 子进程

环境 环境
dup 系统
0 标准输入端 0 标准输入端
调用后
1 关闭 1 关闭

2 标准错误端 2 标准错误端

3 管道读取(pipereader)管道 3 关闭

4 管道写入(pipewriter)管道 4 关闭

图 1.11 第一个子进程准备向管道中写

(3)第一个子进程关闭了它的标准输出。然后复制文件说明符 4(调用 dup 系统调用) ,


文件说明符 4 表示向管道写。dup 系统调用复制 fd4,并把拷贝复制到可用说明符表格的最下
面一项。在完成拷贝后,dup 系统调用关闭 fd4,而子进程关闭无用的 fd3。这个子进程就完
成了标准输出重新向管道定向。
(4)子进程 2 关闭了它的标准输入。它复制 fd3(通过 dup 系统调用),fd3 表示从管道
中读取。用 dup,创建 fd3 的拷贝并分配给号码最小的可用的说明符。因为 fd0 已被关闭,所
以它是号码最小的可用的说明符。当 dup 关闭 fd3 而子进程关闭 fd4 后,标准输入就重新定
向为管道。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 21

第二个 第二个
子进程 子进程

环境 环境

0 关闭 0 管道读取管道 dup 系统
调用后
1 标准输出端 1 标准输出端
2 标准错误端 2 标准错误端

3 管道读取(pipereader)管道 3 关闭

4 管道写入(pipewriter)管道 4 关闭

图 1.12 第二个子进程准备从管道中读

子进程 子进程

环境 环境

写入端 0 标准输入端 0 管道读取(pipereader)管道 读取端


1 管道写入(pipewriter)管道 1 标准输出端
2 标准错误端 2 标准错误端
3 关闭 3 关闭

4 关闭 4 关闭

图 1.13 who 命令的输出被发送给 wc 命令做为输入

(5)命令 who 在子进程 1 内部执行,wc 在子进程 2 内部执行。who 的输出进入管道并


等待管道另外一端的 wc 读取。管道中的最后一个命令(wc)的输出显示在屏幕上。

1.5.7 Shell 与信号

当信号发送一个信息(message)给进程时通常会导致进程终止。这种信号一般都是些
意外事件,例如挂起、电源掉电或者是程序错误,例如 0 引起的非法除数和无效的内存地
址。信号也可以通过特定的键传递给进程。例如,你可以通过 Break、Delete、Quit 和 Stop
发送信号给进程,共用同一个终端的所有进程都受到发送信号的影响。你可以 kill 命令取
消一个进程。默认情况下,大多数的信号都会导致程序的终止。每一个进程都以特定的行
为响应特定的信号:
(1)忽略信号。
(2)停止进程。
(3)继续进程。

PDF created with pdfFactory Pro trial version www.pdffactory.com


22 第1章

(4)由专门函数处理相应的信号。
Bourne、bash 和 Korn Shell 允许你按照以下方式处理进入程序的信号、忽略信号或者为
特定的信号指定特定的行为,以及重置信号的默认行为。C 和 TC Shell 被限制只能处理中断
字符^C(Control-C)。
表 1.2 显示的是进程可以使用的标准信号。
表 1.2 标准信号

Number Name 描述 对进程的作用

0 EXIT shell exits termination

1 SIGHUP terminal has disconnected termination

2 SIGINT user presses Control-C termination

3 SIGQUIT user presses Control-\ termination

4 SIGILL illegal hardware instruction program error

5 SIGTRAP produced by debugger program error

8 SIGFPE arithmetic error; e.g., division by zero program error

9 SIGKILL cannot be caught or ignored termination

10 SIGUSR1 application-defined signal for user

11 SIGSEGV invalid memory references program error

12 SIGUSR2 application-defined signal for user

13 SIGPIPE broken pipe connection operator error

14 SIGALRM time-out alarm sent

15 SIGTERM termination of a program termination

17 SIGCHLD child process has stopped or died ignored

18 SIGCONT starts a stopped job; can’t be handled or ignored continue if stopped

19 SIGSTOP stops a job; can’t be handled or ignored stops the process

20 SIGSTP interactive stop; user presses Control-z stops the process

21 SIGTTIN a background job is trying to read from the controlling terminal stops the process

22 SIGTTOU a background job is trying to write to the controlling terminal stops the process

1.6 从脚本执行命令
当把 Shell 用作一种编程语言时,命令和 Shell 控制结构在文本编辑器中被写入文件,
这个文件称为脚本。脚本的命令被 Shell 逐行读取并执行。这些程序是被解释而不是被编译
的。由于编译程序必须把程序翻译成为机器语言才能执行,所以 Shell 程序通常执行得比较

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 23

慢,但是它们更容易写也更适合简单的任务。Shell 编程可以在命令行下以交互方式写入脚
本,这种快捷的方式比较适合简单的任务。然而对于复杂的任务来说,文本编辑器则更为
适合(除非你的输入水平很高)。下面的脚本可以被任何 Shell 执行并输出相同的结果。图
1.14 说明了称为“doit”的脚本是如何建立的,以及它是如何适应已经存在的 Linux 程序/
实用工具/命令的。

Shell

图 1.14 创建一个 Shell 脚本

说明
1 打开一个你喜欢的文本编辑器,输入一系列命令。每个命令一行。在第一行#!后面输入你希望使
用的 Shell 和路径。这个叫作 doit 的脚本将被 TC Shell 执行。
2 保存文件并打开执行权限,你就可以执行它了。
3 像其他 Linux 命令一样执行这个文件。

1.6.1 脚本实例:比较 Shell


乍看起来,下面的程序都非常简单。它们完成相同的事情,且只是语法不同。当使用这
些 Shell 一段时间后,你就会逐渐适应这些不同,并且对于各个 Shell 有了自己的观点。在附
录 B 中有 Bourne、bash、C、TC 和 Korn Shell 不同点的详细比较。注意:C 和 TC Shell 语法
完全一样,bash 和 Korn Shell 在语法和命令上只有很小的不同,Bourne Shell 虽然在语法上有
很大的不同,但是它的脚本可以在 bash 和 Korn Shell 下执行。
下面的脚本给一系列用户发送信息,邀请他们参加一个 Party。Party 的时间和地点被存
入变量,被邀请的人是从一个叫作 guests 的文件中选出来的。食物清单保存在一个单词列表
里,每一个人都被要求从清单中选择一种食物。如果选择同样食物的用户多于该食物的项目
数量,清单就被重置,每一个用户就被要求重新选择另外一种食物。惟一没有被邀请的用户
是 root。

PDF created with pdfFactory Pro trial version www.pdffactory.com


24 第1章

1.6.2 TC Shell 脚本
实例 1.10

1 #!/bin/tcsh -f
# TC shell
2 # The Party Program—-Invitations to friends from the "guest" file
3 set guestfile = ~/shell/guests
4 if ( ! –e "$guestfile" ) then
echo "$guestfile:t non-existent"
exit l
endif
5 setenv PLACE "Sarotini's"
@ Time = `date +%H` + 1
set food = ( cheese crackers shrimp drinks "hot dogs" sandwiches)
6 foreach person ( `cat $guestfile` )
if ( $person =~ root ) continue
# Start of here document
7 mail –v –s "Party" $person << FINIS
Hi ${person}! Please join me at $PLACE for a party!
Meet me at $Time o'clock.
I'll bring the ice cream. Would you please bring $food[1] and
anything else you would like to eat? Let me know if you can't
make it. Hope to see you soon.
Your pal,
ellie@`hostname` # or `uname –n`
FINIS
8 shift food
if ( $#food == 0 ) then
set food = ( cheese crackers shrimp drinks "hot dogs"
sandwiches )
endif
9 end

echo "Bye..."

说明
1 这一行告诉 kernel 你要运行一个 TC Shell 脚本。-f 参数表示快速启动。它在对内核说, “不要执
行.tcshrc 文件”,通常没有这个参数的时候,系统在启动每一个脚本之前都会先运行初始化文件。
2 这一行是注释,会被 Shell 忽略,但是对于想要理解你的程序的人来说却是很重要的。
3 文件 guests 的完整路径被赋值给变量 guestfile。
4 这行的意思是如果文件 guests 不存在的话,就在屏幕上打印“guests nonexistent” ,然后把退出状
态设置为 1。以表示发生错误,最后退出。
5 设置表示地点、时间和可以取用的食品清单的变量。PLACE 是一个环境变量。Time 是一个局部
变量。@符号告诉 TC Shell 采用内部算法,即从 date 命令中提取小时后,在 Time 变量中加 1。将
Time 变量名的 T 大写,是为了跟 TC shell 中的保留字 time 区分。
6 每一个人(除了 root 以外)都会收到一封 mail,邀请他们参加在指定地点和时间举行的 Party,他
们可以在食品清单中选择一种喜欢的食品。
7 mail 信息在 here document 中建立。所有在 FINIS 和最后的 FINIS 之间的文本都将被发送给 mail
程序。foreach 循环从头开始,以每一个客人的名字为步进,直到关键字 end 为止,结束循环。
8 在一条信息发送给客人以后,食品清单信息就会改变(减少一项) ,以保证下一位客人拿到是新的

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 25

可供其选择的食品清单。如果食品清单上的项目数量比客人的数量少,食品清单就要重置,以保
证每一个客人都有食品。
9 循环结束。

1.6.3 C Shell 脚本
实例 1.11

1 #!/bin/csh -f
# Standard Berkeley C Shell
2 # The Party Program—-Invitations to friends from the "guest" file
3 set guestfile = ~/shell/guests
4 if ( ! –e "$guestfile" ) then
echo "$guestfile:t non-existent"
exit 1
endif
5 setenv PLACE "Sarotini's"
@ Time = 'date +%H' + l
set food = ( cheese crackers shrimp drinks "hot dogs" sandwiches )
6 foreach person ( 'cat $guestfile' )
if ( $person =~ root ) continue
# Start of here document
7 mail –v –s "Party" $person << FINIS
Hi ${person}! Please join me at $PLACE for a party!
Meet me at $Time o'clock.
I'll bring the ice cream. Would you please bring $food[1] and
anything else you would like to eat? Let me know if you can't
make it. Hope to see you soon.
Your pal,
ellie@'hostname' # or 'uname –n'
FINIS
8 shift food
if ( $#food == 0 ) then
set food = ( cheese crackers shrimp drinks "hot dogs"
sandwiches)
endif
9 end

echo "Bye..."

说明
1 这一行告诉 kernel 你要运行一个 C Shell 脚本。 -f 参数表示快速启动。它在对内核说,
“不要执
行 .cshrc 文件”
,通常没有这个参数的时候,系统在启动每一个脚本之前都会先运行初始化文件。
如果你仔细观察会发现,这是该脚本跟上个例子仅有的不同的一行。其他部分在前面已经解释了。

1.6.4 Bourne Shell 脚本


实例 1.12

1 #!/bin/bash
# Gnu bash versions 2.x

PDF created with pdfFactory Pro trial version www.pdffactory.com


26 第1章

2 # The Party Program—-Invitations to friends from the


# "guest" file
3 guestfile=~/shell/guests
4 if [[ ! –e "$guestfile" ]]
then
printf "${guestfile##*/} non-existent"
exit 1
fi
5 export PLACE="Sarotini's"
(( Time=$(date +%H) + 1 ))
set cheese crackers shrimp drinks "hot dogs" sandwiches
6 for person in $(cat $guestfile)
do
if [[ $person = root ]]
then
continue
else
# Start of here document
7 mail –v –s "Party" $person <<- FINIS
Hi ${person}! Please join me at $PLACE for a party!
Meet me at $Time o'clock.
I'll bring the ice cream. Would you please bring $1
and anything else you would like to eat? Let me know
if you can't make it.
Hope to see you soon.
Your pal,
ellie@$(hostname)
FINIS
8 shift
if (( $# == 0 ))
then
set cheese crackers shrimp drinks "hot dogs" sandwiches
fi
fi
9 done
printf "Bye..."

说明
1 这行是告诉 kernel 你要运行的是一个 bash Shell(Bourne Again)脚本。任何 2.X 以前的版本都
不支持本脚本中的语法。旧版本的 bash 跟标准的 Bourne Shell 功能类似。所有的版本都向后兼
容。
2 这一行是注释,会被 Shell 忽略,但是对于想要理解你的程序的人来说却是很重要的。
3 文件 guests 的完整路径被赋值给变量 guestfile。
4 这行的意思是如果文件 guests 不存在的话,就在屏幕上打印“guests nonexistent”并从该脚中退
出。
5 设置表示地点、时间的变量。食品清单变量用 set 命令赋值。
6 每一个人,除了 root 以外,都会收到一封 mail,邀请他们参加在指定地点和时间举行的 Party,他
们可以在食品清单中选择一种喜欢的食品。
7 mail 信息发送以后,内容将保存在 here document 内。
8 当把一条信息发送给客人以后,食品清单信息就会改变(减少一项) ,以保证下一位客人拿到的是
新的可供其选择的食品清单。如果食品清单上的项目数量比客人的数量少,食品清单就要重置,
以保证每一个客人都有食品。
9 循环结束。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 27

1.6.5 Bourne Shell 脚本


实例 1.13

1 #!/bin/sh
# Standard AT&T Bourne Shell
2 # The Party Program—-Invitations to friends from the
# "guest " file
3 guestfile=/home/ellie/shell/guests
4 if [ ! –f "$guestfile" ]
then
echo "'basename $guestfile' non-existent"
exit 1
fi
5 PLACE="Sarotini's"
export PLACE
Time='date +%H'
Time='expr $Time + 1'
set cheese crackers shrimp drinks "hot dogs" sandwiches
6 for person in 'cat $guestfile'
do
if [ $person = root ]
then
continue
else
# Start of here document
7 mail –v –s "Party" $person <<- FINIS
Hi $person! Please join me at $PLACE for a party!
Meet me at $Time o'clock.
I'11 bring the ice cream.Would you please bring $1
and anything else you would like to eat? Let me know
if you can't make it.
Hope to see you soon.
Your pal,
ellie@'hostname'
FINIS
8 shift
if [ $# -eq 0 ]
then
set cheese crackers shrimp drinks "hot dogs" sandwiches
fi
fi
9 done
echo "Bye..."

说明
1 这行是告诉 kernel 你要运行的是一个 sh Shell(Bourne)脚本,
它是跟 UNIX 系统一起发布的 Bourne
Shell。这些 UNIX 系统包括:Solaris 和 HP-UX 等等。最新的 sh 版本跟 ATT 及 SVR4 一起发行。
如果运行在 Linux 上且 Shell 是 bash,则这个脚本也可以正常运行。
2 这一行是注释,会被 Shell 忽略,但是对于想要理解你的程序的人来说却是很重要的。

PDF created with pdfFactory Pro trial version www.pdffactory.com


28 第1章

3 文件 guests 的完整路径被赋值给变量 guestfile,在 Bourne Shell 中扩展 Tilde 是不合法的(参见其


他脚本) 。
4 这行的意思是如果文件 guests 不存在的话,就在屏幕上打印“guests nonexistent”并从脚本中退
出。
5 设置表示地点、时间的变量。食品清单变量用 set 命令赋值。
6 每一个人,除了 root 以外,都会收到一封 mail,邀请他们参加在指定地点和时间举行的 Party,他
们可以在食品清单中选择一种喜欢的食品。
7 mail 信息发送以后,内容将保存在 here document 内。
8 当把一条信息发送给客人以后,食品清单信息就会改变(减少一项) ,以保证下一位客人拿到的是
新的可供他选择的食品清单。如果食品清单上的项目数量比客人的数量少,食品清单就要重置,
以保证每一个客人都有食品。
9 循环结束。

1.6.6 Korn Shell 脚本


实例 1.14

1 #!/bin/ksh
# AT&T Korn Shell
2 # The Party Program--Invitations to friends from the
# "guest" file
# AT&T Korn Shell (1988)
3 guestfile=~/shell/guests
4 if [[ ! –a "$guestfile" ]]
then
print "${guestfile##*/} non-existent"
exit 1
fi
5 export PLACE="Sarotini's"
(( Time=$(date +%H) + 1 ))
set cheese crackers shrimp drinks "hot dogs" sandwiches
6 for person in $(< $guestfile)
do
if [[ $person = root ]]
then
continue
else
# Start of here document
7 mail –v –s "Party" $person <<- FINIS
Hi ${person}! Please join me at $PLACE for a party!
Meet me at $Time o'clock.
I'11 bring the ice cream. Would you please bring $1
and anyhing else you would like to eat? Let me know
if you can't make it.
Hope to see you soon.
Your pal,
ellie@$(hostname)
FINIS
8 shift
if (( $# == 0 ))

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux Shell 介绍 29

then
set cheese crackers shrimp drinks "hot dogs" sandwiches
fi
fi
9 done
print "Bye..."

说明
1 这行是告诉 kernel 你要运行的是一个 Korn Shell 脚本。
2 这一行是注释,会被 Shell 忽略,但是对于想要理解你的程序的人来说却是很重要。
3 文件 guests 的完整路径被赋值给变量 guestfile。
4 这行的意思是:如果文件 guests 不存在的话,就在屏幕上打印“guests nonexistent”并从脚本中退
出。参数选项符-a 是 Korn Shell 中用于检测文件是否存在的选项。
5 设置表示地点、时间的变量。食品清单变量用 set 命令赋值。
6 每一个人,除了 root 以外,都会收到一封 mail,邀请他们参加在指定地点和时间举行的 Party,他
们可以在食品清单中选择一种喜欢的食品。
7 mail 信息发送以后,内容将保存在 here document 内。
8 当把一条信息发送给客人以后,食品清单信息就会改变(减少一项) ,以保证下一位客人拿到的是
新的可供他选择的食品清单。如果食品清单上的项目数量比客人的数量少,食品清单就要重置,
以保证每一个客人都有食品。
9 循环结束。

PDF created with pdfFactory Pro trial version www.pdffactory.com


第2章

Linux 工具箱
就像木匠需要合手的工具一样,Shell 程序员也需要一些合手的工具(也称为实用程序)
来编写实用及高效的脚本。有成百的 Linux 工具可供我们使用,其中很多是日常必备的,例
如 ls、pwd、who 和 vi。这些实用程序是由自由软件基金会提供的,并置于 GPL 协议保护下
的,它们通常都是 UNIX 同名软件的加强版本。本章将主要讨论的三个实用程序是 grep、sed
和 gawk。1在了解这三个程序的强大功能以前,首先要了解它们的正则表达式和正则表达式
元字符集(metacharacters)。

2.1 正则表达式(regular expression)

2.1.1 定义和例子
对于熟悉正则表达式和正则表达式元字符集的人来说,这个部分可以略过不看。但是这
些基本知识对于理解 grep、sed 和 gawk 是如何被用来显示和操纵数据是非常关键的。
什么是正则表达式?正则表达式 2 只是一个字符模板,用来在搜索中匹配相同的字符。
在大多数程序中,正则表达式是括在正斜杠中间的。例如,/love/就是一个以正斜杠为分隔
符的正则表达式,其中的模板 love 将用在搜索所有行中与它匹配的字符。更为有趣是正则
表达式可以被特殊的元字符控制。让我们通过下面的例子来进一步理解这个概念。假设你工
作在 vi 编辑器的环境中,正准备给朋友发送一封 E-mail。它看起来应该是这样的:
% vi letter
---------------------------------------------------------------------
Hi tom,
I think I failed my anatomy test yesterday. I had a terrible
stomach ache. I ate too many fried green tomatoes.
Anyway, Tom, I need your help. I'd like to make the test up
tomorrow, but don't know where to begin studying. Do you
think you could help me? After work, about 7 PM, come to
my place and I'll treat you to pizza in return for your help.

1.在本书附录 A 的实用程序列表中可以找到其他的实用程序。
2.如果在你收到的错误信息中包含 RE 的字样,就说明程序中使用的正则表达式存在错误。

PDF created with pdfFactory Pro trial version www.pdffactory.com


32 第2章

Thanks.
Your pal ,
guy@phantom
~
~
~
~
---------------------------------------------------------------------

假设你发觉 Tom 并没有参加考试,而是 David 参加的考试。同时还发现,在贺信中 Tom


的 T 用的是小写。于是你决定做一个全文替换,把 tom 替换成 David:
% vi letter
---------------------------------------------------------------------
Hi David,
I think I failed my anaDavidy test yeserday. I had a terrible
sDavidachache. I think I ate too many fried green Davidatoes.
Anyway, Tom, I need your help. I'd like to make the test up
Davidorrow, but don't know where to begin studying. Do you
think you could help me? After work, about 7 PM, come to
my place and I'll treat you to pizza in return for your help.
Thanks.
Your pal,
guy@phanDavid
~
~
~

--> :1,$s/tom/David/g
---------------------------------------------------------------------

在搜索字符串 tom 时用的就是正则表达式,替换字符串是 David。vi 命令的意思是“从


第一行到文件的结束,把所有找到的字符串 tom 替换为 David” 。这几乎就是你所想要的了,
但是还有一处没有替换,因为你要求替换的只是 tom,但不是 Tom。该怎么办?
正则表达式的元字符是一些特殊的字符,它们允许你以某种方式界定一个模板来控制什
么样的替换将发生。有的元字符锚定一个单词在行首或者行尾。有的元字符允许你指定一个
字符范围或者一些字符,来找到大写字母、小写字母、数字或者非数字等等。例如。当你想
把 tom 或者 Tom 替换成 David 时,下面的 vi 命令将完成这项工作:
:1,$s/\<[Tt]om\>/David/g

这行命令的意思是“从文件的第一行到文件的最后一行(1,$)替换(s)单词 Tom 或者
tom 为 David”。g(global)标志表示这个命令对于全文有效。正则表达式元字符\<和>\分别
界定单词的开始和结束。一对括号[Tt]表示只要匹配括号中的一个字母,在这个例子中,不
论是 T 还是 t。这里出现的五个 Linux/UNIX 模板匹配实用功能可以识别的元字符,它们扩
展了程序编写中可用的元字符集。

2.1.2 正则表达式的元字符集
有两套正则表达式字符集,一套是基本元字符集,另一套是扩展元字符集。另外,

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux 工具箱 33

POSIX3 标准还提供了一套元字符集。表 2.1 提供了可以在所有版本的 vi、ex、grep、egrep、


sed 和 gawk 中使用的基本元字符集,其他元字符将等到它们在实用程序中出现的时候再
详细讨论。
表 2.1 正规表达式的元字符集

元字符 功能 例子 匹配什么
^ 锚定行的开始 /^love/ 匹配所有以 love 开头的行
$ 锚定行的结束 /love$/ 匹配所有以 love 结束的行
. 匹配一个字符 /l..e/ 匹配所有这样的行,这些行包含这样的字符:
第一个字符是 l,紧跟着两个字符,然后是 e
* 代表 0 个或者多个先前字 /*love/ 匹配所有这样的行,有 0 个或者多个空格,
符 空格后跟着 love
[] 匹配字符组中的一个字符 /[Ll]ove/ 匹配所有包含 love 或者 Love 的行
[x-y] 匹配以字符范围组成的组 /[A-Z]ove/ 匹配所有这样的行,这些行包含如下的字符,
中的一个字符 第一个字符是从 A 到 Z 中间的一个,后面跟
着 ove
[^] 匹配一个不在范围内的字 /[^A-Z]ove/ 匹配所有这样的行,这些行包含如下的字符,
符 第一个字符不是从 A 到 Z 中间的一个,后面
跟着 ove
\ 用来转义一个元字符 /love\./ 匹配所有这样的行,这些行包含如下的字符:
love 后面跟着一个点。通常点是表示任何字
符的通配符
许多使用 RE 元字符集的 UNIX 程序都支持附加元字符集。
\< 锚定单词的开始 /\<love/ 匹配所有这样的行,这些行包含以 love 开头
的单词(vi 和 grep 支持这个功能)
\> 锚定单词的结束 /love\>/ 匹配所有这样的行,这些行包含以 love 结束
的单词(vi 和 grep 支持这个功能)
\(..\) 标记后面用到的匹配 /\(love\)able\1rs/ 最多可以使用 9 个标签。第一个标签是模板
字符(传) 最左边的部分。在本例子中,模板 love 保存
为标签 1,后面的\1 指的就是 Love;本例子
搜索的是这样的行,这些行包含这样的字符,
在 Loveable 后面跟着 lovers
x\{m\}or m 次复制字符 x o\{5,10\} 匹配所有这样的行,包含的 o 的数量在 5 到
x\{m,\}or 至少 m 次复制字符 x 10 个之间。(vi 和 grep 支持这个功能)
x\{m,n\} 至少 m 次,至多 n 次
复制字 xa

a. 各种版本的 UNIX 以及模板匹配(pattern-mactching)实用程序并不都是可信赖的,通常我们使用 vi 和 grep。

假设你熟悉 vi 是怎样工作的,每一个元字符都在 vi 搜索字符串的术语中有详细描述。

3.POSIX 是 Portable Operating System Interface for Computer Eaviron ment 的缩写,意思是计算机环境的可移植操作系统界面。

PDF created with pdfFactory Pro trial version www.pdffactory.com


34 第2章

在下面的例子中,被加重的字符就是 vi 将找到的匹配字符。

实例 2.1

(A Simple Regular Expression Search)


% vi picnic
------------------------------------------------------------------
I had a lovely time on our little picnic.
Lovers were all around us. It is springtime. Oh
Love, how much I adore you. Do you know
the extent of my love? Oh, by the way, I think
I lost my gloves somewhere out in that field of
clover. Did you see them? I can only hope love
is forever. I live for you. It's hard to get back in the
groove.
~
~
~
/love/
------------------------------------------------------------------

说明
正则表达式是 love。模板 Love 找到 love 本身外,还搜索到了一些部分匹配的单词,例如 lovely、
gloves 和 clover。

实例 2.2

(The Beginning of Line Anchor (^))


% vi picnic
---------------------------------------------------------------------
I had a lovely time on our little picnic.
Lovers were all around us. It is springtime. Oh
love, how much I adore you. Do you know
the extent of my love? Oh, by the way, I think
I lost my gloves somewhere out in that field of
clover. Did you see them? I can only hope love
is forever. I live for you. It's hard to get back in the
groove.
~
~
~
/^love/
---------------------------------------------------------------------

说明
^符号用来锚定行的开始。vi 将寻找这样的行,行的最左边匹配正则表达式 love。也就是说,Love
必须是这行最开始的字符,即使在前面多一个空格也不行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux 工具箱 35

实例 2.3

(The End of Line Anchor ($))


% vi picnic
---------------------------------------------------------------------
I had a lovely time on our little picnic.
Lovers were all around us. It is springtime. Oh
love, how much I adore you. Do you know
the extent of my love? Oh, by the way, I think
I lost my gloves somewhere out in that field of
clover. Did you see them? I can only hope love
is forever. I live for you. It's hard to get back in the
groove.
~
~
~
/love$/
---------------------------------------------------------------------

说明
美元符号用来锚定行的末尾。vi 将只找到这样的行,行的末尾匹配正则表达式 Love。也就是说
love 是这行最后的字符,在 Love 后面就是新的一行。

实例 2.4

(Any Single Character ( . ))


% vi picnic
---------------------------------------------------------------------
I had a lovely time on our little picnic.
Lovers were all around us. It is springtime. Oh
love, how much I adore you. Do you know
the extent of my love? Oh, by the way, I think
I lost my gloves somewhere out in that field of
clover. Did you see them? I can only hope love
is forever. I live for you. It's hard to get back in the
groove.
~
~
~
/1.ve/
---------------------------------------------------------------------

说明
除了不能匹配整个新行外,点可以匹配任何单个字符。Vi 将只找到这样的行,行包含如下的字
符:第一个字符是 l,紧跟着一个单个字符,然后紧跟的是 ve。在本例子中,找到的结果是由 live 和
love 组合成的。

实例 2.5

(Zero or More of the Preceding Character ( * ))


% vi picnic
---------------------------------------------------------------------

PDF created with pdfFactory Pro trial version www.pdffactory.com


36 第2章

I had a lovely time on our little picnic.


Lovers were all around us. It is springtime. Oh
Love, how much I adore you. Do you know
the extent of my love? Oh, by the way, I think
I lost my gloves somewhere out in that field of
clover. Did you see them? I can only hope love
is forever. I live for you. It's hard to get back in the
groove.
~
~
~
/o*ve/
---------------------------------------------------------------------

说明
星号匹配 0 个或者多个先前字符 4。它与紧挨着它的前面的字符粘合在一起,控制前面的字符。
在本例子中,星号粘合的是字母 o,它匹配一个 o 或者多个 o,甚至根本没有 o。vi 搜寻的是这样的
行,这些行包含这样的字符: 以 0 个或者多个 o 开头,
后面紧跟着 v 和 e。最终找到的包括 love、
loooove
及 lve 等等。

实例 2.6

(A Set of Characters ( [ ] ))
% vi picnic
---------------------------------------------------------------------
I had a lovely time on our little picnic.
Lovers were all around us. It is springtime. Oh
love, how much I adore you. Do you know
the extent of my love? Oh, by the way, I think
I lost my gloves somewhere out in that field of
clover. Did you see them? I can only hope love
is forever. I live for you. It's hard to get back in the
groove
~
~
~
/[L1]ove/
---------------------------------------------------------------------

说明
[ ]表示匹配括号字符集中的一个字符即可。在本例子中,vi 将寻找包含 Love 或者 love 的行。

实例 2.7

(A Range of Characters ( [ - ] ))
% vi picnic
---------------------------------------------------------------------
I had a lovely time on our little picnic.
Lovers were all around us. It is springtime. Oh

4.不要跟通配符相互混淆。它们是完全不同的。Shell 的通配符是代表 0 个或者多个任意字符,而正则表达式的星号匹配 0


个或者多个先前字符。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux 工具箱 37

love,how much I adore you. Do you know


the extent of my love? Oh, by the way, I think
I lost my gloves somewhere out in that field of
clover. Did you see them? I can only hope love
is forever. I live for you. It's hard to get back in the
groove.
~
~
~
/ove[a-z]/
---------------------------------------------------------------------

说明
在括号内的两端有字符的破折号匹配的是,破折号两端字符界定范围内的任何一个字符。vi 将
搜索一个正则表达式,这个表达式是 ove 后面紧跟着一个字符,这个字符的 ASCII 码必须在 a 和 z
的 ASCII 码的范围之间。因为这是一个 ASCII 码范围,所以不能把这个范围倒置为[z-a]。

实例 2.8

(Not One of the Characters in the Set ( [^] ))


% vi picnic
---------------------------------------------------------------------
I had a lovely time on our little picnic.
Lovers were all around us. It is springtime. Oh
love, how much I adore you. Do you know
the extent of my love? Oh, by the way, I think
I lost my gloves somewhere out in that field of
clover. Did you see them? I can only hope love
is forever. I live for you. It's hard to get back in the
groove.
~
~
~
/ove[^a-zA-Z0-9]/
---------------------------------------------------------------------

说明
括号内的^表示的是“非”的含义。vi 将搜索一个正则表达式,这个表达式包括 ove,且后面紧
跟一个字符,该字符的 ASCII 码既不能在 a 到 z 的范围内,也不能在 A 到 Z 的范围内,此外,还不
能够在 0 到 9 的范围内。例如,在 ove 后面跟一个逗号、空格或者点等等都是符合要求的,因为它
们不在上述 ASCII 码范围内。

2.2 正则表达式元字符的组合
我们已经介绍了基本正则表达式的元字符的使用方法,这些元字符可以组合成更为复杂
的表达式。正则表达式里斜杠中的字符都是需要搜索的字符串,搜索的范围都是文本文件的
每一行。

实例 2.9

注意:行号不属于文本内容,各行的竖线标志着文本的左右边界。

PDF created with pdfFactory Pro trial version www.pdffactory.com


38 第2章

------------------------------------------------------------------
1 |Christian Scott lives here and will put on a Christmas party.|
2 |There are around 30 to 35 people invited.|
3 |They are: |
4 | Tom|
5 |Dan|
6 | Rhonda Savage|
7 |Nicky and Kimberly.|
8 |Steve, Suzanne, Ginger and Larry.|
-------------------------------------------------------------------

说明
1 /^[A-Z]..$/
需要在每一行中寻找的字符串是这样的:这个字符串是一行的开始且第一个字符是一个大写字
母,后面紧跟着两个任意字符,然后是新的一行,在本例子中,第 5 行的 Dan 符合这个要求。
2 /^[A-Z][a-z ]*3[0-5]/
需要在每一行中寻找是字符串是这样的:这个字符串是一行的开始, 第一个字符是一个大写字母,
紧跟着 0 个或者多个小写字母,然后是数字 3,再后面是一个介于 0 和 5 之间的数字。在本例中
第 6 行符合要求。
3 /[a-z]*\./
需要在每一行中寻找的字符串是这样的:0 个或者多个小写字母,后面紧跟着一个点号。在本例
中第 1,2,7,8 行符合要求。
4 /^*[A-Z][a-z][a-z]$/
需要在每一行中寻找的字符串是这样的:这个字符串是一行的开始,它首先是 0 个或者多个空格
(注意,制表符不算是一个空格) ,然后是一个大写字母和两个小写字母,紧跟着是新的一行。
在本例中,第 4 行的 Tom 和第 5 行的 Dan 符合要求。
5 /^[A-Za-z]*[^,][A-Za-z]*$/
需要在每一行中寻找的字符串是这样的:这个字符串是一行的开始,字符串的开头是 0 个或者多
个大写字母和(或)小写字母。紧跟着一个只要不是逗号的字符,然后是 0 个或者多个大写或者
小写字母,以及新的一行。在本例中,第 5 行是符合要求的。

2.2.1 更多的正则表达式元字符
下面的正则表达式是一些不必在所有实用程序中间通用的正则表达式,它们可以用于
vi 编辑器和某些版本的 sed 和 grep。下面将要详细讨论的是在 egrep 和 awk 中可以使用的一
些扩展正则表达式。

实例 2.10

(Beginning and End of Word Anchors ( \< \> ))


% vi textfile
-------------------------------------------------------------------
Unusual occurrences happened at the fair.
--> Patty won fourth place in the 50 yard dash square and fair.
Occurrences like this are rare.
The winning ticket is 55222.
The ticket I got is 54333 and Dee got 55544.
Guy fell down while running around the south bend in his last
event.
~
~
~

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux 工具箱 39

/\<fourth\>/
---------------------------------------------------------------------

说明
需要在每一行中搜索的是所有包含单词 fourth 的行。\< 表示的是锚定单词的开始,\>表示锚定
单词的结束。被搜索的单词可以被空格分割,以标点符号结束,在一行的开始,或者在一行的末尾
等等。

实例 2.11

% vi textfile
---------------------------------------------------------------------
Unusual occurrences happened at the fair.
--> Patty won fourth place in the 50 yard dash square and fair.
Occurrences like this are rare.
The winning ticket is 55222.
The ticket I got is 54333 and Dee got 55544.
--> Guy fell down while running around the south bend in his last
event.
~
~
~
/\<f.*th\>/

说明
需要在每一行中搜索的字符串是这样的:以字母 f 开始,紧跟着 0 个或者多个任意字符(.*),
最终以 th 结束。

实例 2.12

(Remembered Patterns \(\))


% vi texfile (Before Substitution)
Unusual occurences happened at the fair.
Patty won fourth place in the 50 yard dash square and fair.
Occurences like this are rare.
The winning ticket is 55222.
The ticket I got is 54333 and Dee got 55544.
Guy fell down while running around the south bend in his last
event.
~
~
~
1. :1,$s/\([0o]ccur\)ence/\1rence/
------------------------------------------------
% vi textfile (After Substitution)
--> Unusual occurrences happened at the fair.
Patty won fourth place in the 50 yard dash square and fair.
--> Occurrences like this are rare.
The winning ticket is 55222.
The ticket I got is 54333 and Dee got 55544.
Guy fell down while running around the south bend in his last
event.
~

PDF created with pdfFactory Pro trial version www.pdffactory.com


40 第2章

~
~

说明
1 编辑器寻找字符串 Occurence 或者 occurence(注意,单词是故意拼写错的),如果找到,在圆括
号中的模板就被标记(也就是说,occur 存储器或者 Occur 被标记) 。因为这是在第一个模板中标
记的,因此称为 tag1。这个模板被保存到内存存储器中,叫作寄存器 1。先把\1 用 1 中的内容替
换,再把单词的其他部分(在本例中是 rence)附加到它的后面。我们以“occurence”开始,以
“occurence”结束,请参考图 2.1。

标签 1

寄存器 1
用寄存器 1
的值替代
Occur
这个 1

下一次,如果模板 occurence 被匹
配,寄存器看起来就是这样的:

寄存器 1

occur

图 2.1 存储的模板和标签

实例 2.13

% vi textfile (Before Substitution)


--------------------------------------------------------
Unusual occurrences happened at the fair.
Patty won fourth place in the 50 yard dash square and fair.
Occurrences like this are rare.
The winning ticket is 55222.
The ticket I got is 54333 and Dee got 55544.
Guy fell down while running around the south bend in his last
event.
~
~
~
1. :s/\(square\) and \(fair\)/\2 and \1/
------------------------------------------------------------
% vi textfile (After Substitution)
------------------------------------------------------------
Unusual occurrences happened at the fair.
--> Patty won fourth place in the 50 yard dash fair and square.
Occurrences like this are rare.
The winning ticket is 55222.
The ticket I got is 54333 and Dee got 55544.
Guy fell down while running around the south bend in his last

PDF created with pdfFactory Pro trial version www.pdffactory.com


Linux 工具箱 41

event.
~
~
~

说明
编辑器搜索正则表达式 square and fair。将 square 标记为#1,并将 fair 标记为#2。在需要替换的
一侧,将#2 的内容替换为\2,将#1 的内容替换为\1。请参考图 2.2 所示。

标签 1 标签 2

寄存器 1 寄存器 2

fair square

图 2.2 使用多个标签

实例 2.14

(Repetition of Patterns ( \{n\} ))


% vi textfile
--------------------------------------------
Unusual occurrences happened at the fair.
Patty won fourth place in the 50 yard dash square and fair.
Occurrences like this are rare.
--> The winning ticket is 55222.
The ticket I got is 54333 and Dee got 55544.
Guy fell down while running around the south bend in his last
event.
~
~
~
~
1. /5\{2\}2\{3\}\./

说明
1 在每一行中寻找这样的字符串:首先是 2 个 5,紧跟着 3 个 2,最后是一个点(.)

PDF created with pdfFactory Pro trial version www.pdffactory.com


第3章

grep 家 族
UNIX 的 grep 家族包括 grep、egrep 和 fgrep。grep 的命令在文件中搜索正则表达式,并
打印所有包含这些表达式的行。egrep 和 fgrep 命令只跟 grep 有很小的不同。egrep 是 grep
的扩展,支持更多的 RE 元字符。fgrep 就是 fixed grep 或者 fast grep,它们把所有的字母都
看作单词。这就是说,正则表达式的元字符不再特殊——它们只表示其自身的字面意义。
Linux 使用的是 Gnu 版本的 grep,它的功能跟 grep 一样,但是更好用。为了符合 POSIX
标准(见表 3.1 和表 3.2),grep 增加了一些新的选项,包括-G、-E 和-F,它们使你在拥有标
准 grep 所提供的功能的同时还拥有 egrep 和 fgrep 的功能。1

3.1 grep 命 令

3.1.1 grep 的含义


grep 的名字可以追溯到 ex 编辑器。如果想启动 ex 编辑器并搜索一个字符串,你就需要
在提示符下输入:
: /pattern/p

这样包含字符串 pattern 的第一行就会被 print 命令打印为“p”,如果你想打印所有包含


pattern 的行,就需要输入:
: g/pattern/p

g 命令的意思是“文件中所有的行”或者“运行一个全文替代”
。因为搜索模板被称为
正则表达式,所以我们可以用 RE 来替换模板,命令读为:
: g/RE/p

你看到了,这就是 grep 命令的含义和名字的来源。它的意思是“全面搜索正则表达式


并把找到的行打印出来(global search regular expression (RE) and print out the line)。使用 grep
的好处是不用启动编辑器就可以运行查找,也不需要用斜杠把正则表达式括起来。因此它比
使用 vi 和 ex 更加快捷和方便。

1.学习使用 grep 递归,请参考附录 A 中 Gnu rgrep 和 xargs 的使用。

PDF created with pdfFactory Pro trial version www.pdffactory.com


44 第3章

3.1.2 grep 怎样工作


grep 命令在一个或者多个文件中搜索字符串模板。如果模板包括空格,则必须被引用。
模板可以是一个被引用的字符串或者是一个单词2,其后面的所有字符串被看作文件名。grep
把搜索结果送到屏幕,但是不影响输入文件。

格式
grep word filename filename

实例 3.1
% grep Tom /etc/passwd

说明
grep 将在文件 /etc/passwd 中搜索模板 Tom。如果成功,搜索的行将显示在屏幕上。如果没有找
到,则没有任何输出。如果文件非法,将在屏幕上显示错误信息。如果模板被找到,grep 通过返回
退出状态值 0 以表示成功;如果没有找到模板,则返回退出状态值 1;如果文件没有找到返回退出
状态值 2。
grep 可以从标准输入、管道和文件取得输入。如果你忘记了为文件命名,grep 就假设正在从标
准输入,即从键盘取得输入,直到你输入什么的时候为止。如果输入来自管道,则意味着将某个命
令的输出作为 grep 命令的输入,如果要求的模板被匹配,grep 就把它打印到屏幕。

实例 3.2
% ps aux | grep root

说明
ps 命令输出(ps aux 显示系统中运行的所有进程)被发送给 grep,所有包含 root 的行都将被打印。

3.1.3 基本及扩展的正则表达式
grep 命令支持多种正则表达式元字符集,定义搜索非常灵活(参见表 3.2) 。同时还提供
了一些选项来定义搜索和显示结构的方式(参见表 3.3) 。例如,你可以通过选项关闭大小写
敏感、显示行数以及显示文件名等等。
正则表达式元字符集有两种版本:基本的和扩展的。正则表达式 grep 使用的是其元字
符集的基本集(参见表 3.2),egrep 或者 grep -E 用的是正则表达式元字符集的扩展集(参见
表 3.3)。Gnu grep 同时使用这两个集。基本集包括:
^,$,.,*,[],[^],\<,and,\>

另外,Gnu 将\b,\w 和\W 作为 POSIX 新增的正则表达式元字符予以识别(参见表 3.4)



-E 选项使得 Gnu grep 能够使用扩展集。即使在没有-E 选项的情况下,标准的 grep 在默
认情况下也支持以反斜杠开头的扩展正则表达式元字符集3。例如:

2.单词也称为 token。
3.任何版本的 grep 都可以通过引用反斜杠关闭正则表达式元字符集的特殊性。

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 45

?,+,{},|,()

前面没有反斜杠的扩展元字符集对于标准的 grep 没有特别的含义。


\?,\+,\{,\|,\(,\)

Gnu grep 的使用格式参见表 3.1。

表 3.1 Gnu grep

格式 含义
grep 'pattern' filename(s) 基本正则表达式元字符集(默认)
grep –G 'pattern' filename(s) 同上(默认)
grep –E 'pattern' filebname(s) 扩展正则表达式元字符集
grep –F 'pattern' filename(s) 无正则表达式元字符集

表 3.2 grep 的正则表达式元字符集(基本集)

元字符 功能 例子 匹配什么
^ 锚定行的开始 ^love 匹配所有以 love 开头的行
$ 锚定行的结束 love$ 匹配所有以 love 结束的行
. 匹配一个字符 l..e 匹配所有这样的行,这些行包含这样的字符,第
一个字符是 l,紧跟着两个字符,然后是 e
* 代表 0 个或者多个先前 *love 匹配所有这样的行,有 0 个或者多个空格,空格
字符 后跟着 love
[] 匹配字符组中的一个字 [Ll]ove 匹配所有包含 love 或者 Love 的行

[^] 匹配一个不在范围内的 [^A-Z]ove 匹配所有这样的行,这些行包含如下的字符,第一
字符 个字符不是从 A~Z 中间的一个,后面跟着 ove
/<a 锚定单词的开始 \<love 匹配所有这样的行,这些行包含以 love 开头的
单词(vi 和 grep 支持这个功能)
\> 锚定单词的结束 love\> 匹配所有这样的行,这些行包含以 love 结束的
单词(vi 和 grep 支持这个功能)
\(..\)b 标记后面用到的匹配字 \(love\)able 最多可以使用 9 个标签。第一个标签是模板最左
符 边的部分。在本例子中,模板 love 保存为标签 1,
后面的\1 指的就是 Love;本例子搜索的是这样
的行,这些行包含这样的字符,在 Loveable 后
面跟着 lovers
x\{m\} m 次复制字符 x o\{5,10\} 匹配所有这样的行,包含的 o 的数量在 5~10
x\{m,\} 至少 m 次复制字符 x o\{5,\} 个之间。(vi 和 grep 支持这个功能)
x\{m,n\}c 至少 m 次。至多 n 次复 o\{5,10\}
制字符 x
\w 文字和数字字符, L\w*e 匹配一个 l 字符,紧跟着 0 个或者多个文字或数
[A-Za-z0-9] 字字符,然后是 e

PDF created with pdfFactory Pro trial version www.pdffactory.com


46 第3章

续表
元字符 功能 例子 匹配什么
\W 同上 love\W+ 匹配 love 后面是一个或者多个非单词字符,如
点号或者问号
\b 单词分界线 \blove\b 仅仅匹配单词 love
a. 这些元字符必须与反斜线一起才能工作,在 grep-E 和 Gnu egrep 中也是这样;他们在 UNIX 下的 egrep 中完全不能用。
b. 这些元字符是扩展集的一部分,它们可以在 UNIX 下的 grep 和 Gnu 的常规 grep 下正常工作。如果加反斜线,在 UNIX 下
的 egrep 中就不能正常工作。
c. 该元字符不能被所有版本的 UNIX 和模板匹配实用程序支持;它们通常在 vi 或 grep 下工作。它们根本不能在 UNIX 的 egrep 下工作。

表 3.3 扩展集(用于 egrep 和 grep-E)

元字符 功能 例子 匹配什么
+ 匹配一个或者多个先前字符 [a-z]+ove 匹配一个小写字符且后面是 ove 的,可以
找到 move、appove、behoove 等
? 匹配 0 个或者多个先前字符 lo?ve 匹配 l 后面有一个或者没有 o,然后是 ve
a|b|c 匹配 a 或 b 或 c love|hate 匹配 love 或 hate 其中一个
() 字符组 love(able|rs)(ov)+ 匹配 lovable 或 lovers,匹配一个或多个 ov
(..)(…)\1\2a 标记匹配字符串 \(love\)ing 标签标记出寄存器的一部分,并稍后替换
模板。该模板叫作\1,并可反复引用。在
表达式中最多可以使用 9 个这样的标签。
例如,模板 love 被保存在寄存器 1 中并稍
后替换标签叫作\1
x{m} 重复字符 X,m 次,至少 m o\{5\} 匹配 5 个 o,或至少 5 个 o,或 5~10 个 o
x{m,} 次,或者 m 次和 n 次之间 o\{5,\}
x{m,n} b o\{5,10\}
a. 标签和反向参考功能在 UNIX 的 egrep 下都不能使用。
b. 该元字符不能被所有版本的 UNIX 和模板匹配实用程序支持,它们通常在 vi 或 grep 下工作。它们根本不能在 UNIX 的 egrep 下工作。

POSIX 字符类。POSIX(the Portable Operating System Interface)是一个用来保证程序


可以在不同操作系统之间移植的工业标准。POSIX 发现不同的国家和地区有不同的字符编码
方式,不同的货币符号,不同的时间和日期的表达方式。为了处理不同的字符集,POSIX 加
入了基本正则表达式和扩展正则表达式,表 3.4 显示的是被括弧括起来的字符类。
这些类,例如:[:alnum:]是 A-Za-z0-9 的另外一种表达方式。要使用这个类就必须把
它放在其他的括弧内,使得它成为可以识别的正则表达式。例如 A-Za-z0-9 本身并不是正则
表达式,但是[A-Za-z0-9]就是。同样,[:alnum:]只有被写成[[:alnum:]]时才是正则表达
式。这两种形式的区别在于,第一种依赖的是 ASCII 字符编码,第二种依赖的是类中的其他
语言,例如瑞典语或者德语。
表 3.4 被括弧括起来的字符类

被括弧括起来的类(Bracketed Class) 含义
[:alnum:] 文字数字字符
[:alpha:] 文字字符

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 47

续表
被括弧括起来的类(Bracketed Class) 含义
[:digit:] 数字字符
[:graph:] 非空字符(非空格、控制字符)
[:lower:] 小写字符
[:cntrl:] 控制字符
[:print:] 跟非空字符一样,但是包括空格
[:punct:] 标点符号
[:space:] 所有白空格字符(新行、空格、制表符)
[:upper:] 大写字符
[:xdigit:] 十六进制数字(0-9、a-f、A-F)

实例 3.3

1 % grep '[[:space:]]\.[[:digit:]][[:space:]]' datafile


southwest SW Lewis Dalsass 2.7 .8 2 18
southeast SE Patricia Hemenway 4.0 .7 4 15
2 % grep –E '[[:space:]]\.[[:digit:]][[:space:]]' datafile
southwest SW Lewis Dalsass 2.7 .8 2 18
southeast SE Patricia Hemenway 4.0 .7 4 15
3 % egrep '[[:space:]]\.[[:digit:]][[:space:]]' datafile
southwest SW Lewis Dalsass 2.7 .8 2 18
southeast SE Patricia Hemenway 4.0 .7 4 15

说明
1,2,3 对于 Linux 下的各种 grep(除了 fgrep)都支持 POSIX 括号括起来的类集。在上面的各
个例子中,grep 都将搜索一个空格、逗号、数字[0-9]和另外一个空格字符。

3.1.4 grep 和退出状态


grep 命令在 Shell 脚本中非常有用,因为它通过返回一个退出状态值来说明是否找到了
你要搜索的模板或者文件。如果模板找到,grep 返回状态值 0,表示成功;如果无法找到模
板,则返回退出状态值 1;如果文件无法找到,就返回退出状态值 2(Linux\UNIX 使用的其
他搜索程序并不返回退出状态值,只在出现语法错误的时候返回错误) 。
在下面的例子中,在文件/etc/passwd 中无法找到 john。

实例 3.4

1 % grep 'john' /etc/passwd


2 % echo $status (bash/tcsh/csh)
1
or
$ echo $? (bash/sh/ksh/tcsh)
1

PDF created with pdfFactory Pro trial version www.pdffactory.com


48 第3章

说明
1 grep 在文件/etc/passwd 中搜索字符串 john,如果成功,grep 的退出状态值就是 0;如果在文件中
没有找到 john,退出状态值就是 1;如果文件没有找到,退出状态值就是 2。
2 在 TC 及 C Shell 中的变量 status 和 Bourne 或者 Korn Shell 中的变量 ?都表示最后执行的命令的
状态。在 Bourne Again(bash)Shell 中,你可以使用以上任何一种变量检测退出状态。

3.1.5 正则 grep 实例(grep、grep-G)

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.5

grep NW datafile or grep –G NW datafile


northwest NW Charles Main 3.0 .98 3 34

说明
打印文件 datafile 中所有包含正则表达式 NW 的行。

实例 3.6

grep NW d*
datafile: northwest NW Charles Main 3.0 .98 3 34
db:northwest NW Joel Craig 30 40 5 123

说明
打印所有以 d 开头的文件中且包含正则表达式 NW 的行。Shell 扩展 d*表示所有以 d 开头的文
件,在本例子文件名中是 datafile 和 db。

实例 3.7

grep '^n' datafile


northwest NW Charles Main 3.0 .98 3 34
northeast NE AM Main Jr. 5.1 .94 3 13
north N0 Margot Weber 4.5 .89 5 9

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 49

说明
打印所有以 n 开头的行。^表示锚定行的开头。

实例 3.8

grep '4$' datafile


northwest NW Charles Main 3.0 .98 3 34

说明
打印所有以 4 结束的行。$表示锚定行的结尾。

实例 3.9

grep TB Savage datafile


grep: Savage: No such file or directory
datafile:eastern EA TB Savage 4.4 .84 5 20

说明
因为第一个参数是模板, 其他的参数是文件名,
所以 grep 将在文件 Savage 和 datafile 中搜索 TB。
要搜索 TB Savage 参考下面的例子。

实例 3.10

grep 'TB Savage' datafile


eastern EA TB Savage 4.4 .84 5 20

说明
打印所有包含模板 TB Savage 的行。如果没有引用(在本例子中,单引号和双引号的作用是一
样的),TB 与 Savage 之间的空格将导致 grep 在文件 Savage 和 datafile 中搜索 TB,就像前面的例
子一样。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


50 第3章

实例 3.11

grep '5\..' datafile


western WE Sharon Gray 5.3 .97 5 23
southern SO Suan Chin 5.1 .95 4 15
northeast NE AM Main Jr. 5.1 .94 3 13
central CT Ann Stephens 5.7 .94 5 13

说明
打印包含第一个字符是 5,紧跟着一个点,再后面是任意一个字符的字符串的行。除非在点的
前面有一个反斜杠,否则它就表示一个任意字符。若前面有反斜杠,则点就不再特殊,而是仅仅表
示一个点。

实例 3.12

grep '\.5' datafile


north NO Margot Weber 4.5 .89 5 9

说明
打印所有包含字符串“.5”的行。

实例 3.13

grep '^[we]' datafile


western WE Sharon Gray 5.3 .97 5 23
eastern EA TB Savage 4.4 .84 5 20

说明
打印所有以 w 或者 e 开头的行。^表示锚定行的开头,括号里的一个字符将被匹配。

实例 3.14

grep '[^0-9]' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
打印所有包含非数字字符的行。括号内的^表示任意一个不在括号范围内的字符。因为与行都包
含至少一个非数字字符,所以所有的行都将被打印(参考-v 选项)

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 51

实例 3.15

grep '[A-Z][A-Z] [A-Z]' datafile


eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13

说明
打印所有包含前两个字符是大写字母,后面紧跟着一个空格及一个大写字母的字符串的行。例
如,TB Savage 和 AM Main。

实例 3.16
grep 'ss* ' datafile
northwest NW Charles Main 3.0 .98 3 34
southwest SW Lewis Dalsass 2.7 .8 2 18

说明
打印所有包含一个或者多个 s 且后面跟有一个空格的字符串的行。比如,Charles 和 Dalsass。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.17
grep '[a-z]\{9\}' datafile
northwest NW Charles Main 3.0 .98 3 34
southwest SW Lewis Dalsass 2.7 .8 2 18
southeast SE Patricia Hemenway 4.0 .7 4 17
northeast NE AM Main Jr. 5.1 .94 3 13

说明
打印所有包含每个字符串至少有 9 个连续小写字符的字符串的行。

实例 3.18

grep '\(3\)\.[0-9].*\1 *\1' datafile


northwest NW Charles Main 3.0 .98 3 34

PDF created with pdfFactory Pro trial version www.pdffactory.com


52 第3章

说明
打印所有包含这样的字符串的行,第一个字符是 3,紧跟着一个句点,然后是任意一个数字,
然后是任意个数字,然后是一个 3,然后是任意个制表符,然后又是一个 3。因为 3 在一对圆括号中,
它可以被后面的\1 引用。\1 的含义就是本正则表达式中第一个被\(和\)括起来的部分。

实例 3.19
grep '\<north' datafile
northwest NW Charles Main 3.0 .98 3 34
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9

说明
打印所有包含以 north 开始的单词的行。\<表示锚定单词的开头。

实例 3.20
grep '\<north\>' datafile
north NO Margot Weber 4.5 .89 5 9

说明
打印所有包含单词 north 的行。\<表示锚定单词的开头,\>表示锚定单词的结尾。

实例 3.21

grep '\bnorth\b' datafile


north NO Margot Weber 4.5 .89 5 9

说明
打印所有包含单词 north 的行,\b 是单词分界符。在所有 Gnu 版本的 grep 中,它都可以用来代
替单词锚。

实例 3.22

grep '^n\w*\w' datafile


northwest NW Charles Main 3.0 .98 3 34
northeast NE AM Main Jr. 5.1 .94 3 13

说明
打印所有包含这样的字符串的行,第一个字符是 n,紧跟着是任意个字母或者数字字符,然后
是一个非字母数字字符。在各种 Gnu 版本的 grep 中,\w 和\W 都是标准的单词匹配符。

实例 3.23

grep '\<[a-z].*n\>' datafile

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 53

northwest NW Charles Main 3.0 .98 3 34


Western WE Sharon Gray 5.3 .97 5 23
southern SO Suan Chin 5.1 .95 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
central CT Ann Stephens 5.7 .94 5 13

说明
打印所有包含这样的字符串的行,第一个字符是一个小写字母,紧跟着是任意个字符,然后以
字符 n 结束。注意.*,它表示任意字符,包括空格。

3.2 扩展 grep(grep-E 或者 egrep)


使用扩展 grep 的主要好处是增加了额外的正则表达式元字符集(参见表 3.5),这些元
字符集已经加入到基本集里了。通过-E 选项,Gnu 允许用户使用这些新的元字符。

表 3.5 egrep 的正则表达式元字符集

元字符 功能 例子 匹配什么
^ 锚定行的开始 ^love 匹配所有以 love 开头的行
$ 锚定行的结束 love$ 匹配所有以 love 结尾的行
. 匹配一个字符 l..e 匹配所有这样的行,包含这样的字符串,第
一个字符是 l,后面紧跟着两个任意字符,最
后是 e
* 匹配零个或者多个字符 *love 匹配所有包含首先是零个或者多个空格,后面
紧跟着模板 love 的字符串的行
[] 匹配方括号范围内 的一 [L1]ove 匹配所有包含单词 love 或者 Love 的行
个字符
[^] 匹配一个不在方括 号范 [^A-KM-Z]ove 匹配所有包含第一个字符不在从 A~K 或者
围内的字符 从 M~Z 的范围内,后面紧跟着 ove 的字符
串的行
grep -E or egrep 新加的元字符
+ 匹配一个或者多个 先前 [a-z]+love 匹配一个或者多个小写字符,紧跟的是 ove。
字符 这里将找到 move、approve、love、behoove 等
? 匹配零个或者一个 先前 lo?ve 匹配这样的字符串,第一个字母是 l 紧跟着
字符 零个或者一个 o,然后是 ve。在这里将找到
love 或 lve
a|b 匹配 a 或者 b love|hate 匹配表达式 love 或者 hate
() 一组字符 love(able|ly)(ov)+ 匹配 loveable 或者 lovaly,其后面是一个或
者多个 ov
x{m} m 次重复字符 x,或者至 o\{5} 匹配的行分别为:包含 5 个 o,
x{m,} 少 m 次,或者至少 m 次 o\{5,} 包含至少 5 个 o,
x{m,n} a 最多 n 次 o\{5,10} 包含 5~10 个 o

PDF created with pdfFactory Pro trial version www.pdffactory.com


54 第3章

续表
元字符 功能 例子 匹配什么
\w 字母数字字符[a-zA-Z0-9] l\w*e 匹配这样的字符串,第一个字符是 l,紧跟着
\W 非 字 母 数 字 字 符 [^a-zA- 零个或者多个字母数字字符,再后面是 e
Z0-9]
\b 单词分界符 \blove\b 仅匹配单词 love
a. {}元字符并不是在所有版本的 UNIX 模板匹配实用程序中都被支持。它们通常配合 vi 和 grep 工作,但是不支持 UNIX
下的 egrep。

3.2.1 扩展 grep 的例子(egrep 或者 grep-E)


下面的例子说明了在 egrep 和 grep-E 中使用正则表达式元字符扩展集的方法。先用 grep
的例子说明标准正则表达式元字符集的用法,支持这些元字符的用法 egrep。在基本的 Gnu
grep 中,任何额外的元字符都可能被用到,这些元字符都是以一个反斜杠开头的特殊字符。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.24

1 % egrep 'NW|EA' datafile


northwest NW Charles Main 3.0 .98 3 34
eastern EA TB Savage 4.4 .84 5 20

2 % grep –E 'NW|EA' datafile


northwest NW Charles Main 3.0 .98 3 34
eastern EA TB Savage 4.4 .84 5 20

3 % grep 'NW|EA' datafile


4 % grep 'NW\|EA' datafile
northwest NW Charles Main 3.0 .98 3 34
eastern EA TB Savage 4.4 .84 5 20

说明
1 打印所有的包含 NW 或者 EA 的行。在这个例子中,egrep 被使用。如果你没有 Gnu 版本的 grep,
就请使用 egrep。
2 在这个例子中,Gnu 版本的 grep 打开- E 选项以支持扩展元字符,就像 egrep。
3 正规的 grep 不支持扩展的正则表达式。竖线是用于表示“或”的扩展正则表达式元字符。正规
grep 无法识别,若搜索“NW|EA”,则会因为找不到匹配而没有任何输出。

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 55

4 在 Gnu 正规 grep(grep-G)里,如果在字符前面加一个反斜杠,这个字符就被翻译成扩展正则表
达式,就像 egrep 和 grep –E 一样。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.25

% egrep '3+' datafile


% grep –E '3+' datafile
% grep '3\+' datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
northeast NE AM Main 5.1 .94 3 13
central CT Ann Stephens 5.7 .94 5 13

说明
打印所有包含一个或者多个 3 的行。

实例 3.26

% egrep '2\.?[0-9]' datafile


% grep –E '2\.?[0-9]' datafile
% grep '2\.\?[0-9]' datafile
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
eastern EA TB Savage 4.4 .84 5 20

说明
打印所有这样的行,该行包含这样的字符串,首先是字符 2,紧跟着零个或者一个点,然后是
一个 0 和 9 之间的数字。

实例 3.27

% egrep '(no)+' datafile


% grep –E '(no)+' datafile
% grep '\(no\)\+' datafile
northwest NW Charles Main 3.0 .98 3 34
northeast NE AM Main 5.1 .94 3 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


56 第3章

north NO Margot Weber 4.5 .89 5 9

说明
打印包含一个或者多个连续的 no 的行。

实例 3.28

% grep –E '\w+\W+[ABC]' datafile


northwest NW Charles Main 3.0 .98 3 34
southern SO Suan Chin 5.1 .95 4 15
northeast NE AM Main Jr. 5.1 .94 3 13
central CT Ann Stephens 5.7 .94 5 13

说明
打印所有这样的行,该行包含这样的字符串,首先是一个或者多个字母数字字符(\w+),紧跟
着一个或者多个非字母数字字符(\w+),最后是 A、B 或者 C 中的一个。

实例 3.29

% egrep 's(h|u)' datafile


% grep –E 's(h|u)' datafile
% grep 's\(h\|u\)' datafile
western WE Sharon Gray 5.3 .97 5 23
southern SO Suan Chin 5.1 .95 4 15

说明
打印所有这样的行,该行包含这样的字符串,首先是字母 S,紧跟着是 h 或者 u。

实例 3.30

% egrep 'Sh|u' datafile


% grep –E 'Sh|u' datafile
% grep 'Sh\|u' datafile
western WE Sharon Gray 5.3 .97 5 23
southern SO Suan Chin 5.1 .95 4 15
southwest SW Lewis Dalsass 2.7 .8 2 18
southeast SE Patricia Hemenway 4.0 .7 4 17

说明
打印所有的包含 Sh 或者 u 的行。

3.2.2 正规和各种扩展 grep 的不同

大多数 Linux 支持的 Gnu 版本的 grep 都跟 UNIX 下的同名软件类似。例如,Solaris 或


BSD UNIX 下的 egrep 就不支持以下三组元字符:重复作用的元字符,\{\};用来做标签的元

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 57

字符,\(\);用来锚定单词的元字符\<\>。在 Linux 下,在 grep 和 grep -E 中使用以上元字符


都是合法的,但是 egrep 不能识别\<\>。下面的例子就用来说明,当你恰好在 UNIX 系统下
而不是 Linux 系统下使用 bash 或者 tcsh 的时候,如果你想使用 grep,特别在脚本中使用 grep
家族的时候,将有哪些不同。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.31

(Linux Gnu grep)


1 % grep '<north>' datafile Must use backslashes
2 % grep '\<north\>' datafile
north NO Margot Weber 4.5 .89 5 9

3 % grep –E '\<north\>' datafile


north NO Margot Weber 4.5 .89 5 9

4 % egrep '\<north\>' datafile


north NO Margot Weber 4.5 .89 5 9

(Solaris egrep)
5 % egrep '\<north\>' datafile
<no output; not recognized>

说明
1 不论使用什么 grep,锚定单词的符号<和>前都必须加反斜杠\。
2 这时 grep 搜索以 north 开头和结尾的单词,\<表示锚定单词的开头,\>表示锚定单词的结束。
3 当 grep 打开-E 选项,也可识别单词锚定元字符。
4 egrep 的 Gnu 形式也识别单词锚定元字符。
5 在使用 Solaris(SVR4)的时候,egrep 不识别单词锚定元字符。

实例 3.32

(Linux Gnu grep)


1 % grep 'w(es)t.*\1' datafile
grep; Invalid back reference

2 % grep 'w\(es\)t.*\1' datafile


northwest NW Charles Main 3.0 .98 3 34

PDF created with pdfFactory Pro trial version www.pdffactory.com


58 第3章

3 % grep –E 'w(es)t.*\1' datafile


northwest NW Charles Main 3.0 .98 3 34

4 % egrep 'w(es)t.*\1' datafile


northwest NW Charles Main 3.0 .98 3 34

(Solaris egrep)
5 % egrep 'w(es)t.*\1' datafile
<no output; not recognized>

说明
1 当使用正规 grep 的时候,扩展元字符()前必须加反斜杠\,否则就会出错。
2 如果正则表达式 w\(es\)t 被匹配,模板 es 就被存储到内存中的寄存器 1 内。这个正则表达式的含
义是如果 west 被找到,标记并保存 es,然后搜索任意个字符(.*),这些字符后面紧跟着另外一
个 es(\1),打印这行,Charles 中的 es 就是匹配的寄存器 1 中的 es。
3 这个例子跟前一个例子是一样的,只是 grep 用是的-E 选项,不需要在()前加反斜杠\。
4 Gnu 也使用扩展元字符() ,不需要反斜杠\。
5 在 Solaris 平台上,egrep 不识别任何形式的标记和反向参考。

实例 3.33

(Linux Gnu grep)


1 % grep '\.[0-9]\{2\}[^0-9]' datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southern SO Suan Chin 5.1 .95 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

2 % grep –E ‘\.[0-9]{2}[^6-9]' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southern SO Suan Chin 5.1 .95 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

3 % egrep ‘\.[0-9]\{2}[^0-9]' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southern SO Suan Chin 5.1 .95 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

(Solaris egrep)
4 % egrep ‘\.[0-9]{2}[^0-9]' datafile
<no output; not recognized with or without backslashes>

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 59

说明
1 扩展元字符{}用来表示重复。除非在前面加上反斜杠,否则 Gnu 和 UNIX 版本的正规 grep 无法
识别这个扩展元字符。该正则表达式的意思是:搜索这样的行,该行包含这样的字符串,第一个
字符是一个点号\.,紧跟着一个 0~9 之间的数字[0-9],如果该字符串重复两次\{2\},则后面就是
一个非数字的字符[^0-9]。
2 若使用扩展 grep 或者 grep -E 选项,则在表示重复的元字符{2}的括号前面就不需要加反斜杠了。
3 因为 Gnu 版本的 egrep 跟 grep -E 的功能一样,因此这个例子的结果跟前面相同。
4 这是标准的 UNIX 下的 egrep,不论是否有反斜杠它都无法识别花括号元字符{}。

3.3 固定 grep(grep-F 或者 fgrep)


fgrep 的命令行形式跟 grep 相似,但是它不识别任何正则表达式元字符。所有的字符都
表示它们自己。一个插入符号就表示插入符号,一个美元符号就表示美元符号等等。-F 选项
使得 Gnu grep 精确地模仿 fgrep。

3.4 递归 grep(rgrep)
跟其他 grep 家族成员不同,rgrep 可以递归访问目录树。rgrep 有一些命令行选项支持跟
标准的 grep 一样的选项。参考附录 A 或者在命令行输入 rgrep?以获得帮助(标准的 UNIX
下的 grep 不支持这个特性)。

实例 3.34

% fgrep '[A-Z]****[0-9]..$5.00' file or


% grep –F '[A-Z]****[0-9]..$5.00' file

说明
找到包含字符串“[A-Z]****[0-9]..$5.00”的行,这里的每个字符都表示它们自己,没有特殊字
符。

3.5 grep 与管道


作为文件输入的一种替代形式,grep 可以从管道中取得输入。

实例 3.35

% ls –l
drwxrwxrwx 2 ellie 2441 Jan 6 12:34 dir1
-rw-r—-r-- 1 ellie 1538 Jan 2 15:50 file1
-rw-r-—r-- 1 ellie 1539 Jan 3 13:36 file2
drwxrwxrwx 2 e11ie 2341 Jan 6 12:34 grades

% ls –l | grep '^d'

PDF created with pdfFactory Pro trial version www.pdffactory.com


60 第3章

drwxrwxrwx 2 ellie 2441 Jan 6 12:34 dir1


drwxrwxrwx 2 ellie 2341 Jan 6 12:34 grades

说明
命令 ls 的输出通过管道传递给 grep,输出中所有以 d 开头的行都被打印,也就是所有的目录都
被打印。

3.6 grep 与选项


grep 命令有一些选项用来控制其行为。但并不是所有的 UNIX 版本都支持相同选项,所
以你应该通过检查 man 页面来获得完整的列表。Gnu 版本的 grep 加入了一些新的选项和选
项的可供选择的使用方法,参考表 3.6。各种版本的 grep(grop-G、-E 及-F)都可以使用 Gun
grep 的选项,参考表 3.7。

表 3.6 Gnu grep 选项

选项 功能
-b 在搜索到的行的前面打印该行所在的块号码。该功能在通过内容定位块的
号码时非常有用
-c 只显示有多少行匹配,而不具体显示匹配的行
-h 不显示文件名
-i 在字符串比较的时候忽略大小写
-l 只显示包含匹配模板的行的文件名清单,不同项目之间用换行符分隔
-n 在每一行前面打印该行在文件中的行数
-s 静默工作,除非出现错误信息,否则不打印任何信息,这个功能在检测退
出状态的时候有用
-v 反检索,只显示不匹配的行
-w 如果被\<和\>引用,就把表达式做为一个单词搜索只在 grep 中有效(不是
所有版本的 grep 都支持该特性,SCO UNIX 的 grep 就不支持)

表 3.7 所有 Gnu 版本都可以使用的 grep 的选项(-G、-E 和-F)

选项 功能
-# 表示同时显示匹配行上下的#行,例如:grep -2 pattern filename 表示同时
显示匹配行上下各两行
-A#,--after-context=# 在匹配指定的内容的行打印完毕后,再打印一行#号
-B#,--before-context=# 在匹配模板的行的前面打印 1 行#号
-C,--context 相当于选项-2,在匹配行的前后各打印 2 行空行
-V,--version 显示包括 bug 报告在内的版本信息
-b,--byte-offset 在每一行前面打印字符偏移量

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 61

续表
选项 功能
-c,--count 打印每个文件匹配行的个数
-e PATTERN,--regexp=PATTERN 使用 PATTERN 的字面意义作为模板,在模板以“-”开头的时候非
常有用
-f FILE,--file=FILE 从 FILE 中提取模板。空文件中包含 0 个模板,所以什么也不匹配
-h,--no-filename 当多个文件匹配时候,不显示匹配文件名前缀
-i,--ignore-case 在模板和输入文件中都忽略大小写差别
-L,--files-without-match 打印不匹配模板的文件名清单
-l,-- files-with-matches 打印匹配模板的文件名清单
-n,--line-number 在匹配的行前面打印行号
-q,--quiet 取消标准输出,跟-n 功能是一样的
-s,--silent 不显示关于不存在或者无法读文件的错误信息
-v,--revert-match 打印不匹配模板的行
-w,--word-regexp 只打印以单词形式匹配模板的行,模板可以是包含数字、字符和下
划线的字符串
-x,--line-regexp 只打印整行匹配的行
-y 用法同-i
-U,--binary 把文件作为二进制文件,这个选项只在 MS-DOS 和 MS-Windows
中被支持
-u,--unix-byte-offsets 按照 UNIX 风格报告字符偏移量。只在-b 选项同时被使用的时候才
有效。这个选项只在 MS-DOS 和 MS-Windows 中被支持

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.36
% grep –n '^south'datafile
3:southwest SW Lewis Dalsass 2.7 .8 2 18
4:southern SO Suan Chin 5.1 .95 4 15
5:southeast SE Patricia Hemenway 4.0 .7 4 17

说明
选项-n 在每一个匹配行的前面打印行号。

PDF created with pdfFactory Pro trial version www.pdffactory.com


62 第3章

实例 3.37

% grep –i 'pat' datafile


southeast SE Patricia Hemenway 4.0 .7 4 17

说明
选项-i 关闭了大小写敏感。模板中可以包含任意的大小写形式。

实例 3.38

% grep –v 'Suan Chin' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
打印所有不包含 Suan Chin 的行。当需要从文本中删除特定的行时采用这个选项。删除特定行的
方法是,首先把输出重新定向到一个临时文件,然后再把临时文件重新命名为原文件的名字。
Grep -v 'Suan Chin' datafile > temp
Mv temp datafile

注意:在重新定向的时候必须使用临时文件,如果从 datafile 定向到 datafile 就会发生“严重错


误”(clobber)
,具体内容参见“重新定向”部分。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.39
% grep –l 'ss' *
datafile
datebook

说明
选项-l 使得 grep 只打印匹配的文件名,而不打印匹配的行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 63

实例 3.40

% grep –c 'west' datafile


3

说明
选项-c 使得 grep 只打印有多少匹配模板的行。注意:打印的不是模板出现了多少次,而是包含
模板的行有多少。

实例 3.41

% grep –w 'north' datafile


north NO Margot Weber 4.5 .89 5 9

说明
选项-w 使得 grep 只打印按照单词方式匹配模板的行,而不是作为单词的一部分匹配模板的行。
在这个例子中,包含单词 north 的行被打印,而包含 northeast 或者 northwest 的行不被打印。注意:
“单词”指的是由字母和数字组成的,在行的开头或者前面有空格,以空格或者换行符结束的字符
串。

实例 3.42

% echo $LOGNAME
lewis
% grep –i "$LOGNAME" datafile
southwest SW Lewis Dalsass 2.7 .8 2 18

说明
打印 Shell 环境变量$LOGNAME 的值。它包含用户登录的名字。如果变量被双引号引用,变量
还是需要由 Shell 来解释,而且一旦赋给变量多个单词,Shell 翻译时就会自动取消空格;如果变量
被单引号引用,变量就不发生替换,而是打印$LOGNAME。

3.6.1 Gnu grep 选项例子


实例 3.43

% grep -V
grep (GNU grep) 2.2
Copyright (C) 1988, 92, 93, 94, 95, 96, 97 Free Software Foundation,
Inc. This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

说明
选择选项-v 后,将列出 grep 的版权和版本信息,并提示你在发现任何 bug 时发送报告给自由软件
基金会。

PDF created with pdfFactory Pro trial version www.pdffactory.com


64 第3章

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.44
1 % grep –2 Patricia datafile
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
2 % grep –c Patricia datafile
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13

说明
1 打印匹配 Patricia 的行以及该行的上下各两行。
2 选项-C 跟选项-2 的功能是一样的。

实例 3.45

% grep –A 2 Patricia datafile


southeast SE Patricia Hemenway 4.0 .7 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13

说明
grep 打印匹配 Patricia 的行以及该行的下两行。

实例 3.46
% grep –B 2 Patricia datafile
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 15

说明
grep 打印匹配 Patricia 的行以及该行的上两行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 65

实例 3.47

% grep –b '[abc]' datafile


0:northwest NW Charles Main 3.0 .98 3 34
39:western WE Sharon Gray 5.3 .97 5 23
76:southwest SW Lewis Dalsass 2.7 .8 2 18
115:southern SO Suan Chin 5.1 .95 4 15
150:southeast SE Patricia Hemenway 4.0 .7 4 15
193:eastern EA TB Savage 4.4 .84 5 20
228:northeast NE AM Main Jr. 5.1 .94 3 13
266:north NO Margot Weber 4.5 .89 5 9
301:central CT Ann Stephens 5.7 .94 5 13

说明
选项-b 使得 grep 在打印每一行前打印字符偏移量。

下面我们没有用数据库文件,而是用一个叫作 negative 的文件来说明-e 和-x 选项的


用法。

% cat negative
-40 is cold.
This is line 1.
This is line 2.5
-alF are options to the ls command

实例 3.48

1 % grep –e '-alF' negative


-alF are options to the ls command

2 % grep –-regexp=-40 negative


-40 is cold.

说明
1 选项-e 使得 grep 平等的对待模板中所有的字符,这样开头的短横线才不会被误认为是选项。
2 当模板是正则表达式的时候,另一个选择是写成--regrxp=pattern 的形式;在这个例子中,正则表
达式是-40。

实例 3.49

% grep –x –e '-40 is cold.' negative


-40 is cold.

说明
选项-x 使得 grep 寻找整行匹配模板的行,选项-e 使得模板可以以“-”开头。

PDF created with pdfFactory Pro trial version www.pdffactory.com


66 第3章

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.50

1 % cat repatterns
western
north

2 % -grep -f repatterns datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9

说明
1 显示文件 repatterns 的内容,它们就是 grep 在后面对于输入文件要逐行搜索的东西,western 和
north 就是 grep 在后面搜索中使用的模板。
2 选项-f 后面紧跟着一个文件名,这告诉 grep 需要从这个文件中取得模板,然后在输入文件中逐
行搜索,寻找与模板匹配的行,最后打印所有包含模板 western 和 north 的行。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 3.51

1 % grep '[0-9]' datafile db


datafile:northwest NW Charles Main 3.0 .98 3 34
datafile:western WE Sharon Gray 5.3 .97 5 23
datafile:southwest SW Lewis Dalsass 2.7 .8 2 18
datafile:southern SO Suan Chin 5.1 .95 4 15
datafile:southeast SE Patricia Hemenway 4.0 .7 4 15
datafile:eastern EA TB Savage 4.4 .84 5 20

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 67

datafile:northeast NE AM Main Jr. 5.1 .94 3 13


datafile:north NO Margot Weber 4.5 .89 5 9
datafile:central CT Ann Stephens 5.7 .94 5 13
db:123

2 % grep –h '[0-9]' datafile db


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13
123

说明
1 如果被列出的文件不止一个,grep 就在每一行的前面追加文件名,在这个例子中是 datafile 和 db。
2 选项-h 使得 grep 不打印头信息,例如在这个例子中,就不打印文件名。

实例 3.52

% grep –q Charles datafile or grep –-quiet Charles datafile


% echo $status
0

说明
选项 quiet 取消 grep 所有的输出,在只需要退出状态值的场合这个选项就显得非常有用,如果
退出状态值是 0 则表示 grep 找到了与模板匹配的行。

3.6.2 标准 grep 回顾
表 3.8 包含了标准 grep 的例子和用法。
表 3.8 grep 回顾

命令 功能
grep '\<Tom\>' file 打印包含单词 Tom 的行
grep 'Tom savage' file 打印包含 Tom savage 的行
grep '^Tommy' file 打印以 Tommy 开头的行
grep '\.bak$' file 打印以\.bak 结束的行,单引号保护美元符号($)不作为模板的一部分
grep '[Pp]yramid' * 打印当前目录下所有文件中包含 Pyramid 或者 pyramid 的行
grep '[A-Z]' file 打印包含至少一个大写字母的行
grep '[0-9]' file 打印包含至少一个数字的行
grep '[A-Z]…[0-9]' file 打印包含 5 个字符,并以一个大写字符开头,及一个数字结束的字符串的行

PDF created with pdfFactory Pro trial version www.pdffactory.com


68 第3章

续表

命令 功能
grep -w '[tT]est' file 打印包含单词 Test 或者 test 的行
grep -s "Mark Todd" file 寻找包含 Mark Todd 的行,但是不打印行,而是用来检查退出状态值
grep -v 'Marry' file 打印所有不包含 Marry 的行
grep -i 'sam' file 打印所有包含 sam 的行,而不考虑大小写(如,SAM、sam、SaM、sAm)
grep -l 'Dear Boss' * 打印包含 Dear Boss 的文件的文件名清单
grep -n 'Tom' file 在打印的匹配行前追加行号
grep "$name" file 把变量$name 的值作为模板,在文件中寻找匹配模板的行。注意,必须使
用双引号
grep '$5' file 打印包含$5 的行,必须使用单引号
命令 ps -ef 的结果通过管道传递给 grep,grep 打印其中以 user1 开头(在
ps -ef|grep " ^ *user1"
user1 前有 0 个或者多个空格也可以)的行

3.6.3 回顾 egrep 和 grep -E


表 3.9 包含了 egrep 命令的例子和用法。
a
表 3.9 回顾 egrep

命令 功能
egrep '^ +' filefg 打印以一个或者多个空格开头的行
*egrep '^*' file 打印以 0 个或者多个空格开头的行
egrep '(Tom|Dan) Savage' file 打印包含 Tom savage 或者 San savage 的行
egrep '(ab)+' file 打印包含一个或者多个 ab 的行
egrep '^X[0-9]?' file 打印以 X 或者 X 后面跟着一个零或数字开头的行
*egrep 'fun\.$' * 从所有文件中打印以 fun.结束的行
egrep '[A-Z]+' file 打印包含至少一个大写字母的行
*egrep '[0-9]' file 打印包含至少一个数字的行
*egrep '[A-Z]…[0-9]' file 打印包含 5 个字符,并以一个大写字符开头,及一个数字结束的字符串的行
*egrep '[tT]est' files 打印包含单词 Test 或者 test 的行
*egrep "Susan Jean" file 打印包含 Susan Jean 的行
*egrep -v 'Marry' file 打印不包含 Marry 的行
*egrep -i 'sam' file 打印所有包含 sam 的行,而不考虑大小写(如 SAM、sam、SaM、sAm 等)
*egrep -l 'Dear Boss' * 打印所有包含 Dear Boss 的文件名清单
*egrep -n 'Tom' file 打印包含 Tom 的行,在每一行前面追加行号
*egrep -s "$name" file 把变量$name 的值作为模板,在文件中寻找匹配模板的行,但是不打印,
可以用做检测退出状态值
a. 星号表示,egrep 和 grep 以相同的方式处理该行命令中的模板。

PDF created with pdfFactory Pro trial version www.pdffactory.com


grep 家族 69

Linux 工具实验室 1

grep 和 egrep 练习

Steve Blenheim:238-923-7366:95 Latham Lane, Easton, PA 83755:11/12/56:20300


Betty Boop:245-836-8357:635 Cutesy Lane, Hollywood, CA 91464:6/23/23:14500
Igor Chevsky:385-375-8395:3567 Populus Place, Caldwell, NJ 23875:6/18/68:23400
Norma Corder:397-857-2735:74 Pine Street, Dearborn, MI 23874:3/28/45:245700
Jennifer Cowan:548-834-2348:583 Laurel Ave., Kingsville, TX 83745:10/1/35:58900
Jon DeLoach:408-253-3122:123 Park St., San Jose, CA 04086:7/25/53:85100
Karen Evich:284-758-2857:23 Edgecliff Place, Lincoln, NB 92743:7/25/53:85100
Karen Evich:284-758-2867:23 Edgecliff Place, Lincoln, NB 92743:1.1./3/35:58200
Karen Evich:284-758-2867:23 Edgecliff Place, Lincoln, NB 92743:11/3/35:58200
Fred Fardbarkle:674-843-1385:20 Parak Lane, DeLuth, MN 23850:4/12/23:780900
Fred Fardbarkle:674-843-1385:20 Parak Lane, DeLuth, MN 23850:4/12/23:780900
Lori Gortz:327-832-5728:3465 Mirlo Street, Peabody, MA 34756:10/2/65:35200
Paco Gutierrez:835-365-1284:454 Easy Street, Decatur, IL 75732:2/28/53:123500
Ephram Hardy:293-259-5395:235 CarltonLane, Joliet, IL 73858:8/12/20:56700
James Ikeda:834-938-8376:23445 Aster Ave., Allentown, NJ 83745:12/1/38:45000
Barbara Kertz:385-573-8326:832 Ponce Drive, Gary, IN 83756:12/1/46:268500
Lesley Kirstin:408-456-1234:4 Harvard Square, Boston, MA 02133:4/22/62:52600
William Kopf:846-836-2837:6937 Ware Road, Milton, PA 93756:9/21/46:43500
Sir Lancelot:837-835-8257:474 Camelot Boulevard, Bath, WY 28356:5/1/69:24500
Jesse Neal:408-233-8971:45 Rose Terrace, San Francisco, CA 92303:2/3/36:25000
Zippy Pinhead:834-823-8319:2356 Bizarro Ave., Farmount, IL 84357:1/1/67:89500
Arthur Putie:923-835-8745:23 Wimp Lane, Kensington, DL 38758:8/31/69:126000
Popeye Sailor:156-454-3322:945 Bluto Street, Anywhere, USA 29358:3/19/35:22350
Jose Santiago:385-898-8357:38 Fife Way, Abilene, TX 39673:1/5/58:95600
Tommy Savage:408-724-01.40:1222 Oxbow Court, Sunnyvale, CA 94087:5/19/66:34200
Yukio Takeshida:387-827-1095:13 Uno Lane, Ashville, NC 23556:7/3/29:57000
Vinh Tranh:438-910-7449:8235 Maple Street, Wilmington, VM 29085:9/23/63:68900
(这里提到的数据库在 CD 中名为 datebook)

1.打印你使用的 grep 的版本信息。


2.打印所有包含字符串 San 的行。
3.打印所有包含 CA 或者 ca 的行。
4.打印所有名字的第一个字母是 J 的行。
5.打印以 700 结束的所有的行以及该行的上下各两行。
6.打印所有不包含 834 的行。
7.打印所有生日是 December 的行。
8.打印所有电话地区号码是 408 的行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


70 第3章

9.打印所有这样的行,包含这样的字符串,第一个字母的大写字母,紧跟着 4 个小写
字母,然后是一个逗号,最后是一个大写字母。
10.打印所有以 K 或者 k 开头的姓的行。
11.打印所有薪水是 6 位数字的行,并每行的前面追加行号。
12.打印所有包含 Lincoln 或者 lincoln 的行(grep 对大小写是不敏感的) 。
13.打印所有第一个字母 3,紧跟着是短横线,然后是至少一个其他数字的字符串的行。
14.打印包含 Jesse 的行以及该行的前两行。
15.打印以模板 Yukio 或者 Vinh 开头的行。
16.把模板 San Francisco 和 Sir Lancelot 放入一个文件,grep 将从这个文件中取出模板,
在文件 databook 中搜索匹配的行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


第4章

流线式编辑器
——sed
4.1 什么是 sed
sed 命令是一个流线式的非交互式的编辑器。你可以用 sed 实现在 vi 和 ex 里一样的编辑
任务。与交互式的编辑器不同,sed 允许你在命令行输入命令和文件名,然后在屏幕上看命
令的输出结果。sed 编辑器是非破坏性的,在你用 Shell 重新定向存储输出以前,sed 不会改
变你的文件。在默认情况下,是所有行都打印到屏幕。
sed 编辑器对于 Shell 脚本来说是非常有用的,因为在使用交互式编辑器时,例如 vi 和
ex,需要脚本的使用者非常熟悉编辑器并且允许用户对打开的文件做不应该的修改。如果你
想同时启动多个 sed 拷贝,或者为 Shell 提示符下引用 sed 命令感到烦恼,也可以把 sed 命令
放在一个称为 sed 脚本的文件中。1

4.2 sed 版 本
Linux 使用的是 Gnu 版本的 sed,版权属于自由软件基金会。这个版本跟标准 UNIX 提
供的版本在功能上几乎完全一样。

实例 4.1

% sed –V or sed --version


GNU sed version 3.02

Copyright (C) 1998 Free Software Foundation, Inc.


This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE, to the extent permitted by law.

1.记住,当一个命令从命令行输入时,Shell 将试图识别每一个元字符甚空白,而任何命令行都无法识别的字符必须被引用
起来。

PDF created with pdfFactory Pro trial version www.pdffactory.com


72 第4章

4.3 sed 怎样工作?


sed 一次处理一行文件(或者输入)并把输出送往屏幕。它的命令是那些也许你已经在
vi 和 ed/ex 中熟悉的命令。sed 把当前处理的行存储在临时缓冲区中,称为模式空间(pattern
space) 。一旦 sed 完成对模式空间中的行的处理(也就是执行完这一行中的 sed 命令),模式
空间中的行就被送往屏幕(除非命令是删除行或者打印到打印机) 。行被处理完成以后,就
被移出模式空间,程序接着读入下一行,处理,显示,移出……文件输入的最后一行被处理
完以后 sed 结束。通过存储每一行在临时缓冲区,然后在缓冲区中操作该行,保证了原始文
件不会被破坏。

4.4 定 址
可以通过定址来判断那一行你所希望编辑的。可以通过数字、正则表达式或者二者结合
的方式定址。在没有指定地址的情况下,sed 处理输入文件的所有行。
当该地址是由数字构成时,数字表示的是行数,美元符号($)表示输入文件的最后一
行。用逗号分隔的两个行数表示以这两行为起止的行的范围(包括行数表示的那两行)。可
以通过数字、正则表达式或者二者结合的方式确定一个范围。
sed 命令告诉 sed 对行做什么样的处理:打印、删除或修改等等。

FORMAT
sed 'command' filename(s)

实例 4.2

1 % sed '1,3d' myfile

2 % sed –n '\[Jj]ohn/p' datafile

说明
1 打印文件 myfile 中除了 1、2 和 3 以外的所有行,或者删除 1、2 及 3 行。
2 只打印文件 myfile 中匹配模板 John 或者 john 的行。

4.5 命令和选项
sed 命令通过定位告诉 sed 如何处理每一行。如果没有给定地址,sed 将处理所有输入的
行。如果一个 sed 命令以!开头,则不论是什么操作,都将对所有的行发生作用,而不仅仅
是被选定行。%是 Shell 提示符。参考图 4.1 来了解 sed 命令及其作用,参考图 4.2,了解 sed
命令的选项以及它们是如何控制 sed 的行为。-h 选项可以显示一个命令行选项列表以及简单
的用法说明。

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 73

实例 4.3

% sed –h
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...

-n, --quiet, --silent


suppress automatic printing of pattern space
-e script, --expression=script
add the script to the commands to be executed
-f script-file, --file=script=file
add the contents of script-file to the commands to
be executed
--help display this help and exit
-V, --version output version information and exit

说明
如果没有-e、--explanation、-f 或者--file 选项,那么第一个非选项参数就被认为是需要翻译 sed
脚本。而其他的参数都被认为是输入文件的名字列表;如果没有指定输入文件名,那么就从标准输
入读取输入。

表 4.1 sed 命令

命令 功能
a\ 在当前行后面加入一行或者文本
b label 分支到脚本中带有标号的地方,如果标号不存在就分支到脚本的末尾
c\ 用新的文本改变或者替代本行的文本
d 从模板块(Pattern space)位置删除行
D 删除模板块的第一行
i\ 在当前行上面插入文本
h 拷贝模板块的内容到内存中的缓冲区
H 追加模板块的内容到内存中的缓冲区
g 获得内存缓冲区的内容,并替代当前模板块中的文本
G 获得内存缓冲区的内容,并追加到当前模板块文本的后面
l 列表不能打印字符的清单
n 读取下一个输入行,用下一个命令处理新的行而不是用第一个命令
N 追加下一个输入行到模板块后面并在二者之间嵌入一个新的行,改变当前行的号码
p 打印模板块的行
P 打印模板块的第一行
q 退出 sed
r file 从 file 中读行
t label if 分支,从最后一行开始,条件一旦被满足或者 T 命令或者 t 命令,将导致分支到
带有标号的命令处,或者到脚本的末尾
T label 错误分支,从最后一行开始,一旦发生错误或者 T 命令或者 t 命令,将导致分支到
带有标号的命令处,或者到脚本的末尾

PDF created with pdfFactory Pro trial version www.pdffactory.com


74 第4章

续表

命令 功能
w file 写并追模板块到 file 末尾
W file 写并追模板块的第一行到 file 末尾
! 表示后面的命令对所有没有被选定的行发生作用
s/re/string/ 用 string 替换正则表达式 re
= 打印当前行号码
#command 把注释扩展到下一个换行符以前
替换标记
g 行内全面替换
p 打印行
w 把行写入一个文件
x 互换模板块中的文本和缓冲区中的文本
y 把一个字符翻译为另外的字符(但是不能用于正则表达式)

表 4.2 sed 选项

选项 功能
-e command 允许多点编辑
--expression=command 同上
-h,--help 打印命令行选项摘要,并显示 bug 列表的地址
-n,--quiet,--silent 取消默认输出
-f, 引导 sed 脚本文件名
--filr=script-file 同上
-V,--version 打印版本和版权信息

当多个命令被使用或者地址在一定的地址范围内需要嵌套时,命令放在一对括号内,且
每一个命令占有独立的行,或者以半个括号结束。
感叹号可以用来翻转命令,例如:
% sed '/Tom/d' file

告诉 sed 删除所有包含模板 Tom 的行,而下面的命令:


% sed '/Tom/!d' file

告诉 sed 删除所有不包含模板 Tom 的行。


sed 选项有-e、-f 和-n。-e 用于命令行多点编辑,-f 用于引导脚本文件名,-n 用于取消打
印输出。

4.6 错误信息和退出状态
当 sed 发现语法错误时,就会直接将错误信息发送到标准错误输出。如果无法确定错误

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 75

出现在那里,sed 就会出现的错误信息,你可以从中推测错误出现的位置。sed 会返回一个退


出状态给 Shell,如果没有错误就返回 0,否则返回非 0 整数。2

实例 4.4
1 % sed '1,3v ' file
sed: -e expression #1,char 4: Unknown command: ''v''
% echo $status (echo $? If using sh or ksh shells)
2
2 % sed 'John ' file
sed: -e expression #1, char 5: Unterminated address regex
3 % sed 's/1235/g'file
sed: -e expression #1, char 7: Unterminated 's'command

说明
1 sed 不识别 v 命令,退出状态值是 2,表示出现了语法问题。
2 模板表达式需要成对闭合的斜杠。
3 替换命令 s 包含需要搜索的字符串,却没有用于替换的字符串。

4.6.1 元字符集
像 grep 一样,sed 支持一些特殊的用于控制模板搜索的元字符集。参考表 4.3。

表 4.3 sed 正则表达式元字符集

元字符 功能 例子 匹配什么
^ 锚定行的开始 /^love/ 匹配所有以 love 开头的行
$ 锚定行的结束 /love$/ 匹配所有以 love 结束的行
. 匹配一个非换行符的字符 /l..e/ 匹配所有包含 l 紧随两个任意字符,然
后是 e 的行
* 匹配零或多个字符 /*love/ 匹配所有模板是一个或多个空格后紧
跟 love 的行
[] 匹配一个指定范围内的字符 /[Ll]ove/ 匹配包含 Love 或 love 的行
[^] 匹配一个不在指定范围内的 /[^A-KM-Z]ove/ 匹配包含模板以 A~K 或 M~Z 的一个
字符 字母开头,紧跟 ove 的行
\(..\) 保存匹配的字符 s/\(love\)able/\1rs/ 标签标记出来的部分被保存在 1 号标签
中,为了方便在后面引用,使用\|来表
示,\|将被标签标记出来的模板替换,
从表达式的最左边开始,向右最多可以
使用 9 个标签。例如,love 被保存在寄
存器 1 当中,并稍后用做字符串替换。
loveable 就被替换为 lovers 3

2.参考 UNIX 下 sed 的手册页可以获得完整的错误信息清单。

PDF created with pdfFactory Pro trial version www.pdffactory.com


76 第4章

续表

元字符 功能 例子 匹配什么
& 保存搜索字符用来替换其他 s/love/**&**/ &表示搜索字符串,所以 love 就变为
字符 **love**
\< 锚定单词的开始 /\<love/ 匹配包含以 love 开头的单词的行
\> 锚定单词的结束 /love\>/ 匹配包含以 love 结尾的单词的行
x\{m\} 重复字符 X,M 次 /o\{5\}/ 匹配包含 5 个 o,或至少 5 个 o 或 5~10
x\{m,\} 重复字符 X,至少 M 次 个 o 的行
x\{m,n\} a 重复字符 X,至少 M 次,不
多于 N 次
a. 不能在所有版本的 UNIX 或模板匹配程序中独立使用;通常用在 vi 或 grep 中。

4.7 sed 实例
% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

4.7.1 打印:p 命令和-quiet 选项

实例 4.5
% sed '/north/p' datafile
northwest NW Charles Main 3.0 .98 3 34
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
默认打印所有的行到标准输出。如果模板 north 被找到,sed 除了打印所有的行以外,还要打印
匹配行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 77

实例 4.6

1 % sed –n '/north/p' datafile


northwest NW Charles Main 3.0 .98 3 34
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9

2 % sed –-quiet '/north/p' datafile


northwest NW Charles Main 3.0 .98 3 34
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9

说明
1 -n 选项取消了在使用-p 选项时 sed 的默认行为。在没有-n 的时候,包含模板的行将被打印两次,
但是在使用-n 的时候将只打印包含模板的行。
2 --quiet 选项的功能跟-n 是一样的。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

4.7.2 删除:d 命令

实例 4.7

% sed '3d' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
删除第三行。其他的行默认打印到屏幕。

实例 4.8

% sed '3,$d' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23

PDF created with pdfFactory Pro trial version www.pdffactory.com


78 第4章

说明
从第三行到最后一行都被删除。其他的行被打印。美元符号($)表示最后一行,逗号(.)被
称为范围操作符。在这个例子中,范围是从第三行开始到最后一行结束的。

实例 4.9

% sed '$d' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9

说明
删除最后一行。美元符号($)表示最后一行。默认打印除了那些被 d 命令删除的行以外的所有
行。

实例 4.10

% sed '/north/d' datafile


western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
central CT Ann Stevens 5.7 .94 5 13

说明
删除所有包含模板 north 的行,并打印其他行。

4.7.3 替换:s 命令

实例 4.11

% sed 's/west/north/g' datafile


northnorth NW Charles Main 3.0 .98 3 34
northern WE Sharon Gray 5.3 .97 5 23
southnorth SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 79

说明
s 命令表示替换。行末尾的 g 标志表示命令作用的范围是整个行。也就是说,如果找到多个
west,它们就都将被替换为 north。如果没有 g 标志,则只有每行第一个 west 被替换为 north。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 4.12

% sed –n 's/^west/north/p' datafile


northern WE Sharon Gray 5.3 .97 5 23

说明
s 命令表示替换。-n 选项和行末尾的 p 标志一起使用告诉 sed 只打印那些发生替换的行。也就是
说,如果某一行开头的 west 被替换为 north,那么就打印这行。

实例 4.13

% sed 's[0-9][0-9]$/&.5/' datafile


northwest NW Charles Main 3.0 .98 3 34.5
western WE Sharon Gray 5.3 .97 5 23.5
southwest SW Lewis Dalsass 2.7 .8 2 18.5
southern SO Suan Chin 5.1 .95 4 15.5
southeast SE Patricia Hemenway 4.0 .7 4 17.5
eastern EA TB Savage 4.4 .84 5 20.5
northeast NE AM Main Jr. 5.1 .94 3 13.5
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13.5

说明
&符号表示替换字符串中被找到的部分。所有以两个数字结束的行,最后的数字都将被它们自
己替换,同时追加.5。

实例 4.14

% sed -n's/Hemenway/Jones/gp' datafile


southeast SE Patricia Jones 4.0 .7 4 17

PDF created with pdfFactory Pro trial version www.pdffactory.com


80 第4章

说明
所有的 Hemenway 都被 jose 替换,只有发生变化的行才被打印。-n 选项与 p 命令一同使用,取
消默认输出。g 表示对该行进行全局替换。

实例 4.15

% sed -n's/\(Mar\)got/\lianne/p' datafile


north NO Marianne Weber 4.5 .89 5 9

说明
模板 Mar 被包含在一对括号内,并在特殊的寄存器中保存为标签 1(tag1),它将在后面作为\1
替换字符串。Margot 替换所有的 Marianne。

实例 4.16

% sed 's#3#88#g' datafile


northwest NW Charles Main 88.0 .98 88 884
western WE Sharon Gray 5.88 .97 5 288
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 88 188
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 188

说明
s 后面的字符是分隔搜索字符串和替换字符串的分隔符。默认分隔符是斜杠,但是在 s 命令使用
的情况下可以改变。不论什么字符紧跟着 s 命令都被认为是新的分隔符。这个技术在搜索包含斜杠
的模板时非常有用,例如搜索时间和路径的时候。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

4.7.4 选定行的范围:逗号

实例 4.17

% sed -n'/west/,/east/p' datafile

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 81

northwest NW Charles Main 3.0 .98 3 34


western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17

说明
所有在模板 west 和 east 所确定的范围内的行都被打印。如果 west 出现在 east 后面的行中,从 west
开始到下一个 east,无论这个 east 出现在那里,二者之间的行都将被打印,即使从 west 开始到了文件
的末尾如果还没有出现 east,那么从 west 到末尾的所有的行都将打印。箭头所表示的就是被打印的范
围。

实例 4.18
% sed -n'5,/^northeast/p' datafile
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13

说明
打印从第五行开始到第一个包含 northeast 的行之间的所有的行。

实例 4.19

% sed '/west/,/east/s/$/**VACA**/' datafile


northwest NW Charles Main 3.0 .98 3 34**VACA**
western WE Sharon Gray 5.3 .97 5 23**VACA**
southwest SW Lewis Dalsass 2.7 .8 2 18**VACA**
southern SO Suan Chin 5.1 .95 4 15**VACA**
southeast SE Patricia Hemenway 4.0 .7 4 17**VACA**
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
对于模板 east 和 west 之间的行,每行的末尾用字符串**VACA**替换,下一行都被移动到该字
符串的后面。箭头所表示的就是被选定的范围。

4.7.5 多点编辑:e 命令

实例 4.20

% sed –e 'l,3d'-e 's/Hemenway/Jones/' datafile


southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Jones 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


82 第4章

说明
-e 选项允许多点编辑。第一个编辑命令是删除第一到第三行。第二个编辑命令是用 Hemenway
替换 Jones。因为两个命令是在同一行执行(也就是说,两个命令都是在当前行的固定空间中执行) ,
所以命令的执行顺序将影响命令的结果。例如,如果两个命令都是替换命令,那么第一个替换命令
将影响第二个替换命令的结果。

实例 4.21

% sed -–expression='s/TB/Tobias/' -–expression='/north/d' datafile


western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 15
eastern EA Tobias Savage 4.4 .84 5 20
central CT Ann Stephens 5.7 .94 5 13

说明
-e 选项允许多点编辑。一个描述能力更强的选项是 – expression,它给 sed 表达式赋值。第一个
编辑命令告诉 sed 用字符串 Tobias 替换正则表达式 TB。第二个编辑命令表示删除所有包含 north 的
行。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

4.7.6 从文件读入:r 命令

实例 4.22

% cat newfile
------------------------------------
***SUAN HAS LEFT THE COMPANY***
—————————————————————
% sed '/Suan/r newfile' datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
------------------------------------
***SUAN HAS LEFT THE COMPANY***
—————————————————————
southeast SE Patricia Hemenway 4.0 .7 4 17

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 83

eastern EA TB Savage 4.4 .84 5 20


northeast NE AM Main 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
命令 r 表示从文件中读取指定的行。newfile 文件的内容被读入输入文件 datafile 中,显示在与
suzan 相匹配的行的后面。如果 suzan 在不止一行中出现,newfile 的内容就将显示在所有匹配行的下
面。

4.7.7 写入文件:w 命令
实例 4.23

% sed –n '/north/w newfile2' datafile


cat newfile2
northwest NW Charles Main 3.0 .98 3 34
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9

说明
命令 w 表示把一个指定的行写入文件。所有包含 north 的行都将被写入名为 newfile2 的文件中。

4.7.8 追加:a 命令
实例 4.24

% sed '/^north /a\\


--->THE NORTH SALES DISTRICT HAS MOVED<---' datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
--->THE NORTH SALES DISTRICT HAS MOVED<---
central CT Ann Stephens 5.7 .94 5 13

说明
命令 a 表示追加。字符串--->THE NORTH SALES DISTRICT HAS MOVED <---将被追加到以
north 开头,且 north 后面有空格的行的后面。需要被追加的文本必须写在命令行中追加命令的后面。
sed 要求在命令 a 后面有一个反斜杠。第二个反斜杠表示在 TC Shell 下转义换行符,这样才能在
下一行完成引用。a 如果被追加的不止一行,除了最后一行,每一行也都要用反斜杠结束。

a.bash、Bourne 和 Korn Shell 不需要第二个反斜杠来转义换行符,因为它们不需要在同一行匹配引用。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23

PDF created with pdfFactory Pro trial version www.pdffactory.com


84 第4章

southwest SW Lewis Dalsass 2.7 .8 2 18


southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

4.7.9 插入:i 命令

实例 4.25

% sed '/eastern/i\\
NEW ENGLAND REGION
--------------------------------' datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
NEW ENGLAND REGION
--------------------------------
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
命令 i 是插入命令。如果模板 eastern 被匹配,i 命令就把反斜杠后面的文本插入到包含 eastern
的行的前面。除了最后一行,每一行插入完成后都需要反斜杠(额外的反斜杠是用来满足 TC Shell
的要求的) 。

4.7.10 下一个:n 命令

实例 4.26

% sed '/eastern /{ n; s/AM/Archie/; }' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE Archie Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
如果模板 eastern 匹配了某一行,n 命令就移动到输入文件中该行的下一行,替换这一行的模式
空间。即用 Archie 替换 AM,打印该行,然后再继续。

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 85

4.7.11 变形:y 命令
实例 4.27

% sed '1,3y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKL
MNOPQRSTUVWXYZ/' datafile
NORTHWEST NW CHARLES MAIN 3.0 .98 3 34
WESTERN WE SHARON GRAY 5.3 .97 5 23
SOUTHWEST SW LEWIS DALSASS 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

说明
对于 1~3 行,y 命令把所有的小写字符转变为大写字符。正则表达式元字符不能使用这个命令。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

4.7.12 退出:q 命令
实例 4.28
% sed '5q' datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway .0 .7 4 17

说明
打印完第五行后,q 命令使得 sed 程序退出。

实例 4.29
% sed '/Lewis/{ s/Lewis/Joseph/;q; }' datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Joseph Dalsass 2.7 .8 2 18

PDF created with pdfFactory Pro trial version www.pdffactory.com


86 第4章

说明
当模板 Lewis 在某一行被匹配,替换命令首先将 Lewis 替换为 Joseph,
然后再用 q 命令退出 sed 程序。

4.7.13 保持和获取:h 命令和 g 命令

实例 4.30

% sed -e '/northeast/h' -e '$G' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13
northeast NE AM Main Jr. 5.1 .94 3 13

说明

在 sed 处理的文件的时候,每一行都被保存在一个叫作模式空间(pattern space)的临时缓冲区


中。除非行被删除或者输出被取消,否则所有被处理过的行都将打印在屏幕上。接着模式空间被清
空,并存入新的一行等待处理。在这个例子中,包含模板的 northeast 行被找到,并被放入模式空间
中,h 命令将其复制并存入一个称为保持缓冲区(holding buffer)的特殊缓冲区内。在第二个 sed 结
构中,当达到最后一行后,G 命令告诉 sed 从保持缓冲区中取得该行,然后把它放回到模式空间中,
且追加到现在已经存在于模式空间中的行的末尾——在这个例子中,就是追加到最后一行。简单来
讲:任何包含模板 northeast 的行都将被复制并追加到该文件的末尾。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 4.31

% sed –e '/WE/{h;d; } '-e '/CT/{G; }' datafile


northwest NW Charles Main 3.0 .98 3 34
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 87

north NO Margot Weber 4.5 .89 5 9


central CT Ann Stephens 5.7 .94 5 13
western WE Sharon Gray 5.3 .97 5 23

说明
如果模板 WE 在某一行中被匹配,h 命令将使得该行被从模式空间中复制并放入保持缓冲区中。
当存储到保持缓冲区中以后,该行还可以被重新利用(通过 G 命令或者 g 命令) 。在这个例子中,模
板 WE 被匹配以后,该行被放入保持缓冲区内。命令 d 在模式空间中删除该行。第二个命令搜索 CT,
一旦被找到, (通过 G 命令)sed 就从保持缓冲区中取回行,并追加到当前模式空间中行的末尾。简单
地说:包含 WE 是行被移动并追加到包含 CT 的行的后面 (参考“保持和互换:h 命令和 x 命令”
)。

删除
模式空间

保持缓冲区

g 以前 n

替换模式空间

g 以后

新模式空间

图 4.1 模式空间和保持缓冲区。参见实例 4.33

实例 4.32
% sed –e '/northeast/h' -e '$q' datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
northeast NE AM Main Jr. 5.1 .94 3 13

说明
sed 所处理文件的每一行都被保存在一个叫作模式空间(pattern space)的临时缓冲区中。除非
该行被删除或者输出被取消,否则所有被处理过的行都将打印在屏幕上。接着模式空间被清空,然
后存入新的一行等待处理。在这个例子中,包含模板的 northeast 行被找到,并被放入模式空间中,h
命令把它复制并存入一个叫作的保持缓冲区(holding buffer)的特殊缓冲区内。在第二个 sed 结构中,
当达到最后一行后,g 命令告诉 sed 从保持缓冲区中取得行,并把它放回到模式空间中,以替换已经
存在于模式空间中的行——在这个例子中,就是替换最后一行。简单地说:包含模板 northeast 的行
被复制并覆盖了文件的末尾。

PDF created with pdfFactory Pro trial version www.pdffactory.com


88 第4章

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

实例 4.33

% sed –e '/WE/{h;d; }'-e '/CT/{g; }' datafile


northwest NW Charles Main 3.0 .98 3 34
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
western WE Sharon Gray 5.3 .97 5 23

说明
如果模板 WE 被匹配,h 命令就把该行拷贝到保持缓冲区中,d 命令在模式空间中删除该行。当
模板 CT 被匹配,g 取得保持缓冲区中的行,并覆盖当前存储在模式空间中的行。简单地说,任何包
含模板 northeast 的行都将被复制,并覆盖包含 CT 的行。

4.7.14 保持和互换:h 命令和 x 命令


实例 4.34

% sed –e '/patricia/h' -e '/Margot/x' datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
southeast SE Patricia Hemenway 4.0 .7 4 17
central CT Ann Stephens 5.7 .94 5 13

说明
命令 x 表示互换模式空间和保持缓冲区的内容。当模板 patricia 被匹配,该行将被保存到保持缓
冲区中。当包含 Margot 的行被找到,模式空间将与保持缓冲区交换彼此的行。简单地说,就是包含
Margot 的行被包含 patricia 的行替换了。

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 89

4.8 sed 脚 本
sed 脚本就是一个文件中的 sed 命令的清单,为了让 sed 了解文件中的命令,当在命令
行启动 sed 时,用-f 选项引导 sed 脚本文件名。sed 对于脚本中输入的命令非常挑剔,在命令
的末尾不能有任何空白或者文本。如果在一行中有多个命令,那么就要用分号分隔它们。当
把一行 sed 脚本从输入文件复制到模式空间后,sed 脚本中的所有命令都将对这行起作用。
当该行被处理完后,输入文件的下一行就被存储到模式空间的缓冲区中,而所有 sed 脚本中
的命令又都将对这行起作用。如果你的语法有错误,sed 就会错乱。
sed 脚本的好处就是不必担心与 Shell 命令行的交互问题。我们不需要引用 sed 命令来防
止它被 Shell 翻译。事实上,你根本不能在 sed 脚本中使用引用,除非它们是搜索模板的一
部分。

% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

4.8.1 sed 脚本实例


实例 4.35

% cat sedding1 (Look at the contents of the sed script.)


1 # My first sed script by Jack Sprat
2 /Lewis/a\
3 Lewis is the TOP Salesperson for April!!\
Lewis is moving to the southern district next month,\
4 CONGRATULATIONS!
5 /Margot/c\
******************\
MARGOT HAS RETIRED\
******************
6 1i\
EMPLOYEE DATABASE\
---------------------
7 $d
% sed –f sedding1 datafile (Execute the sed script commands;the
input file is datafile.)
EMPLOYEE DATABASE
---------------------
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18

PDF created with pdfFactory Pro trial version www.pdffactory.com


90 第4章

Lewis is the TOP Salesperson for April!!\


Lewis is moving to the southern district next month
CONGRATULATIONS!
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13

******************
MARGOT HAS RETIRED
********************

说明
1 这行是注释。注释必须是由#引导的单独的行(即不能跨行) 。
2 如果某一行包含模板 Lewis,那么下三行就追加到这行的后面。
3 每一个被追加的行,除了最后一行,都用反斜杠结束。反斜杠的后面必须紧跟着换行符。如果出
现文本,哪怕只是一个空格,sed 也会出现错误。
4 需要被追加的最后一行不需要反斜杠结束。这就告诉 sed:这是需要处理的最后一行了,下一行
是其他命令。
5 任何包含模板 Margot 的行的内容都将被下三行的文本替换(c 命令)

6 下两行将被插入到第一行的前面(i 命令) 。
7 最后一行被删除。

实例 4.36

% cat sedding2 (Look at the contents of the sed script.)


# This script demonstrates the use of curly braces to nest addresses
# and commands. Comments are preceded by a pound sign (#) and must
# be on a line by themselves. Commands are terminated with a newline
# or semicolon. If, using the Unix version of sed, there is any
# text after a command, even one space, you receive an error message:
# sed: Extra text at end of command:

1 /western/, /southeast/{
/^ *$/d
/Suan/{ h; d; }
}
2 /Ann/g
3 s/TB \(Savage\)/Thomas \1/

% sed –f sedding2 datafile


northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southeast SE Patricia Hemenway 4.0 .7 4 17
eastern EA Thomas Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
southern SO Suan Chin 5.1 .95 4 15

说明
1 对于以 western 开始并以 southeast 结束的范围内的行,空白行将被删除,匹配模板 Suan 的行将
被从模式空间中复制到保持缓冲区中,然后再从模式空间删除。

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 91

2 当模板 Ann 被匹配,g 命令复制保持缓冲区中的行到模式空间中,并覆盖模式空间中的内容。


3 所有包含模板 TB Savage 的行都被 Thomas 和被标记的模板内容 Savage 替换。在搜索字符串中,
Savage 被括在被换码的括号中间,成为被标记的字符串,所以它可以被重复使用,它的标记号码
是 1,可以通过\1 引用。

实例 4.37

1 % cat bye4now (Display the contents of the sed script.)


# The sed script
$a\
Thankyou for coming
bye

2 % sed –e 's/Charles/Jimmy/' –f bye4now datafile


northwest NW Jimmy Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

Thankyou for coming


bye

3 % sed –-file=bye4now --expression='s/Charles/Jimmy/' datafile


northwest NW Jimmy Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
southwest SW Lewis Dalsass 2.7 .8 2 18
southern SO Suan Chin 5.1 .95 4 15
southeast SE Patricia Hemenway 4.0 .7 4 15
eastern EA TB Savage 4.4 .84 5 20
northeast NE AM Main Jr. 5.1 .94 3 13
north NO Margot Weber 4.5 .89 5 9
central CT Ann Stephens 5.7 .94 5 13

Thankyou for coming


bye

说明
1 一个被称为 bye4nowSed 脚本的内容显示出来。$a 的意思是追加后面的文本到输入文件的最后一
行。
2 -e 选项用来给 sed 替换命令赋值。也就是说, 用 Jimmy 替换 Charles。-f 选项用来从 sed 脚本 bye4now
中读取命令。
3 -f 的长格式选项是-file,用来引导 sed 脚本文件的名字,紧跟着-e 的长格式选项,--expression 和
sed 替换命令。

4.8.2 sed 回顾
表 4.4 是 sed 命令和功能清单。

PDF created with pdfFactory Pro trial version www.pdffactory.com


92 第4章

表 4.4 回顾 sed

命令 功能
sed –n ‘/sentimental/p’filex 打印所有包含 sentimental 的行到屏幕。文件 filex 并没有变化。如
果没有-n 选项,所有包含 sentimental 的行都将被打印两次
sed ‘1,3d’ filex>newfilex 从文件 filex 中删除 1、2、3 行,并把变化保存到文件 newfilex 中
sed ‘/[Dd]aniel/d’ filex 删除包含 Daniel 或者 deniel 的行
sed –n ’15,20p’ filex 打印第 15 行~第 20 行
sed ‘1,10s/Montana/MT/g’ filex 把从第 1 行~第 20 行范围内所有的 Montana 替换为 MT
sed ‘March/\!d’ filex(tcsh) 删除所有不包含 March 的行(tcsh 中的反斜杠用来避免跟历史字
sed’/March/!d’ filex(sh) 符混淆)
sed ‘/report/s/5/8’ filex 在所有包含 report 的行中,第一个出现的 5 都将被替换成 8
sed ‘s/….//’ filex 删除每一行的前 4 个字符。也就是用空字符串替换每一行的前 4
个字符
sed ‘s/…$//’ filex 删除每一行的最后 3 个字符。也就是用空字符串替换每一行的最
后 3 个字符
sed ‘/east/,/west/s/North/South/’ filex 所有从 east 到 west 之间的行中的 North 都被替换为 Sourth
sed –n ‘/Time off/w timefile’ filex 把所有包含 Time off 的行写入到新文件 timefile 中
sed ‘s/\([Oo]ccur\)ence/\lrence/’ file 用 Occurrence 或者 occurrence 替换 Occurrence 和 occurrence
sed –n ‘l’ filex 打印所有包含不能打印字符的行,这些不能打印的字符显示为
\nn,nn 是字符的八进制值,制表符显示为>

Linux 工具实验室 2

sed 练习
Steve Blenheim:238-923-7366:95 Latham Lane, Easton, PA 83755:11/12/56:20300
Betty Boop:245-836-8357:635 Cutesy Lane, Hollywood, CA 91464:6/23/23:14500
Igor Chevsky:385-375-8395:3567 Populus Place, Caldwell, NJ 23875:6/18/68:23400
Norma Corder:397-857-2735:74 Pine Street, Dearborn, MI 23874:3/28/45:245700
Jennifer Cowan:548-834-2348:583 Laurel Ave., Kingsville, TX 83745:10/1/35:58900
Jon DeLoach:408-253-3122:l23 Park St., San Jose, CA 04086:7/25/53:85100
Karen Evich:284-758-2857:23 Edgecliff Place, Lincoln, NB 92743:7/25/53:85100
Karen Evich:284-758-2867:23 Edgecliff Place, Lincoln, NB 92743:11/3/35:58200
Karen Evich:284-758-2867:23 Edgecliff Place, Lincoln, NB 92743:11/3/35:58200
Fred Fardbarkle:674-843-1385:20 Parak Lane, DeLuth, MN 23850:4/:12/23:780900
Fred Fardbarkle:674-843-1385:20 Parak Lane, DeLuth, MN 23850:4/:12/23:780900
Lori Gortz:327-832-5728:3465 Mirlo Street, Peabody, MA 34756:10/2/65:35200
Paco Gutierrez:835-365-1284:454 Easy Street, Decatur, IL 75732:2/28/53:123500
Ephram Hardy:293-259-5395:235 CarltonLane, Joliet, IL 73858:8/12/20:56700
James Ikeda:834-938-8376:23445 Aster Ave., Allentown, NJ 83745:12/1/38:45000
Barbara Kertz:385-573-8326:832 Ponce Drive, Gary, IN 83756:12/1/46:268500
Lesley Kirstin:408-456-1234:4 Harvard Square, Boston, MA 02133:4/22/62:52600

PDF created with pdfFactory Pro trial version www.pdffactory.com


流线式编辑器——sed 93

William Kopf:846-836-2837:6937 Ware Road, Milton, PA 93756:9/21/46:43500


Sir Lancelot:837-835-8257:474 Camelot Boulevard, Bath, WY 28356:5/13/69:24500
Jesse Neal:408-233-8971:45 Rose Terrace, San Francisco, CA 92303:2/3/36:25000
Zippy Pinhead:834-823-8319:2356 Bizarro Ave., Farmount, IL 84357:1/1/67:89500
Arthur Putie:923-835-8745:23 Wimp Lane, Kensington, DL 38758:8/31/69:126000
Popeye Sailor:156-454-3322:945 Bluto Street, Anywhere, USA 29358:3/:19/35:22350
Jose Santiago:385-898-8357:38 Fife Way, Abilene, TX 39673:1/5/58:95600
Tommy Savage:408-724-0140:1222 Oxbow Court, Sunnyvale, CA 94087:5/19/66:34200
Yukio Takeshida:387-827-1095:13 Uno Lane, Ashville, NC 23556:7/1/29:57000
Vinh Tranh:438-910-7449:8235 Maple Street, Wilmington, VM 29085:9/23/63:68900
(这里提到的数据库在 CD 中称为 datebook)

1.打印你正在使用的 sed 的版本和版权信息。


2.取消默认输出的三种方法。
3.哪一个选项能显示 sed 命令行选项以及它们的功能?
4.如果你找到了某个版本的 sed 的 bug,应该把消息发送到那里?
5.把名字 Fred’s 改为 Frederick。
6.删除前三行。
7.打印第 6 行~第 12 行。
8.删除所有包含 Lane 的行。
9.打印所有生日在 November 或者 December 的行。
10.在以 Karen 开头的行的末尾追加三个星。
11.把包含 jose 的行替换为 JOSE HAS RETIRED。
12.把 Pepeye’s 的生日改为 11/14/46。
13.删除所有空白行。
14.互换包含 paco 和包含 vinh 的行。
15.移动以 Tommy 开头的行到文件的末尾,并覆盖文件的最后一行。
16.写一个 sed 脚本,要求具备以下功能:
a.在第一行前面插入标题 PERSONAL FILE
b.删除薪水末尾是 500 的行
c.调换姓和名,然后打印文件内容
d.在文件末尾追加 THE END

PDF created with pdfFactory Pro trial version www.pdffactory.com


第5章

gawk 实用程序:
Linux 工具——gawk
5.1 什么是 awk,什么是 nawk,什么是 gawk?
awk 是 Linux/UNIX 下的用来操纵数据和产生报告的程序语言。Nawk 是新的版本,gawk
是 Gnu 版本。数据可以来自标准输入、一个或者多个文件,或者其他命令的输出。awk 可用
于命令行的简单操作,也可以写入大的应用程序。因为 awk 可以操纵数据,所以它是 Shell
脚本和管理小型数据库中必需的工具。
awk 逐行扫描文件,从第一行到最后一行,寻找匹配特定模板的行,并在这些行上运行
“选择”动作。如果一个模板没有指定动作,这些匹配的行就被显示在屏幕上。如果一个动
作没有模板,所有被动作指定的行都被处理。
gawk 是 Gnu awk 程序语言工程的成果。它遵守 POSIX 1003.2 关于命令语言的定义和实
用程序标准,以及 Aho、Kernighan 和 Weinberger 对于 awk 的定义。gawk 提供了 Bell 实验
室 awk 扩展和一些 Gnu 的最新扩展。

5.1.1 awk 的含义


awk 分别代表其作者姓氏的第一个字母。 它的作者是 Alfred Aho、Brian Kernighan 和 Peter
Weinberger。他们可以叫它为 wak 或 kaw,但是由于不明原因最后选择 awk。gawk 也是表示
同样的意思,只是 Gnu 的版本而已。

5.1.2 用哪一个 awk?


这么多的 awk 版本,旧 awk、新的 awk、Gnu awk 以及 POSIX awk 等等。awk 写于 1977
年,1985 年的升级使得它可以处理大型程序。加入了用户定义函数、动态正则表达式和处
理输入文件等功能。在大多数的系统上,命令 awk 指的是旧版本,nawk 是新版本,而 gawk
是 Gnu 版本。1本章基于 gawk,这个版本的 awk 是 Linux 发行套件的一部分。--version 选项
可以显示版本信息。

1.在 Red Hat Linux 中,gawk 是连接到 awk 的。这个版本主要是新 awk,nawk 带上 Gnu 扩展。

PDF created with pdfFactory Pro trial version www.pdffactory.com


96 第5章

实例 5.1

% awk --version
GNU Awk 3.0.3
Copyright (C) 1989, 1991-1997 Free Software Foundation.

This program is free software; you can redistribute it and/or modify


it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,


but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple place – Suite 330, Boston, MA 02111-
1307,USA.

在很多系统中,awk 被连接到正在使用的版本中。在描述 awk 和其特性时,我们将使用


awk 命令,假设它已经被连接到 gawk 上了,就像下面的例子一样。

实例 5.2

% ls –l /bin/awk
lrwxrwxrwx 1 root root 4 May 12 04:47 /bin/awk -> gawk

5.2 awk 的格式


一个 awk 程序包括 awk 命令、程序结构、引用(或输入文件)和输入文件的名字。如
果输入文件没有被指定,输入就来自标准输入或键盘。
awk 结构由模板、动作或者模板和动作联合组成。模板是由某种类型表达式的说明组成。
如果没看见关键字 if,但是你在评价表达式的时候想到了单词 if,这就是模板。动作是在大
括弧内被分号或者新的一行分隔的一个或者多个说明。模板不能够放在大括弧中,由在斜杠
中间的正则表达式和 awk 提供的操作中的一个或者几个组成。
awk 命令可以从命令行中输入,也可以从 awk 脚本中输入。输入行可以来自文件、管道
和标准输入。

5.2.1 从文件输入
在下面的例子中,%表示 C Shell 的提示符。

格式
% awk 'pattern' filename

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 97

% awk '{action}' filename


% awk 'pattern' {action}' filename

这里有一个称为 employees 的文件。

实例 5.3

% cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '/Mary/' employees


Mary Adams 5346 11/4/63 28765

说明
awk 打印所有包含模板 Mary 的行。

实例 5.4

% cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '{print $1}' employees


Tom
Mary
Sally
Billy

说明
awk 打印文件 employees 的第一个域,这个域在每一行的开始,并由空格与其他域分隔。

实例 5.5

% cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '/Sally/{print $1, $2}' employees


Sally Chang

说明
awk 打印文件 employees 的第一个和第二个域中包含模板 sally 的行,记住,域是以空白分隔的。

PDF created with pdfFactory Pro trial version www.pdffactory.com


98 第5章

5.2.2 从命令输入
Linux 命令的输出可以通过管道由 awk 处理。Shell 程序通常用 awk 处理命令。

格式

% command | awk 'pattern'


% command | awk '{action}'
% command | awk 'pattern {action}'

实例 5.6

1 % df | awk '$4 > 75000'


/oracle (/dev/dsk/c0t0d057 ):390780 blocks 105756 files
/opt (/dev/dsk/c0t0d058):1943994 blocks 49187 files

2 % rusers | awk '/root$/{print $1}'


ow1
crow
bluebird

说明
1 df 报告文件系统中的剩余空间。df 命令输出通过管道传送给 awk。如果第四个域中的块多于
75000,则打印该行。
2 rusers 命令打印那些通过网络在远程机器上的登录。ruers 命令的输出通过管道传送给 awk 作为
awk 的输入。如果正则表达式 root 匹配行的末尾,该行的第一域就被打印。也就是 root 登录过的
所有机器的名字都将被打印。

5.2.3 awk 命令行选项


awk 有很多的命令行选项。gawk 有两种命令行选项格式:Gnu 长格式以双横线开始(--),
后面紧跟着一个单词;短的传统 POSIX 格式,由一个横线和一个字母组成。grep 使用-W 选
项或者相应的长格式。任何提供给长选项的参数或者用=连接(中间没有空格) ,或者在下一
命令行的参数内提供。--help 选项显示 awk 的所有选项列表。参考表 5.1。

实例 5.7

% awk –help
Usage: awk [POSIX or GNU style options] –f progfile [--] file ...
awk [POSIX or GNU style options] [--] 'program’ file ...
POSIX options: GNU long options
-f progfile --file=progfile
-F fs --field-separator=fs
-v var=val --assign=var=val
-m(fr) val
-W compat --compat
-W copyleft --copyleft
-W copyright --copyright
-W help --help
-W lint --lint

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 99

-W lint-old --lint-old
-W posix --posix
-W re-interval --re-interval
-W source=program=text --source=program=text
-W traditional --traditional
-W usage --usage
-W version --version

Report bugs to bug-gnu-utils@prep.ai.mit.edu,


with a Cc: to arnold@gnu.ai.mit.edu

表 5.1 gawk 命令行选项

选项 含义
-F fs, 指定输入文件的分隔符,fs 是一个字符串或者一个正则表达式
--field-separator fs FS=“:”或者 FS=“[\t]”
-v var=value, 赋值一个用户定义的变量,执行在 awk 脚本前 var,这样在 BEGIN 块中变
--asign var=value 量就是合法的了
-f scriptfile 从脚本文件中读取 awk 命令
--file scriptfile
-mf nnn 对于 nnn 的值设置内存限制。-mf 选项限制分配给 nnn 的最大块数目;-mr
-mr nnn 选项限制记录的最大数目。但在 awk 中无法执行
-W traditional 由于在兼容模式中运行,所以 gawk 的行为跟标准 UNIX 下的 awk 完全一
-W compact 样,所有的 awk 扩展均被忽略。两种模式的行为是一样的,--traditonal 选
--traditional 项是首选的
--compat
-W copyleft 打印简短的版权信息
-W copyright
--copyleft
-W help 打印全部的 awk 选项和每一个选项的简短说明
-W usage
--help
--usage
-W lint 打印关于不能向传统 UNIX 平台移植的结构的警告
--lint
-W lint-old 打印关于不能向原始 UNIX 移植的结构的警告
--lint-old
-W posix 打开兼容模式。不识别:\x、函数关键字、func、换码序列以及当 FS 是一
--posix 个单个空格时,将新行作为一个域分隔字符。操作符**和**=代替^和^=和
fflush
-W re-interval 允许间隔正则表达式的使用(参考“Posix 字符集”)例如括号表达式
--re-interval [[:alpha:]]
-W source program-text 使用 program-text 作为源代码,与命令行 awk 命令与-f 文件混合
--source program-text 例如 awk -W source ‘{print $ 1}-f cmdfile inputfile
-W version 打印 BUG 报告信息的版本
--version
-- 选项处理的结束信号

PDF created with pdfFactory Pro trial version www.pdffactory.com


100 第5章

5.3 格 式 输 出

5.3.1 打印函数
awk 命令的动作部分是在小括号内的。如果没有指定动作并且有模板被匹配,awk 就运
行默认动作, 将所有匹配的行打印到屏幕。print 函数用来打印简单且不需要特殊格式的输出。
要打印更为复杂和精确的格式,就需要使用 printf 和 sprintf 函数了。如果你熟悉 C 语言,就
一定已经知道它们是怎样工作的了。
print 函数也可以直接用在 awk 命令的动作部分(print) 。print 函数的参数可以是变量、
数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联
在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空
格而已。
print 函数的输出可以通过管道重新定向给其他的程序,而其他程序的输出也可以通过管
道重新定向给 awk 的打印函数。

实例 5.8

% date
Wed Jan 12 22:23:16 PST 2000

% date | awk '{ print "Month: " $2 "\nYear: " , $6 }'


Month:Jan
Year:2000

说明
Linux 的 date 命令的输出通过管道给 awk。字符串 Month:被打印,后面跟着包含换行符“\n”
的秒域和 Year:最后是第六域($6) 。

换码序列。换码序列表现为一个反斜杠后面紧跟着一个字母或者数字。它们被用在字符
串中表示表格、新行及换页等等(参见表 5.2)。
表 5.2 换码序列

换码序列 含义
\b Backspace
\f 换页
\n 新行
\r 回车
\t 制表符
\047 十进制 47,表示单引号
\c c 表示其他任何字符

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 101

实例 5.9

Tom Jones 4424 5/12/66 543354


Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '/Sally/{print "\t\tHave a nice day, " $1, $2 "\!"}' employees


Have a nice day, Sally Chang!

说明
如果某行包含模板 Sally,print 函数就打印两个制表符、字符串 Have a nice day、第一个域(用
$1 表示)和第二个域(用$2 表示)后面紧跟着一个包含两个感叹号的字符串。

5.3.2 OFMT 变量
在打印数字的时候你也许想控制数字的格式,我们通常用 printf 来完成这个功能。awk
的特殊变量 OFMT 也可以在使用 print 函数的时候,控制数字的打印格式。它的默认值是
“%.6g”——小数点后面 6 位将被打印(下面的内容将讲述如何改变该变量) 。

实例 5.10

% awk 'BEGIN{OFMT="%.2f"; Print 1.2456789, 12E-2}'


1.25 0.12

说明
OFMT 变量被设置为浮点数(f)的小数点后面两位被打印。百分号(%)用来说明指定的格式。

5.3.3 printf 函数
在打印输出的时候,你可能想指定一些空格用来分隔各个域,以便使得列显得更简洁清
晰。因为带有制表符的函数不能总是保证希望的输出,于是 printf 函数就用来格式化格式复
杂的输出。
printf 函数返回一个打印到屏幕的格式化好的字符串,就像 C 语言中关于 printf 的说明。
printf 函数由被引用的控制字符串组成,这个控制字符串可以嵌入指定格式和改变量。控制
字符串紧跟着一个逗号和一串由逗号分隔的表达式,这些表达式将按照控制字符串指定的形
式被格式化。跟 print 函数不同,printf 函数不提供换行符,如果需要换行就必须写换码序列,
\n。
每一个百分号和格式符号都必须有相应的参数,打印文字百分数则必须使用 2 个百分
号。参考表 5.3——转换字符清单和表 5.4——printf 函数的编辑符。格式符号一般放在百分号
的前面,参考表 5.5——格式符号清单。
当一个参数被打印,打印输出的地方被称为域,域的宽度是指域能容纳多少个字符。
下面例子中的管道符号(垂线) ,作为需要打印字符的一部分,表示格式开始和结束的位
置。

PDF created with pdfFactory Pro trial version www.pdffactory.com


102 第5章

实例 5.11

1 % echo "Linux" | awk '{printf "|%-15s|\n", $1}'


(Output)
|Linux |

2 % echo "Linux" | awk '{ printf "|%15s|\n", $1}'


(Output)
∕ Linux∕

说明
1 echo 命令的结果,Linux 通过管道传输给 awk。printf 函数包含一个控制字符串。百分号使得
printf 打印 15 个空格,双竖线之间的字符串左对齐,最后以换行符结束。百分号后面的破折号表
示左对齐。控制符在逗号和$1 后面。字符串 Linux 按照控制字符的控制打印出来。
2 按照右对齐的方式打印字符串 Linux,15 个空格,用双竖线括起来,并以换行符结束。

实例 5.12

% cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '{printf "The name is: %-15s ID is %8d\n", $1, $3}' employees
The name is Tom ID is 4424
The name is Mary ID is 5346
The name is Sally ID is 1654
The name is Billy ID is 1683

说明
将打印的字符串包含在双引号中。第一个格式说明符%-15s。它有一个相应的参数$1,在逗号左
边括号右边。百分号是一个格式说明符,破折号表示左对齐,15s 表示 15 个空格。这个例子是打印
字符串,15 个空格,然后是 ID 和数字。
%8d 用来说明$3 在打印的字符串中的格式和位置。数字将向右对齐,占用 8 个空格的位置,该
语句中的引号也可以用括号替代。

表 5.3 转换字符

转换字符 定义
c 字符
s 字符串
d 十进制数
ld 长十进制数
u 无正负的十进制数
lu 长的无正负之分的十进制数
x 十六进制数

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 103

续表
转换字符 定义
lx 长十六进制数
o 八进制数
lo 长八进制数
e 用科学记数法记录的浮点数
f 浮点数
g 使用 e 或者 f 函数处理过的浮点数,占用最少的空间

表 5.4 printf 修正符

字符 定义
- 左对齐
# 显示八进制数的时候以 0 开头;显示十六进制数的时候以 0x 开头
+ 使用 d、e、f 或者 g 转换以后,数字前面保持+或者-
0 用 0 代替空格填补空白

表 5.5 格式化说明符

printf 格式化说明符 功能
假设 x=A y=15 z=2.3 $1=Bob Smith
打印单个 ASCII 字符
%c
printf("The character is %c\n", x) 结果为:The character is A
打印十进制数
%d
printf("The boy is %d years old\n", y) 结果为:The boy is 15 years old
打印用科学记数法表示的数
%e
printf("z is %e\n", z) 结果为:z is 2.3e+01
打浮点数
%f
printf("z is %f\n", 2.3 *2) 结果为:z is 4.600000
打印八进制数
%o
printf("y is %o\n", y) 结果为:y is 17
打印字符串
%s printf("The name of the culprit is %s\n", $1) 结果为:The name of the culprit
is Bob Smith
打印十六进制数
%x
printf("y is %x\n", y) 结果为:y is f

5.4 文件中的 awk 命令


如果 awk 命令被放入一个文件中,在文件名后面使用-f 选项,后面紧跟着需要处理的输
入文件名。当一个记录被读取到缓冲中,awk 文件中的每一个命令都对这个记录做出判断和
执行。awk 文件处理完第一个记录以后,就丢弃第一个记录,再读入下一个记录,如此继续。

PDF created with pdfFactory Pro trial version www.pdffactory.com


104 第5章

如果模式无法控制某个动作,就默认打印整条记录。如果某个模式没有捆绑指定的动作,默
认打印模式匹配输入文件的位置的记录。

实例 5.13

(The Database)
$1 $2 $3 $4 $5
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% cat awkfile
1 /^Mary/{print "Hello Mary!"}
2 {print $1, $2, $3}

3 % awk –f awkfile employees


Tom Jones 4424
Hello Mary!
Mary Adams 5346
Sally Chang 1654
Billy Black 1683

4 % awk –w source '/^Sally/' –f awkfile employees


Tom Jones 4424
Hello Mary!
Mary Adams 5346
Sally Chang 1654 7/22/54 650000
Sally Chang 1654
Billy Black 1683

说明
1 如果记录以正则表达式 Mary 开头,就打印字符串“Hello Mary!”,域之间依靠空格分隔。
2 打印每一条记录的第一、第二和第三个域,这个动作在每一行都起作用,因为没有一个模式控制
这个动作。
3 awk 从脚本文件 awkfile 中读取命令。
4 当参数后面有-w 选项,脚本被-f 选项处理后,source 和 awk 可以混合命令行的命令和脚本命令。
在这个例子中,awk 将在行的开头寻找 Sally,并从脚本文件 awkfile 中取得其他的命令。-W 选
项只是在 Gnu 的 awk 中有,UNIX 中的 awk 不提供这个功能。

5.5 记录和域

5.5.1 记录
awk 并不是把输入文件看作一个没有终止的字符串,而是看作具有一定格式和结构的。
默认每一个以换行符结束的行称做一个记录。
记录分隔符。默认输入和输出的分隔符都是回车,保存在 awk 内建变量 ORS 和 RS 中,
ORS 和 RS 的值可以在有限的范围内修改。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 105

$0 变量。在 awk 中$0 指的是整条记录(当发生替换或者赋值而使得$0 的值发生改变时,


NF 的值和域的个数都跟着发生改变)。换行符的值保存在 awk 内建变量 RS 中,默认情况下
回车。

实例 5.14

% cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '{print $0}' employees


Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

说明
awk 变量$0 保存当前记录。打印它到屏幕。默认情况下,如下命令也将打印整个记录:
%awk '{print}' employee

变量 NR。多个记录的每一条都保存在内建变量 NR 中。每处理完一个记录,NR 的值
就增加 1。

实例 5.15

% cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '{print NR, $0}' employees


1 Tom Jones 4424 5/12/66 543354
2 Mary Adams 5346 11/4/63 28765
3 Sally Chang 1654 7/22/54 650000
4 Billy Black 1683 9/23/44 336500

说明
打印保存在文件中的每个记录,$0,并在记录前加记录号,NR。

5.5.2 域
记录中的每一个单词称做“域” ,默认情况下以空格或者 tab 分隔。每一个单词叫作一
个域,awk 跟踪域的数量,并在内建变量 NF 中保存这个数字。每一行的域个数是不一样的,
典型限制为每行 100 个。可以建立新的域。下面的例子有 4 个记录和 5 个域,每一个记录都
从第一个域$1 开始,然后是第二个域,$2,如此继续。

PDF created with pdfFactory Pro trial version www.pdffactory.com


106 第5章

实例 5.16

(Fields are represented by a dollar sign ($) and the field number.)
(The Database)
$1 $2 $3 $4 $5
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '{print NR, $1, $2, $5}' employees


1 Tom Jones 543354
2 Mary Adams 28765
3 Sally Chang 650000
4 Billy Black 336500

说明
awk 将打印记录的个数 NR 及文件记录中每一行的第一个、第二个和第五个域。

实例 5.17

% awk '{print $0, NF}' employees


Tom Jones 44234 5/12/66 543354 5
Mary Adams 5346 11/4/63 28765 5
Sally Chang 1654 7/22/54 650000 5
Billy Black 1683 9/23/44 336500 5

说明
awk 打印文件中的每一个记录($0),后面紧跟域的个数。

5.5.3 域分隔符
输入域分隔符。awk 的内建变量 FS 保存输入域分隔符的值。默认值是空格或者 tab。可
以在 BEGIN 语句中或者命令行上修改 FS 的值。我们现在就要通过使用选项-F,后面紧跟新
的 FS 值来在命令行上修改 FS。
在命令行上修改域分隔符。awk 命令后面加上-F 选项可以用来修改输入文件的域分隔
符。在-F 后面的字符串立刻就变成新的域分隔符。包含元字符的字符串可以重新设置多个域
分隔符。参考下面的例子。

实例 5.18

% cat employees2
Tom Jones:4424:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Billy Black:1683:9/23/44:336500

% awk –F: '/Tom Jones/{print $1, $2}' employees2


Tom Jones 4424

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 107

说明
-F 选项用来在命令行给域分隔符重新赋值。当冒号在-F 后面,awk 就把冒号作为文件 employee2
的新的域分隔符。

使用多个域分隔符。你可以指定多个输入分隔符,若域分隔符由多个字符组成,如 FS,
则该字符串是一个放在方括号内的正则表达式。下面的例子中域分隔符是空格、冒号和 tab
(旧版本的 awk 不支持这个特性)

实例 5.19
% awk –F'[:\t]' '{print $1, $2, $3}' employees2
Tom Jones 4424
Mary Adams 5346
Sally Chang 1654
Billy Black 1683

说明
-F 选项后面跟着用方括号引用的正则表达式。如果空格、冒号或者 tab 被引用,awk 就把它们
作为域分隔符。表达式被引号引用,所以 Shell 不会把它作为元字符看待(记住,shell 用括号进行
文件名扩展)。

输出域分隔符。默认的输出分隔符是一个空格,保存在 awk 内建变量 OFS 中。在后面


的例子中,我们用 print 语句把输出打印到屏幕上。逗号用来分隔 print 的域,就是 OFS 中的
值。如果使用默认值,$1 与$2 之间就使用一个空格分隔,print 函数将在$1 与$2 打印空格。
OFS 可以修改。

实例 5.20
% cat employees2
Tom Jones:4424:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Billy Black:1683:9/23/44:336500
(The command Line)
% awk –F: '/Tom Jones/{print $1, $2, $3, $4}' employees2
Tom Jones 4424 5/12/66 543354

说明
默认的输出域分隔符是一个空格保存在 OFS 中,域之间的逗号表示的就是 OFS 的值。打印到标
准输出的域用一个空格分隔。

实例 5.21
% awk –F: '/Tom Jones/{print $1 $2 $3 $4}' employees2
Tom Jones44245/12/66543354

说明
由于没有使用逗号分隔域,所有的域都挤在一起。

PDF created with pdfFactory Pro trial version www.pdffactory.com


108 第5章

实例 5.22
% awk –F: '/Tom Jones/{print $0}' employees2
Tom Jones:4424:5/12/66:543354

说明
$0 保存着输入文件的当前记录,该记录被原样打印。

5.6 模式与动作

5.6.1 模式
awk 模式控制 awk 对输入的文件行所做的动作。一个模式包括正则表达式、条件表达式,
或者二者的结合。当条件表达式为真的时候,默认动作是打印该行。当读取一个模式表达式
时,可以使用 if 语句。使用 if 时不需要花括号引用。这里的 if 变成一个动作语句,语法结
构发生了变化。

实例 5.23
% Cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
(The Command Line)
1 awk '/Tom/' employees
Tom Jones 4424 5/12/66 543354
2 awk '$3 < 4000' employees
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

说明
1 如果输入文件匹配模式 Tom,就打印该记录。如果没有明确地指定动作,默认动作就是打印行。
等价于:
awk '$0 ~ /Tom/{print $0}' employee
2 由于第三个域小于 4000,所以打印该行。

5.6.2 动作
花括号内的,用分号分隔的语句称为动作。2 如果模式在动作前面,模式决定什么时候
发出动作。动作可以是一个语句也可以是一组语句。语句之间用分号分隔,也可以用换行符。

2.某些版本的 awk 中,动作之间必须用分号或换行符分隔,花括号内的语句也必须用分号或换行符分隔;nawk 和 gawk 只要


求花括号内的语句由分号或换行符分隔,动作之间不要求分号或换行符分隔,例如下面的两个动作之间就不需要分号。
gawk '/Tom/{print "hi Tom"};{x=5}' file

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 109

格式
{ action }

实例 5.24
{ print $1 $2 }

说明

动作是打印第一个和第二个域。
模式可以与动作捆绑在一起。记住,动作是花括号内的语句。模式控制的动作是从第一个左花
括号开始到第一个右花括号结束。如果动作跟在模式后面,第一个左花括号必须与模式在同一行。
模式不需要放在花括号中间。

格式

pattern{ action statement; action statement; etc. }


or
pattern{
action statement
action statement
}

实例 5.25

% awk '/Tom/{print "Hello there, " $1}' employees


Hello there, Tom

说明
如果记录包含模式 Tom,则打印字符串“Hellothere,Tom”。
不显示任何匹配行的模式。字符串匹配模式(string-matching pattern)包含在斜杠之间的正则表
达式。

5.7 正则表达式
对于 awk 来说,正则表达式就是由斜杠之间的字符组成的模式。awk 支持用正则表达式
元字符修改正则表达式。如果输入行的字符串匹配正则表达式,结果条件为真,则执行所有
与模式捆绑在一起的动作,如果正则表达式被匹配但是没有指定任何动作,则只打印该记录。
参考表 5.6。

实例 5.26
% awk '/Mary/' employees
Mary Adams 5346 11/4/63 28765

说明
awk 显示输入文件 employee 中所有包含正则表达式模板 Mary 的行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


110 第5章

实例 5.27

% awk '/Mary/{print $1 $2}' employees


Mary Adams

说明
awk 显示输入文件 employee 中所有包含正则表达式 Mary 的行的第一个和第二个域。

表 5.6 正则表达式元字符

^ 匹配字符串开始
$ 匹配字符串末尾
. 匹配单个字符
* 匹配 0 个或者多个字符
+ 匹配一个或者多个字符
? 匹配 0 个或者一个字符
[ABC] 匹配任意一个大写字母,A、B 或 C
[^ABC] 匹配非大写字母的字符,A、B 或 C
[A-Z] 匹配 A~Z 之间的字符
A\B 匹配 A 或者 B
(AB)+ 匹配一个或者多个 AB
\* 匹配一个星号
& 用来替换字符串中,替换找到的字符串
A{m} 重复字母 A,
A{m,} m 次,至少 m 次,
A{m,n} 或者 m 与 n 之间次
a
\Y 匹配一个单词开头或者末尾的空字符串
\B 匹配单词内的空字符串
\< 匹配一个单词开头的空字符串,也叫作锚定的开始
\> 匹配一个单词末尾的空字符串,也叫作锚定的末尾
\w 匹配一个字母数字组成的单词
\W 匹配一个非字母数字组成的单词
\‘ 匹配字符串开头的一个空字符串
\’ 匹配字符串末尾的一个空字符串
a. 从这里开始到表结束的所有元字符都是 gawk 专用的,不适合 UNIX 版本的 awk。

实例 5.28

% awk '/^Mary/' employees


Mary Adams 5346 11/4/63 28765

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 111

说明
awk 显示 employee 文件中所有以正则表达式 Mary 开头的行。

实例 5.29

% awk '/^[A-Z][a-z]+ /' employees


Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

说明
awk 显示 employee 文件中所有以一个大写字母开头,然后是一个或者多个小写字母,紧跟着是
一个空格的行。

POSIX 字符集。POSIX(the Portable Operating System Interface)是一个用于保证程序


在不同的平台之间移植的工业标准。为了便于移植,POSIX 重新组织了字符集,其中包括不
同国家与地区的字符、编码、用于表示现金、时间以及日期的符号等等。为了处理不同类型
的字符,POSIX 把表 5.7 中用方括号引用的字符集的类加入了基本和扩展的正则表达式中。
gawk 支持这些新的元字符类的字符,但是 awk 和 nawk 却不支持。
类[:alnum:]是 A-Za-z0-9 的另外一种表示方法。要使用这个类,必须将其放在另外一对
方括号中,以重新组织成一个正则表达式。例如 A-Za-z0-9 本身并不是一个正则表达式,但
是[A-Za-z0-9]却是正则表达式。同理,[:alnum:]不是正则表达式,[[:alnum:]]是正则表达式。
这两种格式的区别在于[A-za-z0-9]依赖 ASCII 字符集,而[[:alnum:]]则允许出现其他语言的
字符。
表 5.7 POSIX 增加的方括号字符类

命令 含义

[:alnum:] 字母数字字符
[:alpha:] 字母字符
[:cntrl:] 控制字符
[:digit:] 数字字符
[:graph:] 非空字符(非空格和控制字符)
[:lower:] 小写字母
[:print:] 类似[:graph:],但是包括空格
[:punct:] 标点字符
[:space:] 所有白空格(包括换行符、空格和制表符)
[:upper:] 大写字母
[:xdigit:] 允许十六进制格式的数字

PDF created with pdfFactory Pro trial version www.pdffactory.com


112 第5章

实例 5.30

% awk '/[[:lower:]]+g[[:space:]]+[[:digit:]]/' employees


Sally Chang 1654 7/22/54 650000

说明
awk 搜索一个或者多个小写字母,后面紧跟着一个 g,再后面是一个或者多个空格,最后是一个数字。

5.7.1 匹配操作符
匹配操作符(~)用来在记录或者域内匹配正则表达式。

实例 5.31

% cat employees
Tom Jones 44234 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

% awk '$1 ~ /[Bb]ill/' employees


Billy Black 1683 9/23/44 336500

说明
awk 显示所有在第一个域匹配 Bill 或 bill 的行。

实例 5.32

% awk '$1 !~ /ly$/' employees


Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765

说明
awk 显示所有第一个域的末尾不匹配 ly 的行。

5.8 脚本文件中的 awk 命令


当你有多个 awk 的模式/动作语句的时候,最简单的办法就是把它们放在一个脚本文件
中。脚本文件是一个包含 awk 注释和语句的文件。如果语句和动作在同一行,则必须分号分
隔。若在不同的行则无需使用分号了。如果模式后面是动作,左花括号就必须与模式在同一
行。注释由#引导。

实例 5.33

% cat employees
Tom Jones:4424:5/12/66:54335

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 113

Mary Adams:5346:11/4/63:287655
Billy Black:1683:9/23/44:336500
Sally Chang:1654:7/22/54:65000
Jose Tomas:1683:9/23/44:33650
(The Awk Script)
% cat info
1 # My first awk script by Jack Sprat
# Script name: info; Date: February 28, 2000
2 /Tom/{print "Tom's birthday is "$3}
3 /Mary/{print NR, $0}
4 /^Sally/{print "Hi Sally. " $1 " has a salary of $" $4 "."}
# End of info script

(The Command Line)


5 % awk –F: -f info employees2
Tom's birthday is 5/12/66
2 Mary Adams:5346:11/4/63:28765
Hi Sally. Sally Chang has a salary of $65000.
Tom's birthday is 9/23/44 <-- Finds Jose Thomas

说明
1 注释行。
2 如果正则表达式 Tom 匹配输入文件,就打印字符串“Tom’s birthday is”和第三个域的值。
3 如果正则表达式 Mary 匹配输入文,动作块就打印 NR,即当前记录数和当前记录。
4 如果在输入行的开始发现正则表达式 Sally,就打印字符串“Hi Sally”,后面跟着第一个域的值,
字符串“has a salary of $”,然后是第四个域的值。
5 awk 命令后面是-F:选项,指定冒号作为域分隔符。-f 选项的 awk 后面是脚本文件名。awk 将从
info 文件中读取结构。下一个是输入文件 employees2。

5.9 复 习
在这部分例子中有一个简单数据库,叫作 datafile。在该数据库中,默认输入域分隔符
FS 为空格。域的个数 NF 为 8。每一行的 NF 是可变的,但是在这个文件中,该数字是固定
的。记录分隔符 RS 是换行符,它的作用是分隔文件中不同的行。awk 用变量 NR 跟踪记录
的个数。输出域分隔符 OFS 是空格。如果在输入文件中用逗号分隔域在打印行的时候,打
印出来的结果是不同的域之间依靠空格分隔。

5.9.1 简单模式匹配
% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


114 第5章

实例 5.34

% awk '/west/’ datafile


northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18

说明
打印所有包含模式 west 的行。

实例 5.35
% awk '/^north/’ datafile
northwest NW Joel Craig 3.0 .98 3 4
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9

说明
打印所有以模式 north 开头的行。

实例 5.36

% awk '/^(no|so)/' datafile


northwest NW Joel Craig 3.0 .98 3 4
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9

说明
打印所有以模式 no 或者 so 开头的行。

5.9.2 简单动作

实例 5.37
% awk '{print $3, $2}' datafile
Joel NW
Sharon WE
Chris SW
May So
Derek SE
Susan EA
TJ NE
Val NO
Sheri CT

说明
默认的输出文件域分隔符 OFS 是空格。$2 与$3 之间的逗号翻译为 OFS 的值。打印第三个域和
一个空格,后面是第二个域。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 115

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 5.38

% awk '{print $3, $2}' datafile


JoelNW
SharonWE
ChrisSW
MaySO
DereKSE
SusanEA
TJNE
ValNO
SheriCT

说明
第三个域跟在第二个域的后面。因为没有逗号分隔$2 和$3,所以输出的域之间也没有空格。

实例 5.39

1 % awk 'print $1' datafile


awk: syntax error near line 1
awk: bailing out near line 1

2 % nawk 'print $1' datafile


nawk: syntax error at source line 1
context is
>>> print <<< $1
nawk: bailing out at source line 1

3 % gawk 'print $1' datafile


awk: cmd. line:1: print $1
awk: cmd. line:1: ^parse error

说明
1 这是一个 awk(旧版 awk)的错误信息。旧的 awk 程序很难除错,因为几乎所有错误显示出来的
信息差不多都是这个。在动作语句中缺少一个花括号。
2 这是一个 nawk 的错误信息。nawk 的错误信息比旧的 awk 错误信息要详细一些。在这个例子中,
动作语句缺少花括号。
3 Gnu awk,也就是 gawk 按照与传统的 awk 风格有一点不同的格式显示其诊断。

PDF created with pdfFactory Pro trial version www.pdffactory.com


116 第5章

实例 5.40
% awk '{print $0}' datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 3
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

说明
$0 保存当前记录。打印所有记录。

实例 5.41
% awk '{print "Number of fields: "NF}' datafile
Number of fields: 8
Number of fields: 8
Number of fields: 8
Number of fields: 8
Number of fields: 8
Number of fields: 8
Number of fields: 8
Number of fields: 8
Number of fields: 8

说明
每一个记录中有 8 个域,awk 内建变量 NF 保存了记录的个数,awk 每读取一个记录,并重新设
置 NF 的值。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

5.9.3 由模式和动作组合成的正则表达式

实例 5.42

% awk '/northeast/{print $3, $2}' datafile


TJ NE

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 117

说明
如果记录包含模式 northeast,就打印第二个域和第三个域。

实例 5.43
% awk '/E/' datafile
western WE Sharon Kelly 5.3 .97 5 23
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13

说明
如果记录包含 e,就打印整条记录。

实例 5.44

% awk '/^[ns]/{print $1}' datafile


northwest
southwest
southern
southeast
northeast
north

说明
如果记录以 n 或者 s 开头,就打印第一个域。

实例 5.45

% awk '$5 -/\.[7-9]+/' datafile


southwest SW Chris Foster 2.7 .8 2 18
central CT Sheri Watson 5.7 .94 5 13

说明
如果第五个域包含一个文本逗号,且后面是一个或者多个在 7 和 9 之间的数字,就打印该记录。

实例 5.46

% awk '$2 !~ /E/{print $1, $2}' datafile


northwest NW
southwest SW
southern SO
north NO
central CT

说明
如果第二个域不包含模式 E,就打印第一个域和它后面紧跟着的第二个域($1,$2)。

PDF created with pdfFactory Pro trial version www.pdffactory.com


118 第5章

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 5.47

% awk '$3 ~ /^Joel/{print $3 " is a nice guy."}' datafile


Joel is a nice guy.

说明
如果第三个域以模式 Joel 开头,打印第三个域和字符串“is a nice guy.”。注意,如果打印字符
串则将包含空格。

实例 5.48

% awk '$8 ~ /[0-9][0-9]$/{print $8}' datafile


23
18
15
17
20
13
13

说明
如果第八个域以两个数字结束则打印这个记录。

实例 5.49

% awk '$4 ~ /Chin$/{print "The price is $" $8 "."}' datafile


The price is $15

说明
如果第四个域以 Chin 结束,就打印双引号内的字符串“The price is”,第八个域和一个句号。

实例 5.50

% awk '/TJ/{print $0}' datafile


northeast NE TJ Nichols 5.1 .94 3 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 119

说明
如果记录包含模式 TJ,就打印$0。

5.9.4 输入域分隔符

% cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7;.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:Southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

实例 5.51

% awk '{print $1}' datafile2


Joel
Sharon
Chris
May
Derek
Susan
TJ
Val
Sheri

说明
默认的域分隔符是空格。打印第一个域($1)。

% cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7;.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:Southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

实例 5.52

% awk –F: '{print $1}' datafile2


Joel Craig
Sharon Kelly

PDF created with pdfFactory Pro trial version www.pdffactory.com


120 第5章

Chris Foster
<more output here>
Val Shultz
Sheri Watson

说明
-F 选项指定冒号作为输入域分隔符。打印第一个域($1)。

实例 5.53
% awk '{print "Number of fields: "NF}' datafile2
Number of fields:2
Number of fields:2
Number of fields:2
<more of the same output here>
Number of fields:2
Number of fields:2

说明
因为默认的输入域分隔符是空格,所以域的个数是 2。惟一的空格出现姓和名之间。

实例 5.54
% awk –F: '{print "Number of fields: "NF}' datafile2
Number of fields: 7
Number of fields: 7
Number of fields: 7
<more of the same output here>
Number of fields: 7
Number of fields: 7

说明
因为域分隔符是冒号,所以每一个记录的域的个数是 7。

实例 5.55
% awk –F"[ :]" '{print $1, $2}' datafile2
Joel Craig
Sharon Kelly
Chris Foster
May Chin
Derek Johnson
Susan Beal
TJ Nichols
Val Shultz
Sheri Watson

说明
awk 的正则表达式可以指定多个域分隔符。空格冒号都作为域分隔符出现。打印第一个和第二
个域($1,$2)

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 实用程序:Linux 工具——gawk 121

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

5.9.5 awk 脚本

实例 5.56

% cat awk.sc1
#This is a comment
#This is my first awk script
1 /^north/{print $1, $2, $3}
2 /^south/{print "The " $1 " district."}

% awk –f awk.sc1 datafile


3 northwest NW Joel
The southwest district.
The southern district.
The southwest district.
northeast NE TJ
north NO Val

说明
1 如果记录以模式 north 开头,就打印第一、第二和第三个域($1,$2,$3)。
2 如果记录以模式 south 开头,就打印字符串 The、第一个域的值和字符串 district。
3 -f 选项引导 awk 脚本文件名,后面跟着的是需要处理的输入文件名。

Linux 工具实验室 3

(File: lab3.data)

Mike Harrington:(510) 548-1278:250:100:175


Christian Dobbins:(408) 538-2358:155:90:201
Susan Dalsass:(206) 654-6279:250:60:50
Archie McNichol:(206) 548-1348:250:100:175
Jody Savage:(206) 548-1278:15:188:150
Guy Quigley:(916) 343-6410:250:100:175
Dan Savage:(406) 298-7744:450:300:275
Nancy McNeil:(206) 548-1278:250:80:75
John Goldenrod:(916) 348-4278:250:100:175

PDF created with pdfFactory Pro trial version www.pdffactory.com


122 第5章

Chet Main:(510) 548-5258:50:95:135


Tom Savage:(408) 926-3456:250:168:200
Elizabeth Stachelin:(916) 440-1763:175:75:300

上面的数据库记录了在过去三个月中为党派运动捐款的人的名字、电话和捐款数额。
1.打印所有电话号码。
2.打印 Dan 的电话号码。
3.打印 Susan 的名字和电话号码。
4.打印所有以 D 开头的姓。
5.打印所有以 C 或 E 开头的名。
6.打印所有由四个字母组成的名。
7.打印所有所在地区号码为 916 的人的名。
8.打印 Mike 的捐款,每个数字前面要求加一个美元符号。例如$250、$100 或$175。
9.打印所有紧跟着一个逗号和姓的名字。
10.使用 POSIX 字符集打印所有以一个空格及三个数字结束的行。
11.写一个名为 facts 的脚本:
a.打印 savage 中所有的名字和电话号码
b.打印 Chet 的捐献数额
c.打印所有第一个月捐献数额是 250 美元的人的资料

PDF created with pdfFactory Pro trial version www.pdffactory.com


第6章

gawk 功能:
给表达式赋值
6.1 比 较 表 达 式
比较表达式匹配那些只在条件为真时才运行的行。这些表达式利用关系运算符来比较数
字和字符串。表 6.1 提供了一个关系运算符的清单,如果表达式的为真,它的值就是 1,否
则就是 0。

6.1.1 关系运算符

表 6.1 关系运算符

运算符 含义 例子
< 小于 x<y
<= 小于等于 x<=y
== 等于 x==y
!= 不等于 x!=y
>= 大于等于 x>=y
> 大于 x>y
~ 匹配正则表达式 x~/y/
!~ 不匹配正则表达式 x!~/y/

实例 6.1

(The Database)
% cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

PDF created with pdfFactory Pro trial version www.pdffactory.com


124 第6章

(The Command Line)


1 % awk '$3 == 5346' employees
Mary Adams 5346 11/4/63 28765

2 % awk '$3 > 5000{print $1}' employees


Mary

3 % awk '$2 ~ /Adam/ ' employees


Mary Adams 5346 11/4/63 28765

4 % awk '$2 !~ /Adam/ ' employees


Tom Jones 4424 5/12/66 543354
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

说明
1 如果第三个域等于 5346,则条件为真,gawk 运行默认动作——打印这一行。当 if 条件被隐含,
它就成为一个条件模板测试。
2 如果第三个域大于 5000,awk 就打印该行的第一个域。
3 如果第二个域匹配正则表达式 Adam,就打印这个记录。
4 如果第二个域不匹配正则表达式 Adam,就打印这个记录。如果在一个数值跟一个字符串进行比
较时,使用数值比较符号,那么字符串就会自动被转换为数字。如果这个关系符号是字符串比较
符号,那么数字就会转换为字符串。

6.1.2 条件表达式
条件表达式使用两个符号——问号和冒号给表达式赋值。这里有一个更简洁的方法,它
能实现 if/else 语句一样的效果。标准格式如下:

格式
conditional expression1 ? expression2 : expression3

这个格式跟下面的 if/else 语句产生的结果是一样的(后面有关于 if/else 的完整讨论)



{
if (expression1)
expression2
else
expression3
}

实例 6.2
% awk '{max={$1 > $2} ? $1 : $2: print max}' filename

说明
如果第一个域大于第二个域,那么问号后面的表达式的值就赋给 max,否则冒号后面表达式的
值就赋给 max。
完整的形式如下所示:
if ($1 > $2)

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:给表达式赋值 125

max=$1
else
max=$2

6.1.3 运算
运算可以在模板内进行。awk 把所有的运算都作为浮点运算。表 6.2 提供了数学运算符
号的清单。

实例 6.3
% awk '$3 * $4 > 500' filename

说明
awk 将第三个域跟第四个域相乘,如果结果大于 500,就显示这些行(假设 filename 为包含输入
的文件)。

表 6.2 数学运算符

运算符 含义 例子
= 加 x+y
- 减 x-y
* 乘 x*y
/ 除 x/y
% 取余 x%y
^ 乘方 x^y

6.1.4 复合模板
符合模板是用逻辑运算符连接在一起的由模板组成的表达式,且该表达式从左到右赋
值,表 6.3 列出了逻辑运算符。
表 6.3 逻辑运算符

运算符 含义 例子
&& 逻辑和(AND) a&&b
|| 逻辑或(OR) a||b
! 逻辑非(NOT) !a

实例 6.4
% awk '$2 > 5 && $2 <= 15' filename

说明
awk 显示匹配如下两个条件的行:第二个域大于 5 同时小于等于 15。&&符号表示 AND,符号
两边的条件必须同时为真,整个表达式的值才为真(假设 filename 为包含输入的文件)。

PDF created with pdfFactory Pro trial version www.pdffactory.com


126 第6章

实例 6.5
% awk '$3 ==100 || $4 > 50' filename

说明
awk 显示匹配下面任何一个条件的行:第三个域等于 100 或者第四个域大于 50。||要求符号两边
至少有一个为真,则整个表达式为真(假设 filename 为包含输入的文件)

实例 6.6
% awk '!($2 < 100 && $3 < 20)' filename

说明
如果两个条件都为真,awk 就对表达式求反并显示那些一个条件为假或者两个条件都为假的
行。符号!的含义就是对条件结果求反,如果表达式域是一个真的条件,not 就把它变为假。 (假设
filename 为包含输入的文件)

6.1.5 范围模板
范围模板匹配从第一个模板的第一次出现到第二个模板的第一次出现,第一个模板的下
一次出现到第二个模板的下一次出现等等。如果第一个模板被匹配而第二个模板没有出现,
awk 就显示到文件末尾的所有行。

实例 6.7
% awk '/Tom/,/Suzanne/' filename

说明
awk 显示在 Tom 第一次出现与 Suzanne 的第一次出现之间所有的行。如果 Suzanne 没有找到,
awk 就一直显示这些行直到文件的末尾。如果在 Tom 与 Suzanne 之间的行打印后,Tom 再次出现,
awk 就再次开始显示行,直到下一个 Suzanne 出现或者文件的末尾。

6.1.6 数据验证程序

迄今为止,我们只讨论了 awk 命令的用法,在《The AWK Programming Language》 1 一


书中的密码检测程序(password-checking)举例说明了如何验证文件中的数据。

实例 6.8
(The password Database)
1 % cat /etc/passwd
tooth:pwHfudo.eC9sM:476:40:Contract Admin.:/home/rickenbacker/tooth:/bin/csh
lisam:9JY70uS2f31HY:4467:40:Lisa M. Spencer:/home/fortune1/lisam:/bin/csh
goode:v7Ww.nWJCeSIQ:32555:60:Goodwill Guest User:/usr/goodwill:/bin/csh

,Addison Wesley, 1988。


1.Aho, Wein burger, Kernig han,《The Awk programming Language》

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:给表达式赋值 127

bonzo:eTZbu6M2jM7VA:5101:911: SSTOOL Log account :/home/sun4/bonzo:/bin/csh


info:mKZsrioPtW9hA:611:41:Terri Stern:/home/chewie/info:/bin/cah
cnc:IN1IVqVj1bVv2:10209:41:Charles Carnell:/home/christine/cnc:/bin/csh
bee:*:347:40:Contract Temp.:/home/chane15/bee:/bin/csh
friedman:oyuIiKoFTV0TE:3561:50:Jay Friedman:/home/ibanez/friedman:/bin/csh
chambers:Rw7R1k77yUY4.:592:40:Carol Chambers:/usr/callisto2/chambers:/bin/csh
gregc:nkLulOg:7777:30:Greg Champlin FE Chicago
ramona:gbDQLdDBeRc46:16660:68:RamonaLeininge MWA CustomerService Rep:/home/forsh:

(The Awk Commands)


2 % cat /etc/passwd | awk –F: '\
3 NF != 7{\
4 printf("line %d,does not have 7 fields: %s\n",NR,$0)} \
5 $1 !~ /[A-Za-z0-9]/{printf("line %d, nonalphanumeric user id: %s\n",NR,$0)} \
6 $2== "*" {printf("line %d, no password: %s\n",NR,$0)} '

(The Output)
line 7, no password: bee:*:347:40:Contract Temp.:/home/chane15/bee:/bin/csh
line 10, does not have 7 fields: gregc:nk2Eyi7KLulOg:7777:30:Greg Champlin
FE Chicago
Line 11, does not have 7 fie1ds: ramona:gbDQLdDBeRc46:16660:68:Ramona
Leininger MWA Customer Service Rep:/home/forsh:

说明
1 显示文件/etc/passwd 的内容。
2 cat 把结果输出给 awk,awk 域之间的分隔符是冒号。
3 如果域(NT)的数量不等于 7,就执行下面的程序块。
4 printf 函数打印字符串“ line <number>,does not have 7fields”然后显示记录数(NR)
,和记录本
身($0)。
5 如果第一个域没有包含任何字母和数字,printf 函数就打印“nonalphanumeric user id”然后是记
录数和记录。
6 如果第二个域是一个星号,就打印字符串“no passwd”,紧跟着记录数和记录本身。

6.2 复 习

6.2.1 等于测试
% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


128 第6章

实例 6.9
% awk '$7 == 5' datafile
western WE Sharon Kelly 5.3 .97 5 23
eastern EA Susan Beal 4.4 .84 5 20
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

说明
如果第七个域等于数字 5,就打印这行。

实例 6.10

% awk '$2 == "CT"{print $1, $2}' datafile


central CT

说明
如果第二个域等于字符串 CT,就打印第一个域和第二个域($1,$2),字符串必须被引用。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

6.2.2 关系运算符
实例 6.11

% awk '$7 != 5' datafile


northwest NW Joel Craig 3.0 .98 3 4
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
northeast NE TJ Nichols 5.1 .94 3 13

说明
如果第七个域不等于数字 5,就打印该行。

实例 6.12

% awk '$7 < 5 {print $4, $7}' datafile


Craig 3
Foster 2

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:给表达式赋值 129

Chin 4
Johnson 4
Nichols 3

说明
如果第七个域小于 4,就打印第四和第七个域。

实例 6.13

% awk '$6 < .9 {print $1, $6}' datafile


northwest .98
western .97
southern .95
northeast .94
central .94

说明
如果第六个域大于 9,就打印第一和第六个域。

实例 6.14

% awk '$8 <= 17 { print $8}' datafile


4
15
17
13
9
13

说明
如果第八个域小于或者等于 17,就打印它。

实例 6.15

% awk '$8 >= 17 {print $8}' datafile


23
18
17
20

说明
如果第八个域大于或者等于 17,就打印它。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15

PDF created with pdfFactory Pro trial version www.pdffactory.com


130 第6章

southeast SE Derek Johnson 4.0 .7 4 17


eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

6.2.3 逻辑运算符
实例 6.16

% awk '$8 > 10 && $8 < 17' datafile


southern SO May Chin 5.1 .95 4 15
northeast NE TJ Nichols 5.1 .94 3 13
central CT Sheri Watson 5.7 .94 5 13

说明
如果第八个域大于 10 并且小于 17,就打印记录。只有在两个条件都为真时记录才被打印。

实例 6.17

% awk '$2 == "NW" || $1 ~ /south/{print $1, $2}' datafile


northwest NW
southwest SW
southern SO
southeast SE

说明
如果第二个域等于字符串“NW”或者第一个域包含模板 south,第一个和第二个域就被打印。
至少有一个条件为真时,记录才被打印。

6.2.4 逻辑“非”运算符
实例 6.18
% awk '!($8 == 13){print $8}' datafile
4
23
18
15
17
20
9

说明
如果第八个域等于 13,则反向运算表达式 NOTS 并打印第八个字段。是一个一元反向运算符。

6.2.5 数学运算符号
实例 6.19
% awk '/southern/{print $5 + 10}' datafile
15.1

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:给表达式赋值 131

说明
如果记录包含正则表达式 southern,第五个域就加 10 并打印。注意,数字以浮点格式打印。

实例 6.20

% awk '/southern/{print $8 + 10}' datafile


25

说明
如果记录包含正则表达式 southern,则第八个域加 10 并打印。注意,数字以十进制格式打印。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 6.21

% awk '/southern/{print $5 + 10.56}' datafile


15.66

说明
如果记录包含正则表达式 southern,第五个域就加 10.56,然后再打印。

实例 6.22
% awk '/southern/{print $8 - 10}' datafile
5

说明
如果记录包含正则表达式 southern,则第八个域就减 10,然后再打印。

实例 6.23
% awk '/southern/{print $8 / 2}' datafile
7.5

说明
如果记录包含正则表达式 southern,第八个域就除 2,然后再打印。

PDF created with pdfFactory Pro trial version www.pdffactory.com


132 第6章

实例 6.24

% awk '/northeast/{print $8 / 3}' datafile


4.3333

说明
如果记录包含正则表达式 northeast,第八个域就除 3,然后打印。精确到小数点后面 6 位。

实例 6.25

% awk '/southern/ {print $8 * 2}' datafile


30

说明
如果记录包含正则表达式 southern,第八个域就乘 2,然后打印。

实例 6.26

% awk '/northeast/ {print $8 % 3}' datafile


1

说明
如果记录包含正则表达式 northeast,第八个域就除 3,然后打印余数。

实例 6.27

% awk '$3 ~ /^Susan/\


{print "percentage: "$6 + .2 " Volume: " $8}' datafile
percentage:1.04 Volume: 20

说明
如果第三个域以正则表达式 Susan 开始,print 函数就打印计算结果和双引号内的字符串。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:给表达式赋值 133

6.2.6 范围运算符

实例 6.28

% awk '/^western/,/^eastern/' datafile


western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20

说明
打印从以正则表达式 western 开头的记录到以正则表达式 eastern 开头的记录这个范围内的所有
记录。如果找到一个新的以正则表达式 western 开头的记录,则继续打印直到下一个以正则表达式
eastern 开头的记录出现或者到达文件末尾。

6.2.7 条件运算符

实例 6.29

% awk '{print ($7 > 4 ? "high "$7 : "low "$7)}' datafile


low 3
high 5
low 2
low 4
low 4
high 5
low 3
high 5
high 5

说明
如果第七个域大于 4,print 函数就打印问号后面的表达式的值(字符串 high 和第七个域的值)

否则 print 函数就打印冒号后面的表达式的值(字符串 low 和第七个域的值)

6.2.8 赋值符号

实例 6.30

% awk '$3 == "Chris"{ $3 = "Christian"; print}' datafile


southwest SW Christian Foster 2.7 .8 2 18

说明
如果第三个域等于 Chris,就把 Christian 赋值给第三个域并打印该记录。双等号表示等于关系的
判断,而单等号则表示赋值。

PDF created with pdfFactory Pro trial version www.pdffactory.com


134 第6章

实例 6.31

% awk '/Derek/{$8 += 12; print $8}' datafile


29

说明
如果找到正则表达式 Derek,则第八个域就加上 12(+=)并打印结果。另外一种写法是:$8=$8+12。

实例 6.32

% awk '{$7 %= 3; print $7}' datafile


0
2
2
1
1
2
0
2
2

说明
将所有记录的第七个域都除以 3,并将余数赋值给第七个域,最后打印出来。

Linux 工具实验室 4

(File lab4.data)

Mike Harrington:(510) 548-1278:250:100:175


Christian Dobbins:(408) 538-2358:155:90:201
Susan Dalsass:(206) 654-6279:250:60:50
Archie McNichol:(206) 548-1348:250:100:175
Jody Savage:(206) 548-1278:15:188:150
Guy Quigley:(916) 343-6410:250:100:175
Dan Savage:(406) 298-7744:450:300:275
Nancy McNeil:(206) 548-1278:250:80:75
John Goldenrod:(916) 348-4278:250:100:175
Chet Main:(510) 548-5258:50:95:135
Tom Savage:(408) 926-3456:250:168:200
Elizabeth Stachelin:(916) 440-1763:175:75:300

上面的数据库包含姓名、电话号码和在过去三个月中为党派运动捐献的金钱的数额。
1.打印在第二个月中捐献数额在 100 以上的人的姓名。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:给表达式赋值 135

2.打印在最后一个月内捐献数额少于 85 的人的电话和姓名。
3.打印在第一个月捐献数额在 75 和 150 之间的人的姓名。
4.打印三个月捐献总额少于 800 的人的姓名。
5.打印月平均捐献数额大于 200 的人的姓名和地址。
6.打印地址所在地区编码不是 916 的人的名字。
7.打印所有以行号开头的行。
8.打印每一个人的名字和捐款总额。
9.将 Chet 第二次捐款数额加上 10。
10.把 Tom Savage 改名为 Steve Hanson。

PDF created with pdfFactory Pro trial version www.pdffactory.com


第7章

gawk 功能:
gawk 编程
7.1 变 量

7.1.1 数字和字符串常量
数字常量可以是整数,如 243,浮点数,如 3.14,或者使用科学记数法,像 723E-1 或
者 3.4e7。字符串常量需要用双引号括起来,比如“Hello world”。
初始化和类型强制。在 awk 中,变量不需要定义就可以直接使用,使用一个变量就是对
变量的定义。变量的类型可以是数字、字符串,或者两者都是。在赋值的时候,等号右边表
达式的类型就是变量的类型。
根据使用的不同,未初始化的变量的值为 0 或者空白字符串“ ” 。
Name = "Nancy" name 是一个字符串
x++ x 是一个数字;将 x 初始化为 0,然后再加 1
number = 35 number 是一个数字

强制字符串转换为数字:
name + 0
强制数字转换为字符串:
number " "
所有 split 函数建立的域和数组元素都被认为是字符串变量,除非它只包含数字值。如
果域或者数组元素为空(null),那么它们的值就是 null。一个空行也被看做是一个空的字符
串。

7.1.2 自定义变量
自定义变量由字母、数字和下划线组成,但是不能以数字开头。awk 中的变量不需要声
明。awk 根据表达式中变量的内容来确定变量的类型。如果变量没有初始化,awk 就初始化
字符串变量的值为 NULL,数值变量值为 0。如果有必要,awk 能把数字变量转换为字符串

PDF created with pdfFactory Pro trial version www.pdffactory.com


138 第7章

变量,或者相反。变量通过赋值符号被赋值,参见表 7.1。

表 7.1 赋值符号

符号 含义 等价形式
= a=5 a=5
+= a=a+5 a+=5
-= a=a-5 a-=5
*= a=a*5 a*=5
/= a=a/5 a/=5
%= a=a%5 a%=5
^= a=a^5 a^=5

最简单的赋值方法就是把一个表达式的结果赋给一个变量。

格式
Variable = expression

实例 7.1
% awk '$1 ~ /Tom/ {wage = $2 * $3; print wage}' filename

说明
awk 先扫描第一个域,一旦 Tom 被匹配,就把第二个域的值跟第三个域的值相乘,并把结果赋
值给自定义变量 wage。因为乘法运算是数学计算操作,所以 awk 赋给 wage 的初始化值是 0。
(%是
UNIX 的提示符,filename 是输入文件。

递增和递减操作符。递增操作符用于在操作数上加 1。表达式 x++等价于 x=x+1。类似


的,递减操作符的作用是在操作数上减少 1。表达式 x-等价于 x=x-1。值得一提的是,如果
你想在循环操作中简单地记录增加和减少的幅度,这两个操作符是不错的选择。你可以把操
作符放在变量的前面,就像++ x,或者放在变量的后面,如 x++。如果将这些表达式用在赋
值语句中,操作符的不同放置位置会造成不同的结果。
{ x = 1; y = x++ ; print x, y}

这里的++被称为“后递增符(postincrement)”,y 的被赋值为 1,而 x 在给 y 赋值后又


增加 1,所以当表达式运算完成以后,y 的值是 1,而 x 的值是 2。
{ x = 1; y = ++x; print x, y}

这里的++被称为“前递增符(preincrement)”,x 首先增加 1,然后把(x 的值)2 赋给 y。


所以当表达式运算完成以后,y 的值是 2,x 的值也是 2。
命令行自定义变量。可以在命令行中给变量赋值,然后再把这个变量传输给 awk 脚本。
参考后面“在 awk 中处理命令行参数”部分以获得关于参数处理和 ARGV 的详细资料。

实例 7.2
% awk –F: -f awkscript month=4 year=2000 filename

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 139

说明
变量 month 和 year 都是自定义变量,且分别被赋值为 4 和 2000。在 awk 脚本中,这些变量使用
起来就像它们是在脚本中建立的一样。注意,如果参数前面出现 filename,那么在 BEGIN 语句中的
变量就不能被使用(参考“BEGIN 模板” )。

-v 选项(awk)。awk 的-v 选项允许 BEGIN 语句处理命令行参数。对于每一个从命令行


传输的参数,它们前面都必须有-v 选项。
域变量。域变量的用法类似于自定义变量,只是域变量引用的域不同。新的域可以通过
赋值建立。一个域如果已被参考但是还没有赋值,它的值就是空字符串。一旦域的值改变了,
就使用当前作为域分隔符的 OSF 的值重新计算$0 变量。通常域的总数限制在 100 以内。

实例 7.3
% awk ' { $5 = 1000 * $3 / $2; print } ' filename

说明
如果第五个域($5)不存在,awk 将计算表达式 1000*$3/$2 的值,并将其赋给$5。如果第五个
域($5)存在,则用表达式的值覆盖$5 原来的值。

实例 7.4
% awk ' $4 == "CA" { $4 = "California"; print}' filename

说明
如果第四个域的值($4)等于 CA,awk 就把第四个域赋值为“California”。注意,一定要有双
引号,否则系统就会误认为是自定义变量而赋值为 NULL。

内建变量。内建变量的名字是由大写字母组成。它们事先被赋值并可以在表达式中使用。
参考表 7.2。
表 7.2 内建变量清单

变量名 变量内容

ARGC 命令行参数的数量
ARGIND 命令行正在处理的当前文件的 AGV 的索引(仅在 gawk 中有效)
ARGV 命令行参数数组
CONVFMT 转换数字格式(仅在 gawk 中有效)
,默认%.6g
ENVIRON 从 shell 传递来的包含当前环境变量的数组
ERRNO 当使用 close 函数或者通过 getline 函数读取的时候,发生的重新定向错误
的描述信息就保存在这个变量中(仅在 gawk 中有效)
FIELDWIDTHS 在对记录进行固定域宽的分割时,
可以替代 FS 的分隔符的列表
(仅在 gawk
中有效)
FILENAME 当前的输入文件名

PDF created with pdfFactory Pro trial version www.pdffactory.com


140 第7章

续表
变量名 变量内容
FNR 当前文件的记录号
FS 输入域分隔符,默认是空格
IGNORECASE 在正则表达式和字符串操作中关闭大小写敏感(仅在 gawk 中有效)
NF 当前文件域的数量
NR 当前文件的记录数
OFMT 数字输出格式
OFS 输出域分隔符
ORS 输出记录分隔符
RLENGTH 通过 match 函数匹配的字符串的长度
RS 输入记录分隔符
RSTART 通过 match 函数匹配的字符串的偏移量
RT 记录结束符在输入文本的时候,gawk 把它设为与 RS 相同
SUBSEP 下标分隔符

实例 7.5

(The Employees Database)

% cat employees2
Tom Jones:4423:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Mary Black:1683:9/23/44:336500
(The Command Line)

% awk –F: '{IGNORECASE=1}; \


$1 == "mary adams"{print NR, $1, $2,$NF}' employees2

(The Output)
2 Mary Adams 5346 28765

说明
选项-F 设置域分隔符为冒号。gawk 内建变量 IGNORECASE 在值为非 0 的时候,表示在进行字
符串操作和处理正则表达式时关闭大小写敏感。字符串“mary adams”在整个文件中将匹配“Mary
。print 函数将打印记录数、第一个域、第二个域和最后一个域。
adams”

7.1.3 BEGIN 模块
BEGIN 模块后面紧跟着动作块,这个动作块在 awk 处理任何输入文件行之前执行。事
实上,BEGIN 块可以在没有任何输入文件的条件下测试, 因为在 BEGIN 块执行完毕以前 awk
不读取任何输入文件。BEGIN 块通常被用来改变内建变量的值,例如 OFS、RS 及 FS 等等。
初始化自定义变量的值;以及打印输出标题。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 141

实例 7.6
% awk 'BEGIN{FS=":"; OFS="\t"; ORS="\n\n"}{print $1,$2,$3}' file

说明
在处理输入文件以前,域分隔符(FS)被设置为冒号,输出文件分隔符(OFS)被设置为制表
符,输出记录分隔符(ORS)被设置为两个换行符。如果在动作模块中有多个语句,那么它们之间
应该用分号分隔,或者写在不同的行上(在命令行环境下用反斜杠转义换行符)。

实例 7.7
% awk 'BEGIN{print "MAKE YEAR"}'
make year

说明
awk 显示 MAKE YEAR。print 函数在输入文件被读取以前执行,即使没有输入文件,awk 也会打
印 MAKE YEAR。在调试 awk 脚本的过程中,你可以在编写程序的其他部分以前测试 BEGIN 模块。

7.1.4 END 模块

END 模块不匹配任何输入文件,但是执行动作块中的所有动作。END 模块在整个输入


文件处理完毕后被处理。

实例 7.8
% awk 'END{print "The number of records is " NR }' filename
The number of records is 4

说明
在 awk 处理完输入文件以后,执行 END 模块,NR 的值是读入的最后一个记录的记录号。

实例 7.9
% awk '/Mary/{count++}END{print "Mary was found " count " times."}'
employees2
Mary was found 2 times.

说明
对于每一个包含模板 Mary 的行,count 的值都会增加 1。当 awk 处理完整个文件,END 模块打
印字符串“Mary was found”
、count 的值以及字符串“times”。

7.2 重新定向和管道

7.2.1 输出文件的重新定向
重新定向 awk 的输出到一个 UNIX 文件,需要使用 Shell 重新定向符,文件名必须被双

PDF created with pdfFactory Pro trial version www.pdffactory.com


142 第7章

引号括起来。当使用>的时候,文件就被打开并截断;一旦文件被打开,直到文件被明确地
关闭或者 awk 程序终止,它都会一直保持被打开的状态。来自后面的打印语句的输出会追加
到前面内容的后面。
符号>>用来打开一个文件但是不清空文件。重新定向的输出只是被追加到这个文件的后
面。

实例 7.10
% awk '$4 >= 70 {print $1, $2 > "passing_file" }' filename

说明
如果第四个域的值大于等于 70,第一个和第二个域就将被打印到文件 passing_file 中。

7.2.2 输入重新定向(getline)
getline 函数。getline 函数的作用是从标准输入、管道或者当前正在处理的文件之外的其
他输入文件获得输入。它负责从输入获得下一行的内容,并给 NF、NR 和 FNR 等内建变量
赋值。如果得到一个记录,getline 函数就返回 1,如果到达文件的末尾就返回 0。如果出现
错误,例如打开文件失败,就返回-1。

实例 7.11

% awk 'BEGIN{ "date" | getline d; print d}' filename


Thu Jan 14 11:24:24 PST 2000

说明
执行 UNIX 的 data 命令,并通过管道输出给 getline,然后再把输出赋值给自定义变量 d 并打印它。

实例 7.12

% awk 'BEGIN{ "date " | getline d; split( d, mon) ; print mon[2]}'


filename
Oct

说明
执行 data 命令并通过管道输出给 getline,然后 getline 函数从管道中读取并将输入赋值给自定
义变量 d。split 函数把变量 d 转化为数组 mon,然后打印数组 mon 的第二个元素。

实例 7.13
% awk 'BEGIN{while("ls" | getline) print}'
a.out
db
dbook
getdir
file
sortedf

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 143

说明
命令 ls 的输出传递给 getline 作为输入,循环的每一个反复,getline 都从 ls 读取一行输入,并把
它打印到屏幕。输入文件并不是必要的,因为 BEGIN 模块在 awk 打开输入以前就执行了。

实例 7.14

(The Command Line)


1 % awk 'BEGIN{ printf "What is your name?" ;\
getline name < "/dev/tty"}\
2 $1 ~ name {print "Found" name " on line ", NR "."}\
3 END{print "See ya, " name "."}' filename

(The Output)
What is your name? Ellie < Waits for input from user>
Found Ellie on line 5.
See ya, Ellie.

说明
1 在屏幕上打印“What is your name?”并等待用户应答。当一行输入完毕以后,getline 函数从终
端上接收该行输入,并把它存储在自定义变量 name 中。
2 如果第一个域匹配变量 name 的值,print 函数就被执行。
3 END 语句打印“See ya”和 name 变量的值 Ellie。

实例 7.15

(The Command Line)

% awk 'BEGIN{while (getline < "/etc/passwd" > 0 )lc++; print lc}'


file

(The Output)
16

说明
awk 将逐行读取文件/etc/passwd 的内容,在到达文件末尾前,计数器 lc 一直增加。当到了末尾
后,打印 lc 的值,这时候 lc 的值是 passwd 文件的行数。
注意:如果文件不存在,getline 返回-1。如果到达文件的末尾就返回 0。如果读到一行,就返回
1,所以命令:
while (getline < "/etc/junk")
在文件/etc/junk 不存在的情况下将陷入无限循环,因为返回值-1 意味着逻辑真。

7.3 管 道
如果你在 awk 程序中打开一个管道,那么在打开下一个管道之前必须关闭它。管道符号
右边可以通过双引号关闭管道。在同一时刻只能有一个管道存在。

PDF created with pdfFactory Pro trial version www.pdffactory.com


144 第7章

实例 7.16

(The Database)
% cat names
john smith
alice cheba
george goldberg
susan goldberg
tony tram
barbara nguyen
elizabeth lone
dan savage
eliza goldberg
john goldenrod
(The Command Line)
% awk '{print $1, $2 | "sort –r +1 –2 +0 –1 "}' names

(The Output)
tony tram
john smith
dan savage
barbara nguyen
elizabeth lone
john goldenrod
susan goldberg
george goldberg
eliza goldberg
alice cheba

说明
awk 把 print 语句的输出通过管道作为 UNIX sort 命令的输入,sort 把第二个域作为主关键字,
把第一个域作为从关键字,进行排序。UNIX 命令必须有双引号关闭(参见附录 A 中的 sort 命令)。

7.4 关闭文件和管道
如果你打算在 awk 程序中再次使用文件、管道来进行读或者写,则先要去关闭它,因为
直到脚本结束它都不会自动关闭。一旦打开,管道就保持开的状态直到 awk 退出。所以 END
模块中的语句对于管道也是有效的,END 模块的第一行语句就是关闭管道。

实例 7.17

(In Script)
1 { print $1, $2, $3 | " sort –r +1 –2 +0 –1"}
END{
2 close("sort –r +1 –2 +0 –1")
<rest of statements> }

说明
1 awk 管道把输入文件逐行传递给 UNIX sort 实用程序。
2 执行到 END 模块后,管道被关闭。双引号引用的字符串必须确认管道是从那里被初始化的。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 145

system 函数。内建的 system 函数把 Linux 命令作为参数执行这些命令,然后返回退出


状态值给 awk 程序。这有一点像 C 标准库中的 system()函数。Linux 的命令必须被双引号引
用。如果 system 的参数是一个空字符串,那么输出缓冲区就被刷新(只在 gawk 中有效) 。

格式
System( "Linux Command")

实例 7.18

(In Script)
{
1 system ( "cat " $1 )
2 system ( "clear" )
}

说明
1 system 函数把 UNIX 的 cat 命令和文件的第一个域作为参数。而 cat 命令把第一个域,也就是文
件名作为它的参数,UNIX Shell 将执行 cat 命令。
2 system 函数把 UNIX 的 clear 命令作为自己的参数,Shell 执行 clear 命令来刷新屏幕。

fflush 函数。fflush 函数是 1994 年才加入到 awk 中来的,它并不是 POSIX 标准的一部分。


gawk 用它来刷新输出缓冲区。如果没有参数,fflush 函数就刷新标准输出的缓冲区。如果 fflush
的参数是空字符串,例如 fflush(""),fflush 就刷新所有文件和管道的输出缓冲区。

7.5 回 顾

7.5.1 递增和递减操作
% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 7.19

% awk '/^north/{count += 1; print count}' datafile


1
2
3

PDF created with pdfFactory Pro trial version www.pdffactory.com


146 第7章

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

说明
如果记录以正则表达式 north 开头,则创建自定义变量 count;count 以 1 的步长递增并且其值被
打印出来。

实例 7.20

% awk '/^north/{count++; print count}' datafile


1
2
3

说明
自动递增操作符使自定义变量 count 以步长 1 递增;其值被打印出来。

实例 7.21
% awk '{x = $7--; print "x= "x ", $7 = "$7}' datafile
x = 3, $7 =2
x = 5, $7 =4
x = 2, $7 =1
x = 4, $7 =3
x = 4, $7 =3
x = 5, $7 =4
x = 3, $7 =2
x = 5, $7 =4
x = 5, $7 =4

说明
当第七个域的值赋给自定义变量 x 后,自动递减符号把第七个域的值减 1;打印 x 第七个域的值。

7.5.2 内建变量
实例 7.22
% awk '/^north/{print "The record number is " NR}' datafile
The record number is 1
The record number is 7
The record number is 8

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 147

说明
如果记录以正则表达式 north 开头,就打印字符串“The record is”和 NR(记录号)的值。

实例 7.23

% awk '{print NR, $0}' datafile


1 northwest NW Joe1 Craig 3.0 .98 3 4
2 western WE Sharon Kelly 5.3 .97 5 23
3 southwest SW Chris Foster 2.7 .8 2 18
4 southern SO May Chin 5.1 .95 4 15
5 southeast SE Derek Johnson 4.0 .7 4 17
6 eastern EA Susan Beal 4.4 .84 5 20
7 northeast NE TJ Nichols 5.1 .94 3 13
8 north NO Val Shultz 4.5 .89 5 9
9 central CT Sheri Watson 5.7 .94 5 13

说明
打印 NR 和第 0 域。也就是打印记录号和整条记录。

实例 7.24

% awk 'NR==2,NR==5{print NR, $0}' datafile


2 western WE Sharon Kelly 5.3 97 5 23
3 southwest SW Chris Foster 2.7 8 2 18
4 southern SO May Chin 5.1 95 4 15
5 southeast SE Derek Johnson 4.0 7 4 17

说明
如果 NR 的值在范围 2~5 之间,就打印 NR 和第 0 域(也就是整条记录)

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 7.25

% awk '/^north/{print NR, $1, $2, $NF, RS}' datafile


1 northwest NW 4

7 northeast NE 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


148 第7章

8 north NO 9

说明
如果记录以正则表达式 north 开头,就打印 NR、第一个域、第二个域、最后一个记录的值(NF
前加美元符号)和 RS 的值(新行) 。因为 print 函数默认产生一个换行符,而 RS 也产生一个换行符,
所以在记录之间是两个空白行。

% cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7;.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:Southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:131.

实例 7.26

% awk –F: 'NR == 5{print NF}' datafile2


7

说明
用选项-F 设置域分隔符为冒号。如果 NR 是 5,就打印 NF(域的个数)

实例 7.27

% awk 'BEGIN{OFMT="%.2f";print 1.2456789,12E-2}' datafile2


1.25 0.12

说明
OFMT(print 函数的输出格式变量)设置小数点后面精确到 2 位。数字 1.2456789 和 12E-2 都按
照新的格式打印。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 149

实例 7.28

% awk '{$9 = $6 * $7; print $9}' datafile


2.94
4.85
1.6
3.8
2.8
4.2
2.82
4.45
4.7

说明
第六个域($6)与第七个域($7)相乘的结果存储在一个新的域——第九个域($9)中,打
印第九个域。原来的文件有 8 个域,现在是 9 个域。

实例 7.29

% awk '{$10 = 100; print NF, $9, $0}' datafile


10 northwest NW Joel Craig 3.0 .98 3 4 100
10 western WE Sharon Kelly 5.3 .97 5 23 100
10 southwest SW Chris Foster 2.7 .8 2 18 100
10 southern SO May Chin 5.1 .95 4 15 100
10 southeast SE Derek Johnson 4.0 .7 4 17 100
10 eastern EA Susan Beal 4.4 .84 5 20 100
10 northeast NE TJ Nichols 5.1 .94 3 13 100
10 north NO Val Shultz 4.5 .89 5 9 100
10 central CT Sheri Watson 5.7 .94 5 13 100

说明
每个记录的第十个域($10)的值都被赋值为 100。这是一个新的域,第九个域($9)不存在,
所以被看作一个空域。打印 NF(域的个数)、第九个域的值、空域及第 0 个域,也就是整条记录。

实例 7.30

% awk 'NR==1{print ENVIRON["USER"], ENVIRON["HOME"]}' datafile


ellie /home/ellie

说明
如果 NR 等于 1,也就是第一条记录,就打印环境变量 USER 和 HOME 的值。环境变量的值由
父进程传递给 awk 程序,通常这个父进程是 Shell,环境变量存储在叫作 ENVIRON 的特殊数组内。

7.5.3 BEGIN 模块
实例 7.31

% awk 'BEGIN{print "---------EMPLOYEES---------"}'


---------EMPLOYEES---------

PDF created with pdfFactory Pro trial version www.pdffactory.com


150 第7章

说明
BEGIN 模块后面紧跟着就是动作块,这个例子中的动作是在打开输入文件以前打印字符串
“---------EMPLOYEE---------”
。注意,输入文件还没有打开,awk 也没有报错。

实例 7.32

% awk 'BEGIN{print "\t\t---------EMPLOYEES---------\n"}\


{print $0}' datafile
---------EMPLOYEES---------
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

说明
BEGIN 的动作模块首先被执行,即打印字符串“---------EMPLOYEE---------”,第二个动作块是
打印输入文件的所有记录。当命令行输入超过行宽的时候,用反斜杠取消硬回车。行也可以用分号
和花括号结束。

% cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:Southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:131.

实例 7.33

% awk 'BEGIN{ FS=":";OFS="\t"};/^Sharon/{print $1, $2, $7 }'


datafile2
Sharon Kelly western 23

说明
BEGIN 被用做初始化变量,变量 FS(域分隔符)被赋值为冒号,OFS(输出域分隔符)被设置
为制表符。如果一个记录以正则表达式 Sharon 开头,就打印这个记录的第一、第二和第八域。每一
个输出域都以制表符分隔。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 151

7.5.4 END 模块

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 7.34

% awk 'END{print "The total number of records is " NR}' datafile


The total number of records is 9

说明
awk 处理完输入文件后,就执行 END 块。打印字符串“The total number of records is”和 NR(最
后一个记录的记录号)。

实例 7.35

% awk '/^north/{count++}END{print count}' datafile


3

说明
如果记录以正则表达式 north 开头,自定义变量 count 就加 1。awk 处理完输入文件后,打印变
量 count 的值。

7.5.5 带有 BEGIN 和 END 的 awk 脚本

实例 7.36

% cat awk.sc2
# Second awk script-- awk.sc2
1 BEGIN{ FS=":"; OFS="\t"
print " NAME\t\tDISTRICT\tQUANTITY"
print " \n"
}

2 {print $1"\t " $3"\t\t" $7}


{total+=$7}
/north/{count++}

3 END{
print "------------------------------------------"

PDF created with pdfFactory Pro trial version www.pdffactory.com


152 第7章

print "the total quantity is " total


print "the number of northern salespersons is " count "."
}

% cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7:.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:Southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:131.

实例 7.36(续)

(The Output)
% awk –f awk.sc2 datafile2
NAME DISTRICT QUANTITY
__________________________________________
Joel Craig NW 4
Sharon Kelly WE 23
Chris Foster SW 18
May Chin SO 15
Derek Johnson SE 17
Susan Beal EA 20
TJ Nichols NE 13
Val Shultz NO 9
Sheri Watson CT 13
-------------------------------------------
The total quantity is 132
The number of northern salespersons is 3.

说明
1 BEGIN 模块首先被执行,设置 FS(域分隔符)和 OFS(输出域分隔符) ,打印输出标题。
2 awk 脚本程序体处理来自文件 data.file2 的每一行输入。
3 当输入文件关闭后,也就是在 awk 退出前,执行 End 块中的语句。
4 在命令行方式下,执行 awk 程序。-f 选项符后面跟着脚本文件名 awk.sc2 和输入文件名 data.file2。

7.5.6 printf 函数

实例 7.37

% awk '{printf "$%6.2f\n",$6 * 100}' datafile


$ 98.00
$ 97.00
$ 80.00
$ 95.00
$ 70.00

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 153

$ 84.00
$ 94.00
$ 89.00
$ 94.00

说明
printf 函数默认把浮点数右对齐并格式化为 6 位数字,一位是小数点,小数点后面是 2 位。首先
对数字四舍五入,然后再打印。

实例 7.38

% awk '{printf "|%-15s|\n",$4}' datafile


/Craig /
/Kelly /
/Foster /
/Chin /
/Johnson /
/Beal /
/Nichols /
/Shultz /
/Watson /

说明
这是一个左对齐的例子,打印包含 15 个空格的字符串,第四个域($4)被放在竖线中间打印,
以便分辨这些空格的存在。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

7.5.7 重新定向和管道

实例 7.39

% awk '/north/{print $1, $3, $4> "districts"}' datafile


% cat districts
northwest Joel Craig
northeast TJ Nichols
north Val Shultz
说明
如果记录包含正则表达式 north,就打印第 1、第 3 和第 4 域到输出文件“districts”。一旦文件
被打开,只有等到被关闭或者程序终止文件才会关闭。文件名“districts”必须使用双引号括起来。

PDF created with pdfFactory Pro trial version www.pdffactory.com


154 第7章

实例 7.40

% awk '/south/{print $1, $2, $3 >> "districts"}' datafile


% cat districts
northwest Joel Craig
northeast TJ Nichols
north Val Shultz
southwest SW Chris
southern SO May
southeast SE Derek

说明
如果记录包含模板 south,第 1、第 2 及第 3 域就被追加到输出文件“districts”的后面。

7.5.8 打开和关闭管道

实例 7.41

% cat awk.sc3
# awk script using pipes –- awk.sc3
1 BEGIN{
2 print " %-22s%s\n", "NAME", "DISTRICT"
print "-------------------------------------"

3 }
4 /west/{count++}
5 {printf "%s %s\t\t%-15s\n", $3, $4, $1| "sort +1" }

6 END{
7 close "sort +1"
printf "The number of sales persons in the western "
printf "region is " count ".\n"}

(The Output)
%awk –f awk.sc3 datafile
1 NAME DISTRICT
2 --------------------------------------------
3 Susan Beal eastern
May Chin southern
Joel Craig northwest
Chris Foster southwest
Derek Johnson southeast
Sharon Kelly western
TJ Nichols northeast
Val Shultz north
Sheri Watson central
The number of sales persons in the western region is 3.

说明
1 BEGIN 模块后面紧跟着一个动作模块,该动作模块中的语句在输入文件被 awk 处理以前就执行了。
2 用 printf 函数把 NAME 作为一个 22 个字符的字符串按照左边对齐的方式显示,然后把字符串
DISTRICT 按照右边对齐显示。
3 BEGIN 模块结束。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 155

4 现在,awk 开始处理输入文件,每次一行。如果模板 west 被匹配,就执行动作模块。也就是自


定义变量 count 增加 1。自定义变量 count 在第一次被使用的时候自动创建并初始化为 0。
5 print 函数格式化并把它送往管道。当所有的输出都完成以后,它们被一起送给 sort 命令处理。
6 END 模块结束。
7 管道必须用与打开它一样的命令关闭它,在这个例子中是“Sort +1”。否则 END 模块产生的输出
会跟以前的输出一起被 sort 分类。

Linux 工具实验室 5

Mike Harrington:(510)548-1278:250:100:175
Christian Dobbins:(408)538-2358:155:90:201
Susan Dalsass:(206)654-6279:250:60:50
Archie McNichol:(206)548-1348:250:100:175
Jody Savage:(206)548-1278:15:188:150
Guy Quigley:(916)343-6410:250:100:175
Dan Savage:(406)298-7744:450:300:275
Nancy McNeil:(206)548-1278:250:80:75
John Goldenrod:(916)348-4278:250:100:175
Chet Main:(510)548-5258:50:95:135
Tom Savage:(408)926-3456:250:168:200
Elizabeth Stachelin:(916)440-1763:175:75:300

上面的数据库包含在过去的三个月中为党派运动捐款的人的名字、电话和捐赠款的数
额。写一个 awk 脚本以获得如下的输出效果:

% awk –f gawk.sc db
***GAMPATGN 2000 CONTRIBUTIONS***
------------------------------------------------------------------------
NAME PHONE Jan | Feb | Mar | Total Donated
------------------------------------------------------------------------
Mike Harrington (510)548-1278 250.00 100.00 175.00 525.00
Christian Dobbins (408)538-2358 155.00 90.00 201.00 446.00
Susan Dalsass (206)654-6279 250.00 60.00 50.00 360.00
Archie McNichol (206)548-1348 250.00 100.00 175.00 525.00
Jody Savage (206)548-1278 15.00 188.00 150.00 353.00
Guy Quigley (916)343-6410 250.00 100.00 175.00 525.00
Dan Savage (406)298-7744 450.00 300.00 275.00 1025.00
Nancy McNeil (206)548-1278 250.00 80.00 75.00 405.00
John Goldenrod (916)348-4278 250.00 100.00 175.00 525.00
Chet Main (510)548-5258 50.00 95.00 135.00 280.00
Tom Savage (408)926-3456 250.00 168.00 200.00 618.00
Elizabeth Stachelin (916)440-1763 175.00 75.00 300.00 550.00
----------------------------------------------------------------------
SUMMARY
----------------------------------------------------------------------

PDF created with pdfFactory Pro trial version www.pdffactory.com


156 第7章

The Campaign received a total of $6173.00 for this quarter.


The average donation for the 12 contributors was $511.42.
The highest contribution was $300.00.
The lowest contribution was $15.00.

7.6 条 件 语 句
awk 的条件语句是从 C 语言那里借鉴来的,它们通过得到的结果控制程序流。

7.6.1 if 语句
以 if 开头的语句结构是动作语句。通过条件模块,if 可以被隐含。通过条件动作语句,
可以清楚地说明 if 用法,if 后面紧跟的语句必须放在括号内。如果括号内的表达式的值是真
(非 0 或者非空),就执行紧跟着的语句或者语句块。如果这些语句多于一条,那么每条语
句都需要以分号表示结束,且所有语句需要放在一对花括号中间,以便作为一个整体执行。

格式

if(expression){
statement; statement; ...
}

实例 7.42

1 % awk '{if ( $6 > 50 ) print $1 "Too high"}' filename

2 % awk '{if ($6 > 20 && $6 <= 50){safe++; print "OK"}}' filename

说明
1 if 行动作块中条件表达式首先被测试,如果第六个域的值大于 50,打印语句就执行。因为在条件
表达式后面的语句只有一条,所以(filename 表示输入文件)不需要花括号。
2 if 行动块中条件表达式首先被测试,如果第六个域的值大于 20 同时小于 50,条件表达式后面的
语句就被执行。这些语句必须放在一对花括号中间。

7.6.2 if/else 语句
if/else 语句可以做双重判断。如果 if 关键字后面的表达式为真,那么跟这个表达式捆绑
在一起的语句就被执行。如果 if 表达式的结果是假或者 0,那么关键字 else 后面的语句块就
被执行。如果 if 和 else 包含多条语句,那么就需要用一对花括号把它们括在一起,作为一个
语句块。

格式

{if (expression){
statement; statement;…
}
else{

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 157

statement; statement;…
}
}

实例 7.43

1 % awk '{if( $6 > 50) print $1 " Too high" ;\


else print "Range is OK"}' filename
2 % awk '{if ( $6 > 50 ){ count++; print $3 } \
else { x+5; print $
}' filename

说明
1 如果表达式是真,也就是第六个域大于 50,print 函数就打印第一个域和“Too high”
。否则执行
else 后面的语句,打印“Range is OK”

2 如果表达式是真,也就是第六个域大于 50,就执行语句块。否则就执行 else 后面的语句块。注
意,语句块需要用花括号括起来。

7.6.3 if/else else if 语句


if/else else if 语句可以做多重判断。如果关键字 if 后面的表达式的值是真,就执行跟这
个表达式捆绑在一起的语句块,然后控制开始从最后一个 else 后面的花括号以后继续执行。
否则,就跳转到 else if 后面的表达式测试,如果第一个 else if 表达式是真,那么就执行跟这
个表达式捆绑在一起的语句块。如果没有条件表达式为真,控制就跳转到 else 后面的语句块。
else 被称为默认动作,只有当没有表达式为真时,才执行它。

格式

{if ( expression ){
statement; statement;...
}
else if (expression){
statement; statement;...
}
else if (expression){
statement; statement;...
}
else{
statement;
}
}

实例 7.44

(In the Script)


1 {if ( $3 > 89 && $3 < 101 ) Agrade++
2 else if ( $3 > 79 ) Bgrade++
3 else if ( $3 > 69 ) Cgrade++
4 else if ( $3 > 59 ) Dgrade++
5 else Fgrade++

PDF created with pdfFactory Pro trial version www.pdffactory.com


158 第7章

}
END{print "The number of failures is" Fgrade }

说明
1 if 语句是动作语句,必须用花括号括起来。表达式求值是从左到右,如果第一个表达式的值是假,
那么整个表达式的值就是假;如果第一个表达式的值是真,再对逻辑符 AND(&&)后面的表达
式求值,如果也是真,变量 Agrade 就增加 1。
2 如果 if 语句后面的表达式求值为假,那么就对第一个 else if 后面的表达式求值,如果是真,就执
行表达式后面的语句块。如果第三个域大于 79,变量 Bgrade 就增加 1。
3 如果前两个表达式都为假,就测试 else if 后面的表达式,如果第三个域的值大于 69,变量 Cgrade
就增加 1。
4 如果前三个表达式都为假,就测试 else if 后面的表达式,如果第三个域的值大于 59,变量 Dgrade
就增加 1。
5 如果上面没有表达式为真,就执行 else 后面的语句块。花括号结束整个语句块,变量 Fgrade 增
加 1。

7.7 循 环
循环就是用于在条件表达式为真的情况下,重复执行表达式后面的语句块。循环经常被
用于逐个处理一个记录内所有的域和在 END 模块中处理数组中所有的元素。awk 有三种循
环方式:while 循环、for 循环和将在后面 awk 数组中详细讨论的 special for 循环。

7.7.1 while 循环

使用 while 循环的第一步是设置变量的初始值。这个值将在 while 的表达式中被测试。


如果表达式求值为真,就进入循环体,执行循环体中的语句(若循环体中有多个语句,就需
要用花括号把这些语句括起来)。在循环体结束以前,控制循环表达式的变量的值必须被改
变,否则,就会陷入无限循环。在下面的例子中,每次新的记录被处理,该变量都被重新初
始化。
do/while 循与 while loop 非常类似,不同之处仅在于,它是在循环体运行一次以后才检
验表达式的真伪。

实例 7.45
% awk '{ i=1; while ( i <= NF ) { print NF, $i ; i++ } }' filename

说明
变量 i 的初始值是 1。若 i 小于或者等于 NF(记录中域的个数)
,则执行打印语句,且 i 增加 1。
然后再次测试表达式,直到 i 的值大于 NF。直到处理新的记录 i 才重新初始化。

7.7.2 for 循环
for 循环和 while 循环的实质是一样的,只是 for 循环的括号中有三个表达式:初始化变
量的表达式、测试表达式以及更新测试表达式中所使用的变量的表达式。在 awk 中,括号里

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 159

面第一个初始化语句只能初始化一个变量(在 C 语言中可以通过用冒号分隔进行多个初始
化)。

实例 7.46
% awk '{ for ( i = 1; i <= NF; i++ ) print NF,$i }' filex

说明
变量 i 被初始化为 1,并在测试表达式中测试它是否小于或者等于 NF。如果是,print 函数就打
印 NF 的值并打印该记录的第 i 个域,然后 i 再增加 1(for 循环经常用做在 END 模块中循环处理数
组所有的元素) 。参见“数组”。

7.7.3 循环控制
break 和 continue 语句。break 语句使你能在满足某个特定条件时跳出循环。在满足某个
特定条件的情况下,continue 可以使循环忽略任何语句,而直接返回循环的顶端,开始下一次
重复。

实例 7.47
(In the script)
{for ( x = 3; x <= NF; x++ )
1 if ( $x < 0 ){ print "Bottomed out!"; break}
# breaks out of for loop
}
{for ( x = 3; x <= NF; x++ )
2 if ( $x == 0 ) { print "Get next item"; continue}
# starts next iteration of the for loop
}

说明
1 如果 x 域($x)的值小于 0,break 语句就使得控制跳到循环语句末尾的花括号之后。也就是跳
出循环。
2 如果 x 域的值等于 0,continue 语句就使循环从循环的顶端开始,重新执行 for 循环的第三个表达
式 x++。

7.8 程序控制语句
7.8.1 next 语句
next 语句从输入文件中读取下一行,然后从头开始执行 awk 脚本。

实例 7.48
(In Script)
{ if ($1 ~ /Peter/){next}
else {print}
}

PDF created with pdfFactory Pro trial version www.pdffactory.com


160 第7章

说明
如果第一个域包含 Peter,awk 就略过这一行,然后从输入文件读取下一行,脚本从头开始执行。

7.8.2 exit 语句
exit 语句用于结束 awk 程序。它终止对记录的处理,但是不会略过 END 模块,如果 exit
语句被赋予一个 0~255 之间的参数(例如 exit 1),这个参数就被打印到命令行,以判断退出
成功还是失败。

实例 7.49

(In Script)
{exit(1) }

(The Command Line)


% echo $status (csh, tcsh)
1

$ echo $? (bash, sh, ksh, tcsh)


1

说明
退出状态值为 0,表示退出成功,否则表示失败(这是 Linux 和 UNIX 中的惯例)程序退出状
态值完全是由具体程序的程序员提供。在这个例子中,程序退出返回值就是 1。

7.9 数 组
因为 awk 中数组的下标可以是数字和字母,所以称为关联数组(associative array)。数
组的下标(subscript)通常称为关键字(key)并且跟相应元素的值有关系。值和关键字都存
储在内部的一张对问题中的关键字和值应用散列法运算法则的表格里(也就是哈希表) 。由
于在哈希表中使用的技术,数组元素不是按照顺序存储的。如果显示数组的内容就会发现,
它们并不是按照你预料的顺序显示出来的。
数组跟变量一样,都是在使用的时候自动创建,awk 可以自己判断其存储的是数字还是
字符串。根据内容的不同,awk 的数组初始值是数字 0 或空字符串。你不需要声明 awk 数组
的大小。awk 数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹
配的次数等等。

7.9.1 下标与关联数组
用变量作为数组索引。变量可以作为数组下标值的索引,该变量的值可以是数字或者字
符串。

实例 7.50

(The Input File)

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 161

% cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500

(The Command Line)


1 % awk '{name[x++]=$2};END{for(i=0; i<NR; i++)\
print i, name[i]}' employees
0 Jones
1 Adams
2 Chang
3 Black

2 % awk '{id[NR]=$3};END{for(x = 1; x <= NR; x++)\


print id[x]}' employees
4424
5346
1654
1683

说明
1 数组 name 中的下标是一个自定义变量 x。awk 初始化 x 的值为 0,在每次使用后增加 1。第二个
域的值被赋给 name 数组的各个元素。在 END 模块中,for 循环被用于循环整个数组,从下标为
0 的元素开始,打印那些存储在数组的值。因为下标是关键字,所以它不一定从 0 开始,可以从
任何值开始(数字或者字符串)。
2 awk 变量 NR 包含当前的记录号,通过 NR 作为下标,各个记录的第三个域的值被赋给数组中相
应的元素。最后,在 END 模块中,for 循环被用于循环整个数组,打印那些存储在数组的值。

special for 循环。special for 循环在 for 循环一旦无法发挥作用的时候,用于循环读取关


联数组中的元素。当下标是字符串或者是不连续的数字时,special for 循环就把下标作为关
键字,访问跟其关联的值。

格式
{for(item in arrayname){
print arrayname[item]
}
}

实例 7.51

(The Input File)


% cat db
Tom Jones
Mary Adams
Sally Chang
Billy Black
Tom Savage
Tom Chung
Reggie Steel
Tommy Tucker
(The Command Line, For Loop)
1 % awk '/^Tom/{name[NR]=$1};\

PDF created with pdfFactory Pro trial version www.pdffactory.com


162 第7章

END{for( i = 1; i <= NR; i++ )print name[i]}' db


Tom
Tom

Tommy
(The Command Line, Special For Loop)
2 % awk '/^Tom/{name[NR]=$1};\
END{for(i in name){print name[i]}}' db
Tom
Tommy
Tom
Tom

说明
1 如果正则表达式 Tom 在输入文件中被匹配,数组 name 就被赋值。因为用 NR(当前记录号)作
为下标,所以数组的下标几乎不可能是连续的数字,所以在 END 模块中用传统的 for 循环打印
时,不存在的元素就打印空字符串。
2 special for 循环循环数组元素只打印那些有值的元素。打印的顺序是随机的,完全取决于关联数
组的存储方式(hashed,即散列算法)。

用字符串作为下标。下标可以是文字或者包含字符串的变量。如果下标是文字,那么必
须用双引号括起来。

实例 7.52

(The Input File)


% cat datafile3
tom
mary
sean
tom
mary
mary
bob
mary
alex
(The Script)
# awk.sc script
1 /tom/ { count["tom"]++ }
2 /mary/{ count["mary"]++ }
3 END{print "There are " count["tom"] "Toms in the file and "\
count["mary"]" Marys in the file."}

(The Command Line)


% awk –f awk.sc datafile3
There are 2 Toms in the file and 4 Marys in the file.

说明
1 count 数组由两个元素组成,count["tom"]和 count["mary']。每一个元素的初始值都是 0,每一次
tom 被匹配,相应的元素值就增加 1。
2 相同的过程也被应用到 count['mary"]中。注意,即使每行多次出现 tom,也只被计算一次。
3 END 模块中打印存储在数组中的各个元素的值。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 163

2 1 1

Name [“Tom”] Name [“Eliza”] Name [“Mary”]

下标

图 7.1 用字符串作为数组的下标(参见实例 7.52)

用域值作为数组的下标。任何表达式都可以用作数组的下标,域当然也可以。实例 7.52
中的程序计算所有在第二个域中的名字出现的次数并介绍了一种新的 for 循环方式。
for (index_value in array) statement

在上个例子中,END 模块里面的 for 循环工作过程如下:变量 name 被设置为数组 count


的索引,在 for 的每一次循环中,都执行打印,先打印索引的值,然后打印元素里面的值(打
印的顺序无法保证)。

实例 7.53

(The Input File)


% cat datfile4
4234 Tom 43
4567 Arch 45
2008 Eliza 65
4571 Tom 22
3298 Eliza 21
4622 Tom 53
2345 Mary 24

(The Command Line)


% awk '{count[$2]++}END{for(name in count)print name,count[name] }'
datafile4
Tom 3
Arch 1
Eliza 2
Mary 1

说明
awk 语句首先以第二个域作为数组 count 的下标。第二个域变化,索引就变化。所以 count 数组
中的第一个索引是 Tom,存储在 count["Tom"]中的值是 1。
随后,count["Arch"]、 awk 第二次在第二域中发现 Tom,
count["Eliza"]及 count["Mary"]被赋值为 1,
count["Tom"]的值再加 1,当前 Tom 的值为 2。每次出现 Arch、Eliza 和 Mary 都按相同的方式处理。

实例 7.54

(The Input File)


% cat datafile4
4234 Tom 43
4567 Arch 45
2008 Eliza 65
4571 Tom 22
3298 Eliza 21

PDF created with pdfFactory Pro trial version www.pdffactory.com


164 第7章

4622 Tom 53
2345 Mary 24

(The Command Line)


% awk '{dup[$2]++; if (dup[$2] > 1){name[$2]++ }}\
END{print "The duplicates were";\
for (i in name){print i, name[i]}}' datafile4

(The Output)
Tom 2
Eliza 2

说明
数组 dup 的下标是第二个域的值——每一个人的名字。储存在这里的初始值都是 0,每处理一
个新的记录就增加 1,如果名字出现重复,那么以该名字作为下标的元素值就是 2,以此类推。如果
数组 dup 的值大于 1,那么一个新的叫作 name 的数组就被使用,它也以第二个域作为下标,该数组
用来跟踪出现次数多于 1 的名字。

数组和 split 函数。awk 的内建函数 split 允许你把一个字符串分隔为单词并存储在数组


中。你可以自己定义域分隔符或者使用现在 FS(域分隔符)的值。

格式

Split(string, array, field seperator)


Split(string, array)

实例 7.55

(The Command Line)


% awk 'BEGON{ split( "3/15/2000", data, "/");\
print "The month is " data[1] \
"and the year is " date[3] }' filename

(The Output)
The month is 3 and the year is 2000.

说明
字符串“3/15/2000”存储在数组 data 中,用斜杠作为域分隔符。data[1]的值是 3,data[2]的值
是 15,data[3]的值是 2000。域分隔符在第三个参数中指定,若没有指定,FS 的值就是域分隔符。

delete 函数。该函数用于删除数组元素。

实例 7.56
% awk '{line[x++]=$2}END{for(x in line) delete(line[x])}' filename

说明
分配给数组 line 的值是第二个域的值,所有记录被处理完后,special for 循环将循环处理每一个
元素,delete 函数将按顺序删除每一个元素。

awk 的多维数组。虽然 awk 没有正式支持多维数组,但是提供了一个具备多维数组外

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 165

观的语法结构。这通过把多个索引串联为一个靠内建变量 SUBSEP 分隔的字符串来实现。


SUBSEP 变量包含“\034”,它是一个非打印字符,几乎不可能在索引字符中找到。表达式
matrix[2,8]实质上是数组 matrix[2 SUNSEP 8],也就是 matrix["2\0348"]。至此,索引对于关
联数组来说就变成了独一无二的字符串。

实例 7.57

% cat numbers (The Input File)


1 2 3 4 5
2 3 4 5 6
6 7 8 9 10

% awk.scnum (The Script)


1 {nf=NF
2 for(x = 1; x <= NF; x++ ){
3 matrix[NR, x] = $x
}
}
4 END { for (x=1; x <= NR; x++ ){
for (y = 1; y <= nf; y++ )
printf "%d ", matrix[x,y]
printf"\n"
}
}

% awk –f awk.scnum numbers (The Output)


1 2 3 4 5
2 3 4 5 6
6 7 8 9 10

说明
1 把 NF 的值赋给变量 nf,即域的个数(该程序每个记录的域的个数是固定值 5) 。
2 进入 for 循环,在变量 x 中保存每行域的个数。
3 数组 matrix 是一个二维数组,一维是 NR,一维是 x。将每个域的值赋给 NR 及 X。
4 在 END 模块中,使用两个 for 循环,打印存储在数组 matrix 中的值。这个例子仅有的作用是说
明二维数组是可以模拟的。

7.9.2 awk 的命令参数处理


ARGV。对于 awk 和 gawk 来说,可以合法地在命令行上使用内建数组 ARGV。这个数
组包括命令本身,但不是所有可传递到 aswk 的选项。数组 ARGV 的索引从 0 开始。
ARGC。ARGC 是一个内建的、包含命令行参数个数的变量。

实例 7.58

% cat argvs (The Script)


# This script is called argvs
BEGIN{
for ( i=0; i < ARGC; i++ ){
printf("argv[%d] is %s\n", i, ARGV[i])
}
printf("The number of arguments, ARGC=%d\n", ARGC)

PDF created with pdfFactory Pro trial version www.pdffactory.com


166 第7章

(The Output)
% awk –f argvs datafile
argv[0] is awk
argv[1] is datafile
The number of arguments, ARGC=2

说明
在 for 循环中,i 设置为 0,判断它是不是比命令行参数的个数(ARGC)小,printf 函数会按照
顺序显示每一个参数。当所有的参数都显示完后,printf 函数就打印参数的个数,ARGC。这个例子
说明 awk 并不把选项作为参数。

实例 7.59

(The Command Line)


% awk –f argvs datafile "Peter Pan" 12
argv[0] is awk
argv[1] is datafile
argv[2] is Peter Pan
argv[3] is 12
The number of arguments, ARGC=4

说明
在上一个例子中,每一个参数都被打印。awk 把命令作为第一个参数,但不包括-f 选项、脚本
名称和 argvs。

实例 7.60

(The Datafile)
% cat datafile5
Tom Jones:123:03/14/56
Peter Pan:456:06/22/58
Joe Blow:145:12/12/78
Santa Ana:234:02/03/66
Ariel Jones:987:11/12/66

(The script)
% cat arging.sc
# This script is called arging.sc
1 BEGIN{FS=":"; name=ARGV[2]
2 print "ARGV[2] is " ARGV[2]
}
$1 ~ name { print $0 }

(The Command Line)


% awk –f arging.sc datafile5 "Peter Pan"
ARGV[2] is Peter Pan
Peter Pan:456:06/22/58
awk: arging.sc:5: (FILENAME=datafile5 FNR=6 fatal; cannot
open file 'Peter Pan' for reading (No such file or directory)

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 167

说明
1 在 BEGIN 块中,ARGV[2]的值 Peter Pan 被赋给变量 name。
2 打印 Peter Pan ,但是在处理完毕并关闭了文件 datafile5 以后,awk 试图把 Peter Pan 作为一个文
件打开。awk 把参数看作输入文件。

实例 7.61

(The Script)
% cat arging2.sc
BEGIN(FS=":"; name=ARGV[2]
print "ARGV[2] is " ARGV[2]
delete ARGV[2]
}
$1 ~ name { print $0 }

(The Command Line)


% awk –f arging2.sc datafile5 "Peter Pan"
ARGV[2] is Peter Pan
Peter Pan:456:06/22/58

说明
awk 把 ARGV 数组的元素看作输入文件。处理完一个参数后就向左切换到下一个需要处理的参
数,直到 ASRGV 为空。如果参数在使用完后立刻删除,就无法作为下一个输入文件来处理。

7.10 awk 内建函数

7.10.1 字符串函数
sub 和 gsub 函数。sub 函数匹配记录中最大、最靠左边的子字符串的正则表达式,并用
替换字符串替换这些字符串。如果目标字符串已经指定,正则表达式匹配目标字符串中最大
的、最靠左的子字符串,并有替换字符串替换它。如果没有指定目标字符串就默认使用整个
记录。

格式

sub (regular expression, substitution string);


sub (regular expression, substitution string, target string)

实例 7.62

1 % awk '{sub(/Mac/, "MacIntosh");print}' filename


2 % awk '{sub(/Mac/, "MacIntosh"); $1}; print}' filename

说明
1 第一次正则表达式 Mac 在整个记录中得到匹配,它被字符串“MacIntosh”替换。替换只发生在
这行中第一次匹配发生的时候。(关于在同一行中多次匹配的情况,请参考 gsub。 )

PDF created with pdfFactory Pro trial version www.pdffactory.com


168 第7章

2 当正则表达式 Mac 在整个记录的第一个域中第一次被匹配后,它就被字符串“MacIntosh”替换。


替换只发生在目标字符串中第一次匹配发生的时候。gsub 函数则使得在所有正则表达式被匹配的
时候都发生替换。

格式

gsub(regular expression, substitution string)


gsub(regular expression, substitution string, target string)

实例 7.63

1 % awk '{ gsub(CA/, "California"); print }' datafile


2 % awk '{ gsub(/[Tt]om/, "Thomas", $1 ); print }' filename

说明
1 正则表达式 CA 无论在记录中哪里被匹配,都将被替换为“California”

2 正则表达式 Tom 或 tom 无论在记录中哪里被匹配,都将被替换为“Thomas”。

index 函数。index 函数返回子字符串第一次被匹配的位置,偏移量从位置 1 开始。

格式

index(string, substring)

实例 7.64

% awk '{ print index("hollow", "low") }' filename


4

说明
返回子字符串 low 在 hollow 中的位置,偏移量从 1 开始。

length 函数。length 函数返回没有参数的字符串的长度。length 函数返回整个记录中的


字符数。

格式
length ( string )
length

实例 7.65

% awk '{ print length("hello") }' filename


5

说明
length 函数返回字符串 hello 的字符数。

substr 函数。substr 函数返回从位置 1 开始的字符串的子字符串。如果子字符串的长度

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 169

给定,就返回字符串的一部分。如果指定的长度超过实际长度,就返回整个字符串。

格式
substr(string, starting position)
substr(string, starting position, length of string)

实例 7.66

% awk ' { print substr("Santa Claus", 7, 6 )} ' filename


Claus

说明
在字符串“Santa Claus”中打印从位置 7 开始长度为 6 的子字符串。

match 函数。match 函数返回在字符串中正则表达式位置的索引,如果找不到指定的正


则表达式就返回 0。match 函数设置内建变量 RSTART 为字符串中子字符串的开始位置,
RLENGTH 为到子字符串末尾的字符个数。substr 函数可以用这些变量来提取模式(pattern)
(仅在 awk 和 gawk 中有效)。

格式

match(string, regular expression)

实例 7.67

% awk 'END{start=match("Good ole USA", /[A-Z]+$/); print start}'


filename
10

说明
正则表达式/[A-Z]+$/表示在字符串的末尾搜索连续的大写字母。在字符串“Good ole USA”的
第 10 个位置找到字符串“USA” 。如果没有字符串匹配就返回 0。

实例 7.68

1 % awk 'END{start=match("Good ole USA", /[A-Z]+$/);\


print RSTART, RLENGTH}' filename
10 3
2 % awk 'BEGIN{ line="Good ole USA"}; \
END{ match( line, /[A-Z]+$/);\
print substr(line, RSTART,RLENGTH)}' filename
USA

说明
1 match 函数设置变量 RSTART 为正则表达式第一次被匹配的位置。 RLENGTH 设置为子字符串长度。
2 substr 函数被用来在变量 line 中寻找子字符串,并把变量 RSTART 和 RLENGTH(由 match 函数
设置)作为子字符串开始位置和长度。

PDF created with pdfFactory Pro trial version www.pdffactory.com


170 第7章

toupper 和 tolower 函数(仅在 gawk 中有效)。toupper 函数把字符串中所有的小写字母


都变成大写,而非字母的字符则不改变。同理,tolower 函数把字符串中所有的大写字母都
变成小写,而非字母的字符也不改变。

格式
toupper (string)
tolower (string)

实例 7.69

% awk 'BEGIN{print toupper("linux"), tolower("BASH 2.0")}'


LINUX bash 2.0

split 函数。split 函数使用作为第三个参数的域分隔符把字符串分割为一个数组。如果第


三个参数没有提供,awk 就默认使用当前 FS 值。

格式
split (string, array, field separator)
split (string, array)

实例 7.70

& awk 'BEGIN{split("12/25/99",date,"/");print date[2]}' filename


25

说明
split 函数使用“/”作为域分隔符把字符串“12/25/99”分割到一个叫作 data 的数组中。数组的
下标从 1 开始。打印数组 data 的第二个元素。

sprintf 函数。sprintf 函数返回指定格式的表达式。这个函数允许你使用 printf 函数的格


式说明符。

格式
variable=sprintf("string with format specifiers ", expr1, expr2, ...
, expr2)

实例 7.71

% awk '{line = sprintf ( "%-15s %6.2f ", $1 ,$3 );\


print line}' filename

说明
把第一个和第三个域按照 printf 的规格格式化了(左对齐 15 个字符和右对齐 6 位浮点数)
,并
将结果赋值给自定义变量 line。参考“printf 函数”

7.10.2 时间函数
gawk 提供了两个分别用来获取时间及格式化时间戳的函数。它们是 systime 和 strftime

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 171

函数。
systime 函数。systime 函数返回从 1970 年 1 月 1 日(称为元年)开始到当前时间(不
计闰年)的整秒数。

格式

systime()

实例 7.72

% awk 'BEGIN{now=systime(); print now}’


939515282

说明
systime 函数的返回值保存在自定义变量中。Systime 函数返回从 1970 年 1 月 1 日开始到当前时
间的不计闰年的整秒数。

strftime 函数。strftime 函数使用 C 库中的 strftime 函数格式化时间。格式说明符的格式


是%T 或%D 等等,见表 7.3。时间戳的格式跟 systime 函数返回值是一样的。如果时间戳被
忽略,则默认使用当前时间。
表 7.3 日期和时间格式说明符

定义
数据格式
假设当前时间是 1999 年 10 月 17 日,15:26:26(PDT)
%a 星期几的缩写(如 sun)
%A 星期几的完整名字(如 Sunday)
%b 月名的缩写(Oct)
%B 月名的完整写法(October)
%c 本地日期和时间(Sun Oct 17 15:26:26 1999)
%d 十进制的日期(17)
%D 日期 10/17/99a
%e 日期,如果只有一位就追加一个空格
%H 用十进制数表示 24 小时格式的小时(15)
%I 用十进制数表示 12 小时格式的小时(03)
%j 从 1 月 1 号起一年中的第几天(290)
%m 十进制数表示的月份(10)
%M 十进制数表示的分钟(26)
%p 假设是 12 小时的钟表,AM/PM(PM)
%S 十进制数表示的秒(26)
%U 十进制数表示的一年中的第几个星期(星期天做为每星期的第一天)
(42)
%w 十进制数表示的星期几(星期天是 0)
%W 十进制数表示的一年中的第几个星期(星期一做为每星期的第一天)
(41)

PDF created with pdfFactory Pro trial version www.pdffactory.com


172 第7章

续表
定义
数据格式
假设当前时间是 1999 年 10 月 17 日,15:26:26(PDT)
%x 重新设置本地日期(10/17/99)
%X 重新设置本地时间(15:26:26)
%y 两位数字表示的年(99)
%Y 当前年份(1999)
%Z 时区(PDT)
%% 百分号(%)
a. %D 和%e 只在 awk 的某些版中可以使用。

格式

systime([format specification][,timestamp])

实例 7.73

% awk 'BEGIN{now=strftime("%D", systime()); print now}'


10/09/99

% awk 'BEGIN{now=strftime("%T"); print now}'


17:58:03

% awk 'BEGIN{now=strftime("%m/%d/%y"); print now}'


10/09/99

说明
strftime 函数按照参数提供的格式化结构格式化时间和日期。参考表 7.3。如果 systime 作为秒
参数或者没有参数,就假定为当前本地时间。如果给定秒参数,则必须是跟 systime 函数返回值一
样的格式。

7.10.3 内建数学函数
表 7.4 是内建数学函数清单,x 和 y 是任意表达式。

表 7.4 数学函数

名称 返回值
atan2(x,y) y,x 范围内的余切
cos(x) 余弦函数
exp(x) 求幂
int(x) 取整
log(x) 自然对数
rand() 随机数
sin(x) 正弦

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 173

续表
名称 返回值
sqrt(x) 平方根
srand(x) x 是 rand()函数的种子 a
a. 参见 Aho, wienburger, kernighan.《Awk Programming Language》,Addison Wesley 1988,P.19。

7.10.4 整数函数
int 函数通过去掉浮点数的小数点右边的部分,把浮点数变为整数,这个过程中没有舍
入。

实例 7.74

1 % awk 'END{print 31/3}' filename


10.3333
2 % awk 'END{print int(31/3)' filename
10

说明
1 在 END 模块中打印除法结果的浮点数。
2 END 模块中 int 函数对除法的结果取整,然后打印这个整数。

7.10.5 随机数发生器
rand 函数。rand 函数用来产生一个大于等于 0 而小于 1 的随机数。

实例 7.75

% awk '{print rand()}' filename


0.513871
0.175726
0.308634

% awk '{print rand()}' filename


0.513871
0.175726
0.308634

说明
每次打印的值都一样。srand 函数能使 rand 函数从一个新的初始值开始生成随机数。否则每次使
用 rand 函数都会按照相同的顺序重复这些数。

srand 函数。没有参数的 srand 函数用时间作为 rand 函数的种子。srand(x)则用 x 作为种


子,在程序运行过程中,x 应当是不断变化的。

实例 7.76

% awk 'BEGIN{srand()}; {print rand()}' filename


0.508744

PDF created with pdfFactory Pro trial version www.pdffactory.com


174 第7章

0.639485
0.657277

% awk 'BEGIN{srand()};{print rand()' filename


0.133518
0.324747
0.691794

说明
srand 函数为 rand 函数设置了新的种子,开始点是当时的时间。每次调用 rand 都打印不同的值。

实例 7.77

% awk 'BEGIN{srand()};{print 1 + int(rand() * 25)}' filename


6
24
14

说明
srand 函数为 rand 函数设置了新的种子。开始点是当时的时间。srand 选择了一个在 0~25 之间
的随机数,并把它取整。

7.11 自定义函数
自定义函数可以放在脚本中任何可以放置模板和动作的地方。

格式

function name ( parameter, parameter, parameter, ...) {


statements
return expression
(The return statement and expression are optional )
}

给函数中本地变量传递值。只使用变量的拷贝。数组通过地址或者指针传递,所以可以
在函数内部直接改变数组元素的值。函数内部使用的任何没有作为参数传递的变量都被看作
是全局变量,也就是这些变量对于整个程序都是可见的。如果变量在函数中发生了改变,那
么就是在整个程序中发生了改变。惟一向函数提供本地变量的办法就是把它们放在参数列表
中,这些参数通常被放在列表的最后。如果函数调用的时候没有提供正式的参数,那么参数
就初始化为空。return 语句通常就返回程序控制并向函数调用者返回一个值。

实例 7.78

(The Command Line Display of grades File before Sort)


% cat grades
44 55 66 22 77 99
100 22 77 99 33 66

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 175

55 66 100 99 88 45

(The Script)
% cat sorter.sc
# Script is called sorter
# It sorts numbers in ascending order
1 function sort ( scores, num_elements, temp, i j ) {
# temp,i,and j will be local and private,
# with an initial value of null.
2 for( i = 2; i <= num_elements ; ++i ) {
3 for ( j = i; scores [j-1] > scores[j]; --j ){
temp = scores[j]
scores[j] = scores[j-1]
scores[j-1] = temp
}
4 }
5 }
6 {for ( i =1; i <= NF; i++ )
grades[i]=$i
7 sort(grades, NF) # Two arguments are passed
8 for( j = 1; j <= NF; ++j )
printf( "%d", grades[j] )
printf("\n")
}
(After the Sort)
% awk –f sorter.sc grades
22 44 55 66 77 99
22 33 66 77 99 100
45 55 66 88 99 100

说明
1 定义函数 sort。函数可以在脚本的任何位置定义。除了作为参数传递的那些变量,所有的变量都
是全局的。如果它们在函数中发生了变化,就是在整个程序中发生了改变。数组可以用指针传递。
5 个正式的参数在括号内。数组 scores 通过指针传递,所以所有的元素在函数中均被修改,也就
在整个程序中修改。变量 num——elements 是本地变量,是原始变量的副本。变量 temp、i 及 j
都是本地变量。
2 外部 for 循环整个数组,这里需要至少两个数来比较。
3 内部 for 循环比较当前数和前一个数。如果前一个元素的值比较大,temp 就被赋值为当前元素的
值,当前元素被赋值为前一个元素的值。
4 外部循环结束。
5 函数定义结束。
6 脚本的第一个动作块从这里开始,for 循环当前记录的每一个域,建立一个数字数组。
7 调用 sort 函数,传递当前记录的数字数组和当前记录的域的个数。
8 sort 函数结束,程序控制从这里重新开始。for 循环打印已经分类的数组元素。

7.12 复 习
% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18

PDF created with pdfFactory Pro trial version www.pdffactory.com


176 第7章

southern SO May Chin 5.1 .95 4 15


southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 7.79

% awk '{if ( $8 > 15 ){ print $3 " has a high rating"}\


else print $3 "---NOT A COMPETITOR---"}' datafile

Joel---NOT A COMPETITOR---
Sharon has a high rating
Chris has a high rating
May---NOT A COMPETITOR---
Derek has a high rating
Susan has a high rating
TJ---NOT A COMPETITOR---
Val---NOT A COMPETITOR---
Sheri---NOT A COMPETITOR---

说明
if 语句是一个动作语句。如果后面有不止一个表达式就需要以花括号把它们括起来(这个例子
不需要花括号是因为这里只有一个表达式) 。表达式的含义是——如果第八个域比 15 大,就打印第
三个域和字符串“has a high tating”
,否则就打印第三个域和“---NOT A COMPETITOR---”

实例 7.80

% awk '{i=1; while(i<=NF && NR < 2){print $i; i++}}' datafile


northwest
NW
Joel
Craig
3.0
.98
3
4

说明
给自定义变量 i 赋值为 1。进入 while 循环对表达式判断。如果表达式为真就执行 print 函数,打
印第 i 个域的值及 i 的值。然后 i 增加 1,再次进入循环。当 i 的值大于 NF 值且 NR 的值大于 2 时,
表达式就为假。在进入到下一个记录以前,i 不会被再次初始化。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 177

southern SO May Chin 5.1 .95 4 15


southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 7.81

% awk '{ for( i=3 ; i <= NF && NR == 3 ; i++ ){ print $i }}' datafile
Chris
Foster
2.7
.8
2
18

说明
跟 while 循环功能类似。初始化、判断和循环控制在同一个表达式中。当前记录变量 i 只初始化
一次(i=3)。如果 i 小于或者等于 NF,并且 NR 等于 3,就执行 print 模块。在打印第 i 个域的值以
后,控制返回循环表达式。i 的值增加 1,然后再次进行判断。

实例 7.82

(The Command Line)


% cat awk.sc4
# Awk script illustrating arrays
BEGIN{OFS='\t'}
{ list[NR] = $1 } # The array is called list. The index is the
# number of the current record. The value of the
# first field is assigned to the array element.
END{ for (n = 1; n <= NR; n++){
Print list[n]} # for loop is used to loop
# through the array.
}

(The Command Line)


% awk –f awk.sc4 datafile
northwest
western
southwest
southern
southeast
eastern
northeast
north
central

说明
数组 list 用 NR 作为索引。每次都将所处理行的第一个域赋给数组。在 END 模块中,for 循环循
环数组中的每一个元素。

PDF created with pdfFactory Pro trial version www.pdffactory.com


178 第7章

实例 7.83

(The Command Line)


% cat awk.sc5
# Awk script with special for loop
/north/{name[count++]=$3}
END{ print "The number living in a northern district: " count
print "Their names are: #"
for ( i in name ) # special awk for loop is used to
print name[i] # iterate through the array.
}

% awk –f awk.sc5 datafile


The number living in a northern district: 3
Their names are:
Joel
TJ
Val

说明
每次正则表达式 north 出现在行中,都把第三个域的值赋给数组 name。每当一个新的记录被处
理,索引 count 就增加 1,于是就在数组中产生一个新的元素。在 END 模块中,special for 循环用来
循环整个数组。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 7.84

(The Command Line)


% cat awk.sc6
# Awk and the special for loop
{region[$1]++} # The index is the first field of each record

END{for(item in region){
Print region[item], item
}
}

% awk –f awk.sc6 datafile

1 central

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 179

1 northwest
1 western
1 southeast
1 north
1 southern
1 northeast
1 southwest
1 eastern

% awk –f awk.sc6 datafile3


4 Mary
2 Tom
1 Alax
1 Bob
1 Sean

说明
数组 region 用第一个域作为索引,这个值保存的是 region 被找到的次数。END 模块中用特殊的
awk for 循环循环称为 region 的数组。

Linux 工具实验室 6

(File lab6.data)

Mike Harrington :(510) 548-1278:250:100:175


Christian Dobbins:(408) 538-2358:155:90:201
Susan Dalsass:(206) 654-6279:250:60:50
Archie McNichol:(206) 548-1348:250:100:175
Jody Savage: (206) 548-1278:15:188:150
Guy Quigley:(916) 343-6410:250: 100:175
Dan Savage:(406) 298-7744:450:300:275
Nancy McNeil:(206) 548-1278:250:80:75
John Goldenrod:(916) 348-4278:250:100:175
Chet Main:(510) 548-5258:50:95:135
Tom Savage:(408) 926-3456:250:168:200
Elizabeth Stachelin:(916) 440-1763:175:75:300

数据库中包含名字、电话和为党派运动捐款的数额。写一个awk脚本产生一个如下的
报告:

***FIRST QUARTERLY REPORT****


***CAMPAIGN 2000 CONTRIBUTIONS***
-------------------------------------------------------------------------
NAME PHONE Jan | Feb | Mar | Total Donated
-------------------------------------------------------------------------
Mike Harrington (510)548-1278 250.00 100.00 175.00 525.00
Christian Dobbins (408)538-2358 155.00 90.00 201.00 446.00
Susan Dalsass (206)654-6279 250.00 60.00 50.00 360.00
Archie McNichol (206)548-1348 250.00 100.00 175.00 525.00

PDF created with pdfFactory Pro trial version www.pdffactory.com


180 第7章

Jody Savage (206)548-1278 15.00 188.00 150.00 353.00


Guy Quigley (916)343-6410 250.00 100.00 175.00 525.00
Dan Savage (406)298-7744 450.00 300.00 275.00 1025.00
Nancy McNeil (206)548-1278 250.00 80.00 75.00 405.00
John Goldenrod (916)348-4278 250.00 100.00 175.00 525.00
Chet Main (510)548-5258 50.00 95.00 135.00 280.00
Tom Savage (408)926-3456 250.00 168.00 200.00 618.00
Elizabeth Stachelin (916) 440-1763 175.00 75.00 300.00 550.00
-------------------------------------------------------------------------
SUMMARY
-------------------------------------------------------------------------
The campaign received a total of $6137.00 for this quarter.
The average donation for the 12 contributors was $511.42.
The highest total contribution was $1025.00 made by Dan Savage.
***THANKS Dan***
The following people donated over $500 to the campaign.
They are eligible for the quarterly drawing!!
Listed are their names (sorted by last names) and phone numbers:
John Goldenrod--(916) 348-4278
Mike Harrington--(510) 548-1278
Archie McNichol--(206) 548-1348
Guy Quigley--(916) 343-6410
Dan Savage--(406) 298-7744
Tom Savage--(408) 926-3456
Elizabeth Stachelin--(916) 440-1763
Thanks to all of you for your continued support!!

7.13 其 他 细 节
从磁带或电子表格中读取的数据也许没有明显域分隔符,但是数据有固定的列宽,可以
用 substr 函数预处理这些数据。 (这部分文件可以在 www.infopower.com.cn 网站下载中心所
提供的相关资料的 chapon/oddsAnd-Ends 目录下找到。

7.13.1 固定的域
在下面的例子中,域的宽度是固定的,但不是由域分隔符分隔的。substr 函数用来建立
域。对于 gawk 请参考“域宽变量”。

实例 7.85

% cat fixed
031291ax5633(408)987-0124
021589bg2435(415)866-1345
122490de1237(916)933-1234
010187ax3458(408)264-2546
092491bd9923(415)134-8900
112990bg4567(803)234-1456
070489qr3455(415)899-1426

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 181

% awk '{printf substr($0,1,6)" ";printf substr($0,7,6)" ";\


print substr($0,13,length)}'fixed
031291 ax5633 (408)987-0124
021589 bg2435 (415)866-1345
122490 de1237 (916)933-1234
010187 ax3458 (408)264-2546
092491 bd9923 (415)134-8900
112990 bg4567 (803)234-1456
070489 qr3455 (415)899-1426

说明
从第一个字符开始的 6 个字符作为第一个域被从整个记录中以提取字符串的方式提取出来。接
着打印空格。从第七个字符开始的 6 个字符作为第二个域被从整个记录中以提取字符串的方式提取
出来。接着再打印空格。从第十三个字符开始的其余的字符作为最后一个域被从整个记录中以提取
字符串的方式提取出来。

空域。如果数据保存在固定宽度的域内,那么一些域就可能是空的。在下面的例子中,
substr 函数用来保存这些域,而无论它们是否含有数据。

实例 7.86

1 % cat db
xxx xxx
xxx abc xxx
xxx a bbb
xxx xx

% cat awkfix
# Preserving empty fields. Field width is fixed.
{
2 f[1]=substr($0,1,3)
3 f[2]=substr($0,5,3)
4 f[3]=substr($0,9,3)
5 line=sprintf("%-4s%-4s%-4s\n", f[1], f[2], f[3])
6 print line
}
% awk –f awkfix db
xxx xxx
xxx abc xxx
xxx a bbb
xxx xx

说明
1 打印文件 db 的内容,这个文件中包含空域。
2 数组 f 的第一个元素被赋值为从第一个位置开始的三个字符的子字符串。
3 数组 f 的第二个元素被赋值为从第五个位置开始的三个字符的子字符串。
4 数组 f 的第二个元素被赋值为从第九个位置开始的三个字符的子字符串。
5 数组元素被赋值为用 print 函数格式化以后的自定义变量 line。
6 打印变量 line 的值,保存空域。

FIELDWIDTHS 变量。如果文件中域的宽度固定,则可以使用 FIELDWIDTHS 变量(仅


在 gawk 中使用)。这个变量的值是以空格分隔的数字列表,列表中的每个数字都表示相应的

PDF created with pdfFactory Pro trial version www.pdffactory.com


182 第7章

域的宽度。如果设置了变量 FIELDWIDTHS,FS 的值就将被忽略。

实例 7.87

% cat fixedfile
abc1245556
xxxyyyzzzz

% awk 'BEGIN{FIELDWIDTHS="3 3 4"}{print $2}' fixedfile


124
yyy

说明
gawk 包含了一个变量 FIELDWIDTHS 用来控制如何将行分割为域。这个变量的值是空格分隔的
数字列表,3 3 4 表示记录由固定的域组成,第一个长度是 3,第二个是 3,第三个是 4。即使没有域
分隔符,文件 fixedfile 的记录也会根据上面的数值分割为固定的域。

有$的数字、逗号和其他字符。在下面的例子中,价格域包含一个美元符号和一个逗号。
要计算最后的结果,这些符号必须被去掉。可以用 gsub 处理这件事情。

实例 7.88

% cat vendor
access tech:gp237221:220:vax789:20/20:11/01/90:$1,043.00
alisa systems:bp262292:280:macintosh:new updates:06/30/91:$456.00
alisa systems:gp262345:260:vax8700:alisa talk:02/03/91:$1,598.50
apple computer:zx342567:240:macs:e-mail:06/25/90:$575.75
caci:gp262313:280:sparc station:network11.5:05/12/91:$1,250.75
datalogics:bp132455:260:microvax2:pagestation
maint:07/01/90:$1,200.00
dec:zx354612:220:microvax2:vms sms:07/20/90:$1,350.00

% awk –F: '{gsub(/\$,"");gsub(/,/,""); cost +=$7};\


END{print "The total cost is $" cost}' vendor
The total cost is $7474

说明
第一个 gsub 函数全局替换美元符号($)为空字符串,第二个 gsub 函数全局替换逗号为空字符
串。把七个域的值加起来赋值给自定义变量 cost。在 END 模块中,打印字符串“The total cost is $”

然后是变量 cost 的值。a
a.关于逗号在程序中的具体用法,请参考《The Awk Programming Language》Aefred Aho,Brian Kernighan,PeterWienberger,
Addison Wesley,1988,P.72。

7.13.2 捆绑和解捆绑文件
捆绑程序。在 Alfred、Brian Kernifghan 和 Peter Wienberger 共同编写的《AWK Programming
Language》中,捆绑文件的程序非常的短。我们现在准备合并多个文件以节约磁盘空间,或
者用电子邮件发送文件等等。下面的 awk 程序将打印文件的每一行,且每一行前面都有文件
的名称。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 183

实例 7.89
% awk '{ print FILENAME,$0 }' filel file2 file3 > bundled

说明
打印当前输入文件的文件名 FILENAME,然后是 file1 中的每一行记录。到达 file1 的末尾以后,
awk 将打开下一个文件 file2,做相同的事情,如此继续。输入文件被重新定向到一个叫作 bundled 的
文件。

解捆绑。下面的例子解释如何把两个文件分开。

实例 7.90

% awk '$1 != previous { close(previous); previous=$1};\


{print substr($0, index($0," ") + 1) > $1}' bundled

说明
第一个域是文件的名字,如果文件名与用户自定义的变量 previous 的值不同(初始值为 null),
就执行后面的语句。关闭赋值给 previous 的文件,previous 被赋值为第一个域的值,记录的 substr
被指向 index 函数返回的位置,即包含文件名的第一个域。
要绑定文件,在文件内容上面文件各独占一行,使用如下的命令:
% awk '{if(FNR==1){print FILENAME;print $0}\
else print $0}' filel file2 file3 > bundled

如下的命令将松开绑定:
% awk 'NF==1{filename=$NF} ;\
NF != 1{print $0 > filename}' bundled

7.13.3 多行记录
目前例子中使用的数据文件,每个记录只占用一行。后面的数据文件,叫作 checkbook,
记录用空行来分隔,域用换行符来分隔。要处理这样的文件,应将记录分隔符 RS 赋值为空,
域分隔符 FS 赋值为换行符。

实例 7.91

(The Input File)


% cat checkbook
1/1/99
#125
-695.00
Mortgage

1/1/99
#126
-56.89
PG&E

1/2/99
#127

PDF created with pdfFactory Pro trial version www.pdffactory.com


184 第7章

-89.99
Safeway
1/3/99
+750.00
Pay Check

1/4/99
#128
-60.00
Visa

(The Script)
% cat awdchecker
1 BEGIN{RS=""; FS="\n";ORS="\n\n"}
2 {print NR, $1,$2 $3 $4}

(The Output)
% awk –f awkchecker checkbook
1 1/1/99 #125 -695.00 Mortgage

2 1/1/99 #126 -56.89 PG&E

3 1/2/99 #127 -89.99 Safeway

4 1/3/99 +750.00 Pay Check

5 1/4/99 #128 -60.00 Visa

说明
1 在 BEGIN 块中,RS 赋值为空,FS 赋值为换行符,输出记录分隔符(ORS)赋值为两个换行符。
现在每行是一个域,每个输出记录都由两个换行符分隔。
2 打印记录号,后面是所有的域。

7.13.4 生成正规信笺
下面例子是从《AWK Programming Language》中的一个例子修改得到的。跟踪实际的
处理过程是这个例子中最棘手的部分。输入文件叫作 data.file,它只是包含数据。输入文件
的域依靠冒号分隔。其他的文件叫作 data.letter。这是用于建立时间信笺的格式。该文件用
getline 装入 awk 内存中。正规信笺的每一行都保存在数组中。程序从 data.file 中取得数据,
并把 form.letter 文件中以#和@开头的字符串用 data.file 中的真实数据来替换,从而建立信笺。
临时变量 temp 保存数据替换后需要显示的行。程序允许你为 data.file 文件中的每一个人建
立个性化的格式信笺。

实例 7.92

(The Awk Script)


% cat form.awk
# form.awk is an awk script that requires access to 2 files: The
# first file is called "form.letter". This file contains the
# format for a form letter. The awk script uses another file,
# "data.form", as its input file. This file contains the

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 185

# information that will be substituted into the form letters in


# place of the numbers preceded by pound signs. Today's date
# is substituted in the place of "@date" in "form.letter".
1 BEGIN{ FS=":"; n=1
2 while(getline < "form.letter" > 0)
3 form[n++] = $0 #Store lines from form.letter in an array
4 "date" | getline d; split(d, today, " ")
# Output of date is Sun Mar 2 14:35:50 PST 1999
5 thisday=today[2]". "today[3]", "today[6]
6 }
7 { for( i = 1; i < n;i++ ){
8 temp=form[i]
9 for ( j = 1; j <=NF; j++ ) {
gsub("@date", thisday, temp)
10 gsub("#" j, $j , temp )
}
11 Print temp
}
}

% cat form.letter
The form letter, form.letter, looks like this:
***************************************************
Subject: Status Report for Project "#1"
To: #2
From: #3
Date: @date
This letter is to tell you, #2, that project "#1" is up to
date.
We expect that everything will be completed and ready for
shipment as scheduled on #4.

Sincerely,

#3
*********************************************************

The file, data.form, is awk's input file containing the data that
will replace the #1-4 and the @date in form.letter.
% cat data.form
Dynamo:John Stevens:Dana Smith, Mgr:4/12/1999
Gallactius:Guy Sterling:Dana Smith, Mgr:5/18/99

(The Command Line)

% awk –f form.awk data.form


**************************************************************
Subject: Status Report for Project "Dynamo"
To: John Stevens
From: Dana Smith, Mgr
Date: Mar. 2, 1999
This letter is to tell you, John Stevens, that project
"Dynamo" is up to date.
We expect that everything will be completed and ready for
shipment as scheduled on 4/12/1999.

Sincerely,

Dana Smith, Mgr

PDF created with pdfFactory Pro trial version www.pdffactory.com


186 第7章

Subject: Status Report for Project "Gallactius"


To: Guy Sterling
From: Dana Smith, Mgr
Date: Mar. 2, 1999
This letter is to you, Guy Sterling, that project "Gallactius"
is up to date.
We expect that everything will be completed and ready for
Shipment as scheduled on 5/18/99.

Sincerely,

Dana Smith, Mgr

说明
1 在 BEGIN 块中,FS 赋值为冒号,自定义变量 n 的值是 1。
2 在 while 循环中,getline 函数从文件 form.letter 每次读取一行。如果 getline 无法找到文件,就返
回-1,当到达文件的末尾时返回 0。所以若返回值大于 1,则说明函数从文件中读取了一行。
3 文件 form.letter 中的每一行都赋值给数组 form。
4 UNIX 的 date 命令的输出通过管道输出给 getline 函数并赋值给自定义变量 d。Split 函数用空格分
割变量 d,建立一个叫作 today 的数组。
5 月、日和年被赋值给自定义变量 thisday。
6 BENGIN 块结束。
7 for 循环循环 n 次。
8 自定义变量 temp 被赋值为从数组 form 中读取的行。
9 嵌套 for 循环输入文件 data.form 的每一行 NF 次。保存在变量 temp 中的每一行都被测试是否有
字符串@date 存在,如果存在 gsub 就把它替换为当前的日期。
10 如果在 temp 中某行发现一个#和数字,gsub 就把它替换为输入文件 data.form 中对应的域。例如,
第一行中的#1 将被替换为 Dynamo,#2 替换为 John Stevens,#3 替换为 Dana Smith,#4 替换为
4/12/1999 等等。
11 替换以后打印该行。

7.13.5 与 shell 交互
你已看到了 awk 是如何工作的,你将发现它是一个非常有用的脚本工具。你可以在 Shell
脚本中嵌入一行 awk 命令或者一个 awk 脚本。下面是一个嵌入 awk 命令的 Shell 脚本。

实例 7.93

#!/bin/bash
# Scriptname: bash.sc
# This bash shell script will collect data for awk to use in
# generating form letter(s). See above.
echo "Hello $LOGNAME."
echo "This report is for the month and year:"
1 cal | awk 'NR==1{print $0}'

if [[ -f data.form || -f formletter? ]]
then
rm data.form formletter? 2> /dev/null
fi
let num=1
while true
do

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 187

echo "Form letter #$num:"


echo –n "What is the name of the project? "
read project
echo –n "Who is the status report from? "
read sender
echo –n "Who is the status report to? "
read recipient
echo –n "When is the completion date scheduled? "
read due_date
echo $project:$recipient:$sender:$due_date > data.form
echo –n "Do you wish to generate another form letter? "
read answer
if [[ "$answer" != [Yy]* ]]
then
break
else
2 awk -f form.awk data.form > formletter$num
fi
(( num+=1 ))
done
awk –f form.awk data.form > formletter$num

说明
1 Linux 的 cal 命令通过管道把输出传递给 awk。打印包含当前月和年的第一行。
2 awk 脚本 form.awk 生成正规信笺,并重新定向给一个 UNIX 文件。

7.14 回 顾

7.14.1 字符串函数

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 7.94
% awk 'NR==1{gsub(/northwest/,"southeast", $1) ;print}' datafile
southeast NW Joel Craig 3.0 .98 3 4

说明
如果这是第一个记录(NR==1)
,且在第一个域中找到 northeast,则用 southeast 全局替换 northwest。

PDF created with pdfFactory Pro trial version www.pdffactory.com


188 第7章

实例 7.95
% awk 'NR==1{print substr($3, 1, 3)}' datafile
Joe

说明
如果这是第一个记录,显示从第一个字符开始长度为三的字符的第三个域的子字符串,打印子字
符串 Joe。

实例 7.96
% awk 'NR==1{print length($1)}' datafile
9

说明
如果这是第一个记录就打印第一个域的长度。

实例 7.97
% awk 'NR==1{print index($1,"west")}' datafile
6

说明
如果这是第一个记录,打印第一个域中第一次找到 west 的位置。子字符串 west 在 northwest 字
符串的第六个位置。

实例 7.98
% awk '{if (match($1,/^no/)){print substr($1,RSTART,RLENGTH)}}'
datafile
no
no
no

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

说明
如果 match 函数在第一个域中匹配正则表达式/^no/,就返回最左边字符的索引位置。内建变量
RSTART 保存索引位置而 RLENGTH 变量保存匹配子字符串的长度。substr 函数返回第一个域内字
符串的开始位置、RSTART 和 RLENGTH 字符数。

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 189

实例 7.99

% awk 'BEGIN{split("10/14/98",now,"/");print now[1],now[2],now[3]}'


10 14 98

说明
字符串 10/14/98 被分割为一个称为 now 的数组,分隔符是正斜线。从第一个数组元素开始,打
印所有数组元素。

% cat datafile2
Joel Craig:northwest:NW:3.0:.98:3:4
Sharon Kelly:western:WE:5.3:.97:5:23
Chris Foster:southwest:SW:2.7;.8:2:18
May Chin:southern:SO:5.1:.95:4:15
Derek Johnson:Southeast:SE:4.0:.7:4:17
Susan Beal:eastern:EA:4.4:.84:5:20
TJ Nichols:northeast:NE:5.1:.94:3:13
Val Shultz:north:NO:4.5:.89:5:9
Sheri Watson:central:CT:5.7:.94:5:13

实例 7.100

% awk –F: '/north/{split($1, name, " ");\


print "First name: "name[1];\
print "Last name: " name[2];\
print "\n--------------------"}' datafile2

First name: Joel


Last name: Craig
-----------------------
First name: TJ
Last name: Nichols
------------------------
First name: Val
last name: Shultz
------------------------

说明
输入文件分隔符设置为冒号(-F:)
。如果记录包含正则表达式 north,则第一个域就被分割为数
组 name,空格作为分隔符。打印数组元素。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20

PDF created with pdfFactory Pro trial version www.pdffactory.com


190 第7章

northeast NE TJ Nichols 5.1 .94 3 13


north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

实例 7.101

% awk '{line=sprintf("%10.2f%5s",$7,$2); print line}' datafile


3.00 NW
5.00 WE
2.00 SW
4.00 SO
4.00 SE
5.00 EA
3.00 NE
5.00 NO
5.00 CT

说明
sprintf 函数用 printf 函数的方式格式化了第七个和第二个域($7,$2),格式化后的字符串被赋
值给自定义变量 line 并被打印出来。

7.14.2 命令行参数

实例 7.102

% cat argvs.sc
# Testing command line arguments with ARGV and ARGC using a for loop.

BEGIN{
for(i=0;i < ARGC;i++)
printf("argv[%d] is %s\n", i, ARGV[i])
printf("The number of arguments, ARGC=%d\n",ARGC>
}

% awk –f argvs.sc datafile


argv[0] is awk
argv[l] is datafile
The number of arguments, ARGC=2

说明
BEGIN 块中有一个 for 循环负责处理命令行参数。ARGC 是参数的个数,ARGV 是一个包含所
有参数的数组。awk 不把选项作为参数。在这个例子中,合法的参数只有 awk 命令和输入文件——
datafile。

实例 7.103
% awk 'BEGIN{name=ARGV[1]};\
$0 ~name {print $3 , $4}' "Derek" datafile
awk: can't open Derek

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 191

source line number 1

% awk 'BEGIN{name=ARGV[1]; delete ARGV[1]};\


$0 ~ name {print $3, $4}' "Derek" datafile
Derek Johnson

说明
1 BEGIN 块中名字“Derek”被赋值给变量 name。在模式动作块中,awk 尝试把“Derek”作为一
个文件名并打开它,但是失败了。
2 在把“Derek”赋值给变量 name 后,ARGV[1]被删除。当启动模式动作块的时候,awk 不再尝试
打开输入文件“Derek”,而是打开 datafile。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18
southern SO May Chin 5.1 .95 4 15
southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

7.14.3 读取输入(getline)
实例 7.104

% awk 'BEGIN{"date" | getline d; print d}' datafile


Thu Jan 15 11:24:24 PST 2000

说明
UNIX 命令通过管道把“date”命令的结果重新定向给 getline 函数。将结果保存在变量 d 中并
打印出来。

实例 7.105
% awk 'BEGIN{ "date" | getline d; split(d, mon);print mon[2]}' datafile
Jan

说明
UNIX/Linux 命令通过管道把 date 命令的结果重新定向给 getline 函数并把结果保存在变量 d 中。
split 函数把变量 d 分割为一个叫作 mon 的数组。打印数组的第二个元素。

实例 7.106
% awk 'BEGIN{ printf "Who are you looking for?" ; getline name <
"/dev/tty"};'

PDF created with pdfFactory Pro trial version www.pdffactory.com


192 第7章

说明
从终端/dev/tty 读取输入,保存在数组 name 中。

实例 7.107

% awk 'BEGIN{while(getline < "/etc/passwd" > 0 ){lc++}; print lc}'


datafile
16

说明
while 循环用来每次处理/etc/passwd 文件中的一行。每次进入循环,getline 就读取一行且 lc 的值
增加 1。当退出循环时,打印 lc 的值,具体说只要 getline 的返回值不是 0,就打印/etc/passwd 文件
的行号,也就是一行被读取以后,继续循环。

7.14.4 控制函数

实例 7.108

% awk '{if ( $5 > 4.5) next; print $1}' datafile


northwest
southwest
southeast
eastern
north

说明
如果第五个域大于 4.5,就从输入文件 datafile 中读取下一行并从 awk 脚本开始处理这行(在
BEGIN 块后面)
。否则打印第一个域。

实例 7.109

% awk '{if ($2 ~ /S/){print ; exit 0}}' datafile


southwest SW Chris Foster 2.7 .8 2 18

% echo $status ( csh, tcsh ) or echo $? (bash, sh, ksh)


0

说明
如果第二个域包含一个 S,打印记录并退出 awk 脚本。TC 或者 C Shell 的退出状态变量包含退
出值。如果使用的 bash、Bourne 或者 Korn Shell,$?变量就包含退出状态值。

% cat datafile
northwest NW Joel Craig 3.0 .98 3 4
western WE Sharon Kelly 5.3 .97 5 23
southwest SW Chris Foster 2.7 .8 2 18

PDF created with pdfFactory Pro trial version www.pdffactory.com


gawk 功能:gawk 编程 193

southern SO May Chin 5.1 .95 4 15


southeast SE Derek Johnson 4.0 .7 4 17
eastern EA Susan Beal 4.4 .84 5 20
northeast NE TJ Nichols 5.1 .94 3 13
north NO Val Shultz 4.5 .89 5 9
central CT Sheri Watson 5.7 .94 5 13

7.14.5 自定义函数

实例 7.110

(The Command Line)


% cat awk.sc7
1 BEGIN{largest=0}
2 {maximum=max($5)}

3 function max (num) {


4 if ( num > largest ){ largest=num }
return largest
5 }
6 END{ print "The maximum is " maximum "."}
% awk –f awk.sc7 datafile
The maximum is 5 .7.

说明
1 在 BEGIN 块中,自定义变量 largest 被初始化为 0。
2 文件中的每一行,函数 max 的返回值都保存在变量 maxium 中,$5 作为函数 max 的参数。
3 自定义函数 max,函数语句包含在花括号中。每次从输入文件 datafile 中读取新的记录时,都要
调用 max 函数。
4 比较 num 和 largest 的值,返回其中比较大的值,并保存在变量 largest 中。
5 函数定义结束。
6 END 块打印变量 maxium 的最终值。

Linux 工具实验室 7

(File lab7. data)

Mike Harrington:(510) 548-1278:250:100:175


Christian Dobbins:(408) 538-2358:155:90:201
Susan Dalsass:(206) 654-6279:250:60:50
Archie McNichol:(206) 548-1348:250:100:175
Jody Savage:(206) 548-1278:15:188:150
Guy Quigley:(916) 343-6410:250:100:175

PDF created with pdfFactory Pro trial version www.pdffactory.com


194 第7章

Dan Savage:(406) 298-7744:450:300:275


Nancy McNeil:(206) 548-1278:250:80:75
John Goldenrod:(916) 348-4278:250:100:175
Chet Main:(510) 548-5258:50:95:135
Tom Savage:(408) 926-3456:250:168:200
Elizabeth Stachelin:(916) 440-1763:175:75:300

上面的数据库包含名字、电话和最近三个月给党派运动捐献的金钱的数额。
1.写一个自定义函数,用来返回指定月份的平均捐赠数额。月份通过命令行传递。
2.写另外一个自定义函数打印这个报告生成的数据。

PDF created with pdfFactory Pro trial version www.pdffactory.com


第8章

交互使用
bash Shell
8.1 介 绍
在交互 Shell 中,标准的输入、输出和错误都捆绑在终端上。在使用 Bourne Again Shell
的时候,只需要在提示符下输入 Linux 或者 UNIX 命令然后等待回应。bash 为你提供了大量
的内建命令和命令行快捷方式,例如 history、alias 文件、命令自动完成和命令行编辑等等。
一些特性属于标准的 UNIX Bourne Shell,但是更多的是 Gnu 对于 Shell 的扩展,它增加了许
多新的功能,特别是增加了对 POSIX 的兼容。在 bash2.x 中,很多 UNIX 下的 Korn Shell 的
功能和 C Shell 的功能被加入进来,使得 bash 在向上兼容标准 Bourne Shell 的同时,在交互
和编程方面都有了很完善的全功能。对于 Linux 用户和 UNIX 用户来说,bash 提供了标准
UNIX Shell 以外的另一种选择。
本章将专注于如何在命令行与 bash 交互以及如何个性化你的工作环境。你将学习如何利
用快捷方式和内建特性建立一个高效且有趣的工作环境,而下一章将带你进一步深入。你将
学习如何编写 bash 脚本以进一步设计你的工作环境,使其可以自动处理日常工作,开发更为
精辟的脚本。如果你是一个管理员,则不但可以为自己做这些事情,还可以帮助你的用户群。

8.1.1 Bash 的版本


“Bourne Again Shell 属于摩羯座,出生于 1988 年 1 月 10 日,其生父是 Brian Fox,随
后被 Chet Ramey 收养,他正式维护 bash,加强它,为它除错。”bash 的第一个版本是 0.99
版本。写作本书时的最新版本是 2.03 版本,主要的强化完成于 2.0 版本,现在仍然有很多操
作系统使用 1.14.7 版。所有版本的 bash 都可以在 Gnu 通用许可证的保护下自由取得。若想
了解你目前使用的 bash 的版本,可以在 bash 中使用选项 -version 或打印环境变量
BASH_VERSION 的值。

实例 8.1

$ bash –version
GNU bash, version 2.03.0(1)-release (i686-pc-linux-gnu)
copyright 1998 Free Software Foundation, Inc.

PDF created with pdfFactory Pro trial version www.pdffactory.com


196 第8章

$ echo $BASH_VERSION
2.03.0(1)-release

8.1.2 开始
如果 bash Shell 是你的登录 Shell,那么在你看到 Shell 提示符以前,它要按照处理链条
完成一系列处理。1

图 8.1 以 bash Shell 启动

在引导系统时,第一个需要运行的进程就是 init,PID#1。它衍生出一个 getty 终端过程,


该过程打开一个终端端口,提供一块空间给标准输入、标准输出和标准错误,并把提示符显
示在屏幕上。然后执行程序/bin/login。login 程序提示输入密码,加密并验证密码,建立一
个初始环境,启动登录 shell——/bin/bash——/etc/passwd 文件的最后一项。bash 寻找系统文
件/etc/profile 并执行其中的命令,然后在用户目录下寻找名字叫作.bash_profile 的初始化文
件。执行完 bash_profile2 中的命令以后,就处理用户环境文件中的命令,这个文件通常称
为.bashrc,最后默认提示符美元符号将显示在你的屏幕上,等待你输入命令。(要了解更多
关于初始化的问题,请参考“环境”)。
用 chsh 改变登录 Shell。chsh 命令允许你改变登录 Shell。例如,当前你使用的是标准
Bourne Shell,但你想以 bash 作为登录 Shell,则可以用 chsh 改变登录 Shell。如果你没有在
命令行中输入要改变的 Shell,chsh 就会提示你输入一个。所有合法的 Shell 清单都存储在
/etc/shells 文件中,见表 8.1。

表 8.1 chsh 命令

选项 功能
-l,--list-shells 打印/etc/shells 中合法 Shell 的清单并退出
-s,--shell 指定登录 Shell

1.要取得最新的 bash 版本,请访问站点 http://www.delorie.com./gnu/。


2.bash 使用很多不同的初始化文件,我们将在下一页中详细讨论这个问题。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 197

续表
选项 功能
-u,--help 打印使用方法信息并退出
-v,--version 打印版本信息并退出

实例 8.2

1 $ chsh –1
/bin/bash
/bin/sh
/bin/ash
/bin/bsh
/bin/tcsh
/bin/csh
/bin/ksh
/bin/zsh

2 $ chsh
Changing shell for ellie.
New shell [/bin/sh] tcsh
chsh: shell must be a full pathname.

说明
1 显示 Linux 系统中所有合法的 Shell。
2 一直要求用户输入新登录 Shell 的完整路径,如果不输入完整的路径——例如,/bin/tcsh,则无效。

8.1.3 环境
一个进程的环境由变量、打开的文件、当前工作目录、函数、资源限制和信号等组成。
这些特征的定义是从一个 Shell 到另一个 Shell 继承来的,用于配置工作环境。用户 Shell 配
置定义在 Shell 的初始化文件中。
初始化文件。bash Shell 有很多初始化文件,这些初始化文件中的设置做为当前 Shell 的
一部分存在,读取哪些初始化文件取决于是否是登录 Shell,是否是交互模式(非登录 Shell) ,
或者非交互模式(Shell 脚本)。
在登录到系统,Shell 提示符出现在屏幕上以前,系统初始化文件 etc/profile 就被读取了,
接着,如果用户主目录下存在、bash_profile 文件,那么这个文件也将被读取,它的作用是
设置用户别名和函数,并建立用户指定的环境变量和启动脚本。
如果用户没有.bash_profile 文件,但是存在.bash_login 文件,那么就会读取后者,如果
后者不存在,但是有 profile 文件,那么就读取.profile 文件。
下面我们总结一下初始化文件的处理顺序(参见图 8.2)3:
if /etc /profile exists, source it,
if ~/.bash_profile exists, source it,

3.-no profile 选项可以使 Shell 不读取任何初始化文件。

PDF created with pdfFactory Pro trial version www.pdffactory.com


198 第8章

if ~/.bashrc exists, source it,


else if ~/.bash_login exists, source it,
else if ~/.profile exists, source it.

/etc/profile 文件/etc/profile 文件是由系统管理程序建立的一个泛系统的初始文件,用户


登录的时候就显示。bash 一启动该文件就执行。该文件对系统上的 Bourne Shell 和 Korn Shell
用户同样通用。它的功用是检查收件箱是否收到新邮件,并从/etc/motd 调出当前日期加以显
示。(当你读完本章之后将会更好理解下面的例子)。

图 8.2 文件初始化的顺序

实例 8.3

(Sample /etc/profile)
# /etc/profile

# System wide environment and startup programs


# Functions and aliases go in /etc/bashrc

1 PATH="$PATH:/usr/X11R6/bin"
2 PS1="[\u@\h\W]\\$ "

3 ulimit –c 1000000
4 if [ 'id –gn' = 'id –un' –a 'id –u' –gt 14 ]; then
5 umask 002
else
umask 022
fi

6 USER='id -un'
7 LOGNAME=$USER
8 MAIL="/var/spool/mail/$USER"

9 HOSTNAME='/bin/hostname'
10 HISTSIZE=1000
11 HISTFILESIZE=1000
12 export PATH PS1 HOSTNAME HISTSIZE HISTFILESIZE USER LOGNAME MAIL

13 for i in /etc/profile.d/*.sh ; do
14 if [ -x $i ]; then
15 . $i
fi
16 done

17 unset i #

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 199

说明
1 Shell 从哪里找到命令,就把该位置赋值给 PATH 变量。
2 规定光标的基本显示形式。光标将以以下的形式在 Shell 窗口出现:用户名(\u)、@符号、主机
名(\W)及$符号。
3 ulimit 命令(Shell 内置命令)限制核心文件的最大容量为 1 000 000 字节。核心文件是破坏了的
程序文件的转存,而且占用相当大的磁盘空间。
4 该行是说,假如用户所属的组名和用户名一致,同时用户 id 号不大于 14……(见解释 5)。
5 设置 umask 为 002。若创建的是目录则得到 775 的权限,而文件得到 664 的权限。否则,umask
被设为 022,此时则给予目录 755 的权限,或给文件 644 的权限。
6 把用户名赋给 USER 变量(id - un) 。
7 LOGNAME 变量被赋予$USER 的值。
8 将到收件箱(储存新邮件)的路径赋给 MAIL 变量。
9 将用户主机名赋给 HOSTNAME 变量。
10 设定 HISTSIZE 变量为 1000。HISTSIZE 是用来控制历史记录的数量的。Shell 退出后,Shell 将
历史记录记录在历史文件里。
11 HISTSIZE 使得历史里的命令数最多为 1000,也就是说,当历史文件中储存的命令数超过 1000
时会删掉后来的文件。(见“历史”)。
12 当子 Shell 和子进程需要时,这些变量就会被导出。
13 对于在 /etc/profile.d 目录里的所有 .sh 文件……(见 14)。
14 检查是否为可执行文件,如果是……(见 15) 。
15 用点命令来执行命令。在 /etc/profile.d 目录里的 lang.sh 和 mc.sh 文件分别设置 Linux 的字体和
字型,同时还创建一个名为 mc 的函数,可以用来激活一个名为 Midnight Commander 的浏览文件
管理程序。在 bash 提示符下输入 mc,来了解文件管理程序是如何工作。
16 done 关键字标志 for 循环的结束。
17 变量 i 被复位,也就是说,将它从 Shell 的名字作用域中删除。i 的值是在 for 循环里获得的,假
如该循环顺利地为所有变量赋值的话。

~/.bash_profile 文件。假如在用户的根目录里存在 ~/ .bash_profile 文件,bash 就在


/etc/profile 后面发送~/ .bash_profile。假如~/ .bash_profile 不存在,bash 就查找另一个用户自
定义文件~/ .bash_login 并发送它。如果~/ .bash_login 也不存在,则发送~/ .profile(如果该文
件存在的话) 。只有~/.bash_profile、~/.bash_login 和~/.profile 三个文件中的一个会被发送。
bash 会一直检查看是否用户有一个.bashrc 文件,一旦发现即发送之。

实例 8.4

(Sample .bash_profile)

# .bash_profile
# The file is sourced by bash only when the user logs on.

# Get the aliases and functions


1 if [ -f ~/.bashrc ]; then
2 . ~/.bashrc
fi

# User specific environment and startup programs

3 PATH=$PATH:$HOME/bin
4 ENV=$HOME/.bashrc # or BASH_ENV=$HOME/.bashrc
5 USERNAME="root"

PDF created with pdfFactory Pro trial version www.pdffactory.com


200 第8章

6 export USERNAME ENV PATH


7 mesg n
8 if [ $TERM = linux ]
then
startx # Start the X Windows system
fi

说明
1 如果在用户的根目录有一个叫作 .bashrc 的文件……
2 登录 Shell 时执行文件 .bashrc。
3 用户的 bin 目录被追加到 PATH 变量,该目录通常是储存 Shell 脚本的。
4 BASH_ENVa (ENV)文件被赋予文件.bashrc 的路径名,而只有当 BASH_ENV (ENV)变量被设定
之后,Shell 才会为交互的 bash Shell 和脚本提供一个初始文件。.bashrc 文件包含用户自定义的各
种别名和函数。
5 变量 USERNAME 赋值为 root。
6 变量被导出,以备子 Shell 和其他进程需要之用。
7 执行带 n 参数的 mesg 命令,禁止其他用户从终端写入。
8 假如变量 TERM 的值为“linux”,startx 将会启动 X 窗口系统(该图形用户界面允许多用户虚拟
控制),而不启动 Linux 的交互对话控制平台。因为~/.bash_profile 只是在用户登录的时候才发
送,所以登录 Shell(login shell)正是启动 X 窗口对话最好的环境。

a. 从 bash 2.0 版本开始使用 BASH_ENV 变量。

BASH_ENV(ENV)变量。在 bash 2.0 版本推出之前,BASH_ ENV 文件仅被称为 ENV


文件(Korn Shell 里也是如此)。BASH_ENV(ENV)变量在~/.bash_profile 里设定。若赋给
它一个文件的名字,则在其后每次启动 bash Shell 或 bash 脚本的时候都会执行该文件。
BASH_ENV(ENV)文件里包含着特定的 bash 变量和别名。文件名虽然是常规 .bashrc,但
已经足够满足你的需求。当优先选项(bash –p 或设定–o 优先选项)打开,或-nort 命令行选
项(bash –norc 或 bash –norc(bash 2.x+))时,BASH_ENV(ENV)文件不执行。
.bashrc 文件。BASH_ENV(ENV)变量被赋予(常规)名字.bashrc。那么,当每次启
动一个新的或者交互的 bash Shell 或者 bash 脚本的时候,该文件就会自动执行,而且其设置
都是 bash Shell 独有的。

实例 8.5

(Sample .bashrc)
#If the .bashrc file exists, it is in the user's home directory.
#It contains aliases (nicknames for commands) and user-defined
#functions.

# .bashrc

# User specific aliases and functions

1 set –o vi
2 set –o noclobber
3 set –o ignoreeof
4 alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 201

5 stty erase ^h
# Source global definitions
6 if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
7 case "$-" in
8 *i*) echo This is an interactive bash shell
;;
9 *) echo This shell is noninteractive
;;
esac
10 history_control=ignoredups

11 function cd { builtin cd $1; echo $PWD; }

说明
1 带-o 选项的 set 命令打开/关闭特定的内置命令(见“设定 –o 选项”)。假如开关为 –o, 再加
上一个减号,选项就打开。即如后面跟的是一个加号,选项就关闭。vi 选项支持交互的命令行编
辑。例如,设定-o vi 打开交互的命令行编辑的开关,而 vi +o 则是关闭。(见表 8.2。)
2 将 noclobber 选项打开能防止用户在重新定向时覆盖文件,比如,sort filex > filex(见“标准 I/O
和重定向”)。
3 通常情况下,你可以通过键入 ctrl-D 来退出 Shell。而设置了 ignoreeof 选项后则必须键入 exit。
4 把 rm-i 的别名设置为 rm,可让 rm 工作在交互模式下,即,在真正删除文件前提示用户是否确
认。把 cp-i 的别名设置为 cp,可让 cp 工作在交互模式下。
5 stty 命令用于将终端的退格键设置为擦除符。^h 代表退格键。
6 若存在名为 /etc/bashrc 的文件,则执行它。
7 如果 Shell 是交互式的,则特殊变量$-应包含字符串“i”,否则,你可能正在运行一个脚本。case
命令用于计算$-。
8 如果$-的值与*i*匹配,即任何含“i”的字符串,则 Shell 显示“This is an interactive shell”。
9 否则,Shell 显示“This is noninteractive”,如果你在提示符下启动一个脚本或一个新的 Shell,
你就能知道你的 Shell 是否运行在交互模式下。只有在这里,你才能真正明白什么是交互式的
Shell,而什么是非交互式的 Shell。
10 history_control 设置用于控制如何在历史文件中存储命令。该行:“Don’t save command in the
history file if they are already here.”即,忽略副本。
11 这是个用户自定义的函数,当用户切换目录时,显示当前工作目录 PWD。该函数被命名为 cd 且
该函数的定义还包括了 cd 命令。在函数的定义中,特殊的内置命令 builtin 被添加在 cd 命令前,
以防止函数陷入无限递归,即,无限的自我调用。

/etc/bashrc 文件。系统范围的函数和别名在/etc/bashrc 文件中进行设置。主提示符通常


在该文件中设置。

实例 8.6

(Sample /etc/bashrc)

# System wide functions and aliases


# Environment stuff goes in /etc/profile

# For some unkown reason bash refuses to inherit


# PS1 in some circumstances that I can't figure out.
# Putting PS1 here ensures that it gets loaded every time.
1 PS1="[\u@\h \W\\$ "

PDF created with pdfFactory Pro trial version www.pdffactory.com


202 第8章

2 alias which="type -path"

说明
1 这里设置系统函数和别名,bash 的主提示符被设置为用户名(\v),@符号,主机名(\h),当前
目录名和一个美元符号(参见表 8.3)。这个提示符将出现在所有交互 Shell 中。
2 命令的别名和昵称通常在用户的.bashrc 文件中设置。别名是预先设置的并在 bash 启动以后可以
使用。可以使用它寻找文件在磁盘上的存储位置,即文件所在的目录,例如 which ls 命令的结果
是/bin/ls。

~/ .profile 文件。.profile 文件是一个用户定义的初始化文件,保存在用户的主目录下,


一但用户运行 sh(Bourne Shell)或 bash 登录系统,而以上的所有的初始化文件都找不到的
时候,就会读取该文件。它允许用户定制自己的 Shell 环境,环境和终端设置都通常保存在
该文件中。窗口应用程序和数据库应用程序也都是从这里开始初始化的。

实例 8.7
(Sample .profile)
# A login initialization file sourced when running as sh or the
# .bash_profile or .bash_login are not found.

1 TERN=xterm
2 HOSTNAME='uname -n'
3 EDITOR=/bin/vi
4 PATH=/bin:/usr/ucb:/usr/bin:/usr/local:/etc:/bin:/usr/bin:.
5 PS1="$HOSTNAME $ > "
6 export TERM HOSTNAME EDITOR PATH PS1
7 stty erase ^h
8 go () { cd $1; PS1='pwd'; PS1='basename $PS1'; }
9 trap '$HOME/.logout' EXIT
10 clear

说明
1 TERM 变量的值表示终端的类型,在这里是 xterm。
2 因为 Uname_n 被反引号引用, Shell 将进行命令替换, 命令的结果(主机名)
将赋值给 HOSTNAME。
3 EOITOR 被赋值为/bin/vi.mail 和 history 程序在定义编辑器时将用到这个变量。
4 Shell 将按照 PATH 变量中的目录搜索 Linux 程序。例如输入 es,Shell 将在 PATH 中的目录里搜
索,直到成功否则报为没找到。
5 主提示符被赋值为 HOST NAME,即主机名和$以及>符号。
6 输出该行所列的变量,这些变量将被 Shell 的子进程所继承。
7 stty 命令设置终端选项。erase 键被设为^h,所以当你按下退格键,光标前的字符被删除。
8 定义函数 go。这个函数的作用是取得一个参数,该参数是一个目录,进入这个目录,并把主提
示符设为这个目录名。basename 命令删除最后一个路径外的所有路径主提示符显示的是当前路
径。
9 trap 是一个信号处理命令。当退出登录时,logout 文件被执行,这是一个用户定义的文件,其中
包括退出登录前要执行的命令,如清空临时文件,登录退出时间。
10 用 clear 命令清空屏幕。

~/.bash-logout 文件。用户退出登录,即退出所登录的 Shell 之时,若存在一个名为


~/.bash_logout 文件,就执行它。该文件通常包含有清除临时文件、截断历史文件和记录退

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 203

出登录时间等作用的文件。
能防止执行启动文件的选项。如果 bash 被带–noprofile 选项的命令激活(如,bash
–noprofile),/etc/profile、~/.bash_login 或~/.profile 的启动文件将不会被执行。
如果激活时带的是–p 选项(譬如,bash -p) ,则 bash 不会读用户的 ~/.profile 文件。
假如它是作为 sh(Bourne shell)被激活,则 bash 就尽可能地模仿 Bourne shell 环境。对
于一个登录 Shell,它仅执行/etc/profile 和~/.profile in that order。-noprofile 选项可能会继续使
用而使该行为不能运行。假如 Shell 是作为 sh 激活的,就不会执行任何其他的启动文件。
.inputre 文件。另一个默认的启动文件,当 bash 启动时,.inputre 文件也跟着启动。假
如该文件存在于用户的根目录下,包含了定义键击行为和设置的命令。这些键击行为和设置
将字符串、宏和函数与热键连接起来。热键所连接文件和热键的定义都放在 Readline 库中,
它是由管理文本的应用程序所使用的数据库。这些热键,当运行命令行编辑的时候,为内置
的 emacs 和 vi 编辑器专用(详见“编辑命令行”的 readline) 。

8.1.4 用内置的 set 和 shopt 命令来设置 bash 的选项


set –o 选项。使用-o 选项开关,set 命令就能选择是否接受选项。而使用各种选项就能
定义自己的 Shell 环境。它们的开关与否通常都在 BASH_ENV(ENV)文件内设定。例如,
se t-o noclobber 也可以写成 set –C。见表 8.2。
表 8.2 内置 set 命令选项表

选项名 开关缩写 功能
allexport -a 打开此开关,即自动标记新的或修改要传出的变量,直到重新
关闭
braceexpand -B 将花括号的展开式设为默认值
emacs 进行命令行编辑,使用 emacs 内置编辑器,是默认设置
errexit -e 假如一个命令返回一个非 0 的退出状态值(运行失误)后退出,
则读入启动文件的时候不做任何设置
histexpand -H 当进行历史替换的时候,使!和!!符号有效。这是默认设置
history 使命令行历史记录开关打开。这是个默认值
ignoreeof 防止退出 Shell 的时候也使得 EOF(ctrl-D)失效。必须输入退
出命令。当设置 Shell 变量时也同理操作。IGNOREEOF=10
keyword -k 将键盘参数置于环境中,并作为一个命令看待
interactive-comments 在交互状态下的 Shell 中,以#开头的命令行是注解
monitor -m 允许工作控制
noclobber -C 防止重定向时文件被覆盖
noexec -n 读入但不执行命令。该选项用于检查脚本语法错误,但在交互
环境下失效
noglob -d 使路径扩展式失效。也就是说,关闭了通配符
notify -b 后台工作完成后通知用户
nounset -u 当扩展一个未设置的变量时,系统报错
onecmd -t 读入并执行完一个命令后退出

PDF created with pdfFactory Pro trial version www.pdffactory.com


204 第8章

续表
选项名 开关缩写 功能
physical -P 如果已经设置,当键入 cd 或 pwd 时就不跟随符号链接,而使
用实际目录
posix 如果默认操作没有匹配 POSIX 标准,则 Shell 行为会发生改变
privileged -p 设置之后,Shell 就不读入.profile 或 ENV 文件,而且 Shell 函数
也不从环境中继承了。对于 setuid 脚本而言,则自动设置
posix 根据 POSIX 1003.2 来改变默认行为
verbose -v 打开 verbose 模式以进行调试
vi 使用 vi 内置编辑器来进行命令行编辑
xtrace -x 打开“输出显示”模式以进行调试

格式

set –o option Turns on the option.


set +o option Turns off the option.
set –[a-z] Abbreviation for an option; the minus turns it on.
set +[a-z] Abbreviation for an option; the plus turns it off.

实例 8.8

1 set –o allexport
2 set +o allexport
3 set -a
4 set +a

说明
1 设置 allexport 选项。它导至所有的变量都自动输出给子 Shell。
2 设置 allexport 选项,使所有的变量都只在当前 Shell 中有效。
3 同 1,但不是每一个选项都有缩写,参见表 8.2。
4 同 2。

实例 8.9
1 $ set -o
braceexpand on
errexit off
hashall on
histexpand on
keyword off
monitor on
noclobber off
noexec off
noglob off
notify off
nounset off
onecmd off
physical off
privileged off
verbose off

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 205

xtrace off
history on
ignoreeof off
interactive-comments on
posix off
emacs off
vi on
2 $ set –o noclobber
3 $ date > outfile
4 $ ls > outfile
bash: outfile: Cannot clobber existing file.
5 $ set +o noclobber
6 $ ls > outfile
7 $ set -C

说明
1 带-o 的 set 命令列出当前所有选项,包括已经设置的和未设置的。
2 用带-o 的 set 命令设置 noclobber。它能防止重新定向的时候覆盖文件。没有 noclobber 的时候,>
符号右边若存在文件的话,则该文件被裁截。若不存在则创建一个文件。
3 Linux 的 date 命令的结果将被重新定向到一个名为 outfile 文件中去。
4 此时 outfile 是存在的。但为了重新定向 ls 的结果到 outfile 里去,Shell 实际上不需要这个文件的
存在。如果 noclobber 没有设置的话,该文件就会被删除。
5 带+o 选项的 set 命令关闭 noclobber 开关。
7 由于 noclobber 开关已经关闭,所以此时就可以覆盖/删除 outfile 文件。
8 使用-C 选项是 set 命令控制 noclobber 开关的另一个途径。使用+C 则关闭。

shopt 内置命令(2.x+版本)。shopt(Shell 的选项之一)内置命令是用于新版本的 bash


中的命令,其作用和 set 命令基本相同。shopt 命令很多方面都是和 set 命令一样的,但在对
Shell 的设置中增加了很多扩充。见表 8.3 关于 shopt 命令选项的详细表述。在以下的例子中,
带-p 选项的 shopt 输出所有可用的选项设置命令。-u 开关表明选项没有被设置,而-s 表明目
前已经被设置的选项。

实例 8.10

1 $ shopt -p
shopt –u cdable_vars
shopt –u cdspell
shopt –u checkhash
shopt –u checkwinsise
shopt –s cmdhist
shopt –u dotglob
shopt –u execfail
shopt –s expand_aliases
shopt –u extglob
shopt –u histreedit
shopt –u histappend
shopt –u histverify
shopt –s hostcomplete
shopt –u huponexit
shopt –s interactive_comments
shopt –u lithist

PDF created with pdfFactory Pro trial version www.pdffactory.com


206 第8章

shopt –u mailwarn
shopt –u nocaseglob
shopt –u nullglob
shopt –s promptvars
shopt –u restricted_shell
shopt –u shift_verbose
shopt –s sourcepath
2 $ shopt –s cdspell
3 $ shopt –p cdspell
shopt –s cdspell
4 $ cd /hame
/home
5 $ pwd
/home
6 $ cd /ur/lcal/ban
/usr/local/man
7 $ shopt –u cdspell
8 $ shopt –p cdspell
shopt –u cdspell

说明
1 带-p 选项的 shopt 命令输出所有可设置的 Shell 选项和它们的当前值:已经设置
(-s)
或未设置
(-u)

2 带-s 选项的 shopt 命令打开(或开启)一个选项。cdspell 选项用于纠正目录名的拼写错误,这些
目录名是作为 cd 命令的参数的。它能纠正小的输入错误,加减一两个字母,或更正单词错误。
3 通过-p 选项和选项名,shopt 表明是否设置该选项。在此,选项被设置(-s)。
4 在这个例子里,用户用 cd 回到根目录,但拼错了“home”。于是,Shell 自动修复这个错误。也
就是说,将“hame”纠正为“home”。最终,目录转到“/home”。
5 pwd 命令的结果是显示当前工作目录。尽管用户拼错了,但目录的确换了。
6 这回,目录名缺了字母,而且最后的“ban”也输错了。但 Shell 很好地将缺漏的字母给填补上了,
并且还将 ban 更正为正确的“man”。这是因为输错的是第一个字母。而 Shell 搜索路径的时候
就用最后的“a”和“n”做关键字,然后就找到了“man”。
7 使用-ua 开关,shopt 解除/关闭选项。
8 通过-p 选项和选项名,shopt 决定是否设置选项。cdspell 选项被关闭。

a. 该开关是交互的。它们前面带有“—”破折号,是命令的参数。

8.1.5 提示符
在交互模式下,Shell 的提示符提示用户输入。当用户看见提示符的时候,就知道可以
输入命令了。bash 有四种提示符:最基本的是“$”提示符;第二种是“>” ;第三、第四种
分别是 PS3 和 PS4,将会在稍后讨论。当 Shell 在交互模式下运行的时候,提示符就会出现
在屏幕上。当然你也可以替换提示符。
用变量 PS1 对一个包含有基本提示符($)的字符串进行设置。其值和$符号,在登录时
显示,并等待用户的输入。当然了,一般就是 Linux 命令。变量 PS2 是第二种提示符,默认
的是“>”符号。假如你输入的是部分,或称为不完全的命令,并敲了回车, “>”符号就会
出现。你也可以用其他的符号来变换第一或第二种提示符。
基本提示符。即$符号,是默认的基本提示符。你也可以替换为其他的提示符。一般而
言,提示符可在/etc/bashrc 或在用户的初始文件.bash_profile(在 Bourne Shell 中)里转换/
定义。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 207

实例 8.11

1 $ PS1="$(uname –n) > "


2 chargers >

说明
1 默认提示符为美元符号(bash $)。PS1 提示符被设置为机器名 a(uname – n)加上“>”符号。
2 显示新的提示符。
a. 由于 uname –n 命令被括在以美元符号为前导的括号中,该命令因此得以执行。还有另一个方法是把命令括在反引号中,
见“命令替换”。

用特殊转义序列(escape sequence)设置提示符。可以通过在提示符字符串中插入特
殊的用反斜杠转义的字符序列来定制提示符。表 8.3 列出了特殊序列。
表 8.3 提示符字符串设置

反斜杠序列 功能
\t 以 HH:MM:SS 格式显示的当前时间
\d 以“Weekday Month Date”格式显示的日期(如“Tue May 26”)
\n 换行符
\s Shell 的名称,$0(接着的部分是终结斜线)的基本名(basename)
\w 当前工作目录
\W 当前工作目录的基本名
\n 当前用户的用户名
\h 主机名
\# 本命令的命令个数
\! 本命令的历史序号
\$ 如果有效 UID 为 0,则为#,否则为$
\nnn 八进制数字对应的 ASCII 字符
\\ 一个反斜杠
\[ 非打印字符串序列的起始符,这些非打印字符串可用于在提示符中嵌入终端控制
序列
\] 非打印字符串序列的结束符
bash 2.x+版本中的新序列
\a ASCII 响铃字符
\@ 以 12 小时 AM/PM 格式显示当前时间
\H 主机名
\T 以 12 小时 HH:MM:SS 格式显示当前时间
\e ASCII 转义字符(033)
\v bash 的版本号,如 2.03
\V bash 的版本号,如 2.03.0

PDF created with pdfFactory Pro trial version www.pdffactory.com


208 第8章

实例 8.12

1 $ PS1="[\u@\h \W]\\$ "


[ellie@homebound ellie]$

2 $ PS1="\W:\d> "
ellie:Tue May 18>

说明
1 用反斜杠转义序列来设置 bash 主提示符。\u 代表用户的登录名,\h 为主机名,\W 为当前目录的
基名。然后是两个反斜杠,第一个斜杠对第二个进行转义,结果是\$。美元符号免于对 Shell 进
行解释,因此可以以原形显示。
2 主提示符被赋值为\W,这个转义序列表示当前工作目录的基名和\d,\d 表示当前日期。

次提示符。PS2 变量被赋值为称为次提示符字符串。其值在标准错误中显示,默认情况
下为监视器。当没有完成输入或期望更多的输入时就会出现这个提示符。默认次提示符为
“>”

实例 8.13

1 $ echo "Hello
2 > there"
3 Hello
there
4 $

5 $ PS2="----> "
6 $ echo 'Hi
7 ------>
------>
------> there'
Hi

There
$

8 $ PS2="\s:PS2 > "


$ echo 'Hello
bash:PS2 > what are
bash:PS2 > you
bash:PS2 > trying to do?
bash:PS2 > '
Hello
What are
You
Trying to do?
$

说明
1 在"Hello 字符串后必须匹配双引号。
2 键入换行符后即出现次提示符。直到输入闭合的双引号才会显示次提示符。
3 显示 echo 命令的输出结果。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 209

4 显示主提示符。
5 重置次提示符。
6 在'Hi 后必须匹配单引号。
7 键入换行符后即出现次提示符。直到输入闭合的单引号才会显示次提示符。
8 将 PS2 提示符设置为 shell 的名字(\s)跟着由冒号、PS2、>和一个由空格组成的字符串。

搜索路径。bash 用 PATH 变量来定位在命令行键入的命令。路径是用冒号分隔的目录列


表,用于 Shell 对命令进行搜索。默认路径依赖于各自的系统实现,并由安装 bash 的管理员
进行设置。搜索在路径中自左而右进行。路径末尾的点符号表示当前的工作目录,如果在所
有路径目录列表中都没有发现要找的命令。 shell 将向标准错误输出信息
“filename not found”。
如果使用 bash Shell,通常在.bash_profile 文件中设置路径,如果用的是 Bourne Shell,则
在.profile 文件中设置。
如果在路径中没有点符号,那么在当前工作目录下执行命令或脚本时,就必须在其前面
加上./,如./progname_name。只有这样 Shell 才能找到该程序。

实例 8.14

(Printing the PATH)


1 $ echo $PATH
/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin:.
(Setting the PATH)
2
$ PATH=$HOME:/usr/ucb:/usr:/usr/bin:/usr/local/bin:
3 $ export PATH
4 $ runit
bash: runit: command not found
5 $ ./runit
< program starts running here >

说明
1 PATH 变量的值。路径由冒号分隔的元素列表组成,搜索时从左到右。路径末尾的点符号代表当
前工作路径。
2 PATH 变量的值是一组由冒号分隔的目录,需要注意的是,变量的末尾没有点号,这大概是出于
安全的考虑。
3 通过导出路径,子进程也能对路径进行访问。无需再单独导出,只要在同一行写为:“export
PATH=$HOME=/usr/acb:/bin:.”即可。
4 由于搜索路径中没有点符号,所以在当前工作路径执行程序 runit 时,Shell 无法找到该命令。
5 如果程序在当前工作目录下,则可以在程序名前加点符号和斜杠(./) ,以使 Shell 能够找到并执
行之。

hash 命令。hash 命令控制内部哈希表,该表用于提高 Shell 搜索命令的效率。Shell 不


会为你每次键入的命令都对路径搜索一次。而是在第一次你键入一个命令后,用搜索路径来
查找该命令,并把它存储在 Shell 所属内存的一个表中。当下一次使用相同命令时,Shell 就
会从哈希表中查找它。这样访问一个命令要比全路径搜索快得多。如果你知道自己将要经常
使用某个命令,也可在哈希表中添加命令,当然也可以从哈希表中移除某个命令。hash 命令
的输出结果显示了 Shell 使用该表找到某个命令的次数以及该命令的全路径名。用带-r 选项
的 hash 命令可清空哈希表。--参数则禁止对剩余的参数进行检查。哈希算法由 bash 自动实

PDF created with pdfFactory Pro trial version www.pdffactory.com


210 第8章

现。尽管可以关闭它,但是如果没有什么特别的原因,最好别那样做。

实例 8.15

(Printing the PATH)

(Command line)
1 hash
hits command
1 /usr/bin/mesg
4 /usr/bin/man
2 /bin/ls

2 hash -r
3 hash
No commands in hash table

4 hash find
hits command
0 /usr/bin/find

说明
1 hash 命令显示在本登录会话中已执行命令的全路径名。(内置命令未被列出)命中次数为用哈希
表查到该命令的的次数。
2 hash 命令的-r 选项用于清空哈希表。
3 在使用完上一条带-r 选项的命令后,hash 命令显示目前表中没有命令。
4 如果你知道自己经常使用某条命令,可以通过给 hash 命令一个参数来向哈希表添加该命令。在
这里,添加了 find 命令。表中显示为 0 命中次数,是因为该命令还没有使用过。

source 或点命令。source 命令(从 C shell 而来)是 bash Shell 的内置命令。点命令,


就是一个点符号,(从 Bourne Shell 而来)是 source 的另一名称。这两个命令都以一个脚本
为参数,该脚本将作为当前 Shell 的环境解释执行,即不会启动一个新的子进程。所有在
脚本中设置的变量将成为当前 Shell 环境的一部分。同样的,当前脚本中设置的变量也将
成为脚本的环境,source (或点)命令通常用于重新执行刚修改的初始化文件,如.bash_profile
和.profile 等等。例如,如果在登录后对.bash_profile 中的 EDITOR 和 TERM 变量进行了修
改, 则 可以 用 sourcc 命 令重 新执 行.bash_profile 中的 命 令而 不 用注 销并 重 新登 录 。
像.bash_profile 或其他类似的 Shell 脚本这样,文件无需可执行权限即可用 source 或点命令
执行。

实例 8.16

$ source .bash_profile
$ . .bash_profile

说明
source 或点命令在当前 Shell 上下文中执行初始化文件.bash_profile。于是在 Shell 中定义了局部
和全局变量。用点命令可以在文件被修改后免于注销并再次登录 a。
a. 如果.bash_profile 直接作为一个脚本执行,就会启动一个子 Shell。这样就会在子 Shell 中设置变量,当退出子 Shell 时,
父 Shell 未受到这些变量设置的影响。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 211

8.1.6 命令行
登录后,bash Shell 默认情况下即显示主提示符,美元符号($) 。而 Shell 就是命令解释
器。如果 Shell 在交互模式下运行,它会从终端读入命令将其断为单词。命令行由以空格(多
个空格或/和制表符)分隔的一个或多个单词(或符号)组成,并以换行符(由回车键产生)
为结束标志。命令的首单词是命令,后续单词为命令参数。命令可能是像 ls 或 date 这样的
Linux/UNIX 可执行程序,用户自定义函数,像 cd 和 pwd 这样的内置命令,和一个 Shell 脚
本。命令可能包含称为元字符的转义字符串,在分析命令行时它们将被解释。如果命令行过
长,可用反斜杠加上换行符来续行,随即显示次提示符,直到命令行结束。
处理命令的顺序。命令行的首单词是执行命令。命令可能是一个关键字、函数、特定的
内置命令或是工具、可执行程序或是一个 Shell 脚本等。根据命令类型参照下列顺序执行命
令:
(1)别名
(2)关键字(如 if、function、while 及 until)
(3)函数
(4)内置命令
(5)可执行程序或脚本
特殊内置命令和函数在 Shell 中定义,因此它们在当前 Shell 的上下文中执行,这也使得
它们在执行的时候速度更快。要执行像 ls 和 date 这样存储在磁盘上的脚本和可执行程序以
及 Shell,首先得通过搜索 PATH 环境变量指定的路径在目录结构上对它们进行定位,然后
再调用一个用于执行脚本的新 Shell。要知道你所用命令的类型——如内置命令、别名、函
数或可执行程序等等——可使用内置命令 type。 (见实例 8.17)。

实例 8.17
$ type pwd
pwd is a shell builtin
$ type test
test is a shell builtin
$ type clear
clear is /usr/bin/clear
$ type m
m is aliased to 'more'
$ type bc
bc is /usr/bin/bc
$ type if
if is a shell keyword
$ type –path cal
/usr/bin/cal
$ type which
which is aliased to 'type -path'
$ type greetings
greetings is a function
greetings ()
{
echo "Welcome to my world!";
}

PDF created with pdfFactory Pro trial version www.pdffactory.com


212 第8章

内置命令和 help 命令。内置命令就是 Shell 内部源代码部分的命令。它们是内置的,嵌


在 Shell 中,即可执行的,而不像 date、cat 和 finger 命令那种存储于磁盘上的经编译的二进
制程序。由于没有调用磁盘操作,执行内置命令的开销会更小。内置命令先于磁盘上的程序
而执行。bash 添加了一个新的在线帮助系统,这样你就可以查看所有内置命令及其描述了。
help 本身也是一个内置命令。见表 8.32 中的内置命令完整列表。

实例 8.18

1 $ help help
help: help [pattern ...]
Display helpful information about built-in commands. if PATTERN
is specified, gives detailed help on all commmands matching
PATTERN, otherwise a list of the built-ins is printed.

2 $ help pw
pwd: pwd
Print the current working directory.

改变命令行的处理顺序。bash 提供了三个内置命令来改变命令行处理顺序:command、
builtin 和 enable。
command 内置命令可在参照命令处理顺序时,忽略别名和函数,只处理内置命令和在
搜索路径中查找到的可执行文件。
builtin 命令只查找内置命令,而忽略函数和在路径中找到的可执行程序。
enable 内置命令可以打开/关闭 built-in 开关。默认情况下,允许 biult-ins。禁止 built-in
使得即使在与内置命令同名的情况下,也执行在磁盘上找到的、且不指定全路径名的可执行
命令(通常的处理是,bash 先于磁盘可执行命令搜索内置命令) 。用-n 开关可以关闭 biult-in。
Shell 程序员新手常犯的错误是将脚本命名为 test。由于 test 是一个内置命令,Shell 将会试图
执行它而不是执行用户的脚本(内置命令通常先于任何可执行程序执行) 。通过键入:enable
–n test 可以禁止 test built-in,这样就能优先执行用户的脚本了。
没有参数, 内置命令 enable 将列出一个所有内置命令的列表。下列每个内置命令在 “Shell
内置命令”中有详细描述。

实例 8.19

1 $ enable
enable .
enable :
enable [
enable alias
enable bg
enable bind
enable break
enable builtin
enable cd
enable command
enable continue
enable declare
enable dirs

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 213

......
enable read
enable readonly
enable return
enable set
enable shift
enable shopt
......
enable type
enable typeset
enable ulimit
enable umask
enable unalias
enable unset
enable wait

2 enable –n test

3 function cd { builtin cd; echo $PWD; }

说明
1 没有参数的内置命令 enable 显示所有 bash Shell 内置命令的列表。本例只列出了列表的一部分。
2 用-n 开关,test 内置命令将禁止。现在你就可以执行名为“test”的脚本了,而不必担心被代以执
行内置命令 test。将一个脚本命名为操作系统同名命令并不是一个好习惯,因为如果你试图在另
一个 Shell 运行相同的脚本时,可能对 built-in 的禁止并不存在。
3 函数名为 cd。built 命令使得函数定义中执行 cd 命令而不是函数 cd,否则将导致无穷递归。

退出状态。当一个命令或程序退出后,都会向其父进程返回退出状态。退出状态是介于
0~255 的数字。一般约定,当程序退出且其返回状态为 0,即认为该程序成功执行。当退出
状态为非零时,则意味着命令失败。如果 Shell 没有找到命令,返回的退出状态为 127。如
果是致命信号导致了命令的退出,其退出状态为 128 加上导致其退出的信号值。
用上一条执行命令的返回值设置 Shell 状态变量?程序的成功或失败由编写该程序的程
序员来决定。

实例 8.20

1 $ grep ellie /etc/passwd


ellie:MrHJEFd2YpkJY:501:501::/home/ellie:/bin/bash
2 $ echo $?
0

3 $ grep nicky /ect/passed


4 $ echo $?
1
5 $ grep ellie /junk
grep: /junk: No such file or directory
6 $ echo $?
2
7 $ grip ellie /etc/passwd
bash: grip: command not found
8 $ echo $?
127

PDF created with pdfFactory Pro trial version www.pdffactory.com


214 第8章

9 $ find / -name core ^C User presses Control-C


10 $ echo $?
130

说明
1 grep 程序在/etc/passwd 文件中成功搜索模式“ellie”,并显示/etc/passwd 中的匹配行。
2 ?变量被设置为 grep 命令的返回值。0 表示成功状态。
3 grep 命令在/etc/passwd 文件中没有找到用户 nicky。
4 grep 命令无法找到该模式,?变量返回非零值。返回值 1 表示失败。
5 由于无法打开/junk 文件,grep 出错。grep 的错误信息被送到标准错误,即屏幕。
6 如果 grep 无法找到文件,则返回码为 2。
7 本 Shell 无法找到 grip 命令。
8 由于没有找到该命令,返回码为 127。
9 通过按下 Control-C 发送 SIGINT 信号来终止 find 命令。Ctrl-C 的信号数为 2。
10 被中止的进程的返回码为 128 加上信号数,如 128+2。

命令行中的多条命令。一个命令行可以含多个命令。每个命令之间由分号分隔,命令行
以换行符为结束标志。退出状态为命令串中的最后一个命令的返回码。

实例 8.21
$ ls; pwd; date

说明
从左到右逐个执行命令直至碰到换行符。

实例 8.22
$ ( ls; pwd; data ) > outputfile

说明
每个命令的输出都被送到名为 outputfile 的文件中,括号中的空格是必须的。

命令的条件执行。条件执行由两个特殊的元字符&&和||将两个命令字符串分开的形式实
现的。元字符右边的命令是否执行取决于左边命令的退出条件值。

实例 8.23
$ cc prgm1.c –o prgm1 && prgm1

说明
如果第一个命令成功(返回值为 0),&&后的命令就会执行,即如果 cc 程序能成功编译 prgm1.c,
则执行编译结果——可执行程序 progm1。

实例 8.24
$ cc prog.c >& err || mail bob < err

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 215

说明
如果第一个命令失败了(返回值为非零),就执行||后的命令,即如果 cc 程序无法编译 prog.c,
则将错误信息发送到文件 err 中,并将 err 文件邮给用户 bob。

在后台执行命令。通常情况下,你所执行的命令是在前台运行的,直到命令运行结束提
示符才会出现。有时候等待命令执行结束并不是很方便。如果你在命令行后面加上一个&符
号,Shell 就会立即返回到提示符下,并在后台并行地执行该命令,而你无需等待就可以启
动下一个命令。后台作业在执行时的输出结果将被送到屏幕。因此,如果你要在后台运行命
令,命令的输出结果应该被重新定向到一个文件和指向其他设备的管道,如打印机,这样输
出结果就不会影响你手中的工作。
!变量包含提交给后台的最后一项作业的 PID(作业的进程标识数) ,参见有关后台处
理的“作业控制”部分。

实例 8.25

1 $ man sh | lp&
2 [1] 1557
3 $ kill –9 $!

说明
1 man 命令(Linux 命令的帮助手册)的输出结果通过管道被送到打印机。命令行后的&将作业送
至后台。
2 屏幕显示了两个数:方括号中的数表示这是送到后台执行的第一个作业;第二个数则是 PID,或
是该作业的进程标识数。
3 Shell 提示符马上出现。如果在后台运行程序,Shell 就会在前台等待输出另一个命令。!变量为最
近送入后台执行的作业的 PID。如果你即时取得该值,就可以在它进入打印队列前放弃该作业。

8.1.17 作业控制
作业控制是 bash Shell 提供的一项强大功能,它允许你选择在前台还是后台运行程序,
即作业。运行中的程序称为进程或作业,每一个进程都有一个进程 id 号码,称为 PID。通常
情况下,在命令行中键入的命令会在前台持续运行,直至通过键入 Ctrl-C 或 Ctrl-\发送信号
来终止它。有了作业控制,你就可以将作业送到后台中持续执行。你可以通过键入 Ctrl-Z 来
停止一个作业(实际上是将作业送到后台,并挂起) 。也可以让停止的进程在后台运行。可
将在后台运行的程序送回前台运行,甚至可以中止正在前台和后台运行的程序。作业命令的
列表见表 8.4。
默认情况下,作业控制已被设置(UNIX 的早些版本不支持这项功能) 。如果该项功能
被禁止,则可以通过下列任一条命令来重置它。

格式
set –m (set job control in the .bashrc file)
set –o monitor (set job control in the .bashrc file)
bash –m –i (set job control when invoking interactive bash)

PDF created with pdfFactory Pro trial version www.pdffactory.com


216 第8章

实例 8.26

1 $ vi
[1]+ Stopped vi

2 $ sleep 25&
[2] 4538

3 $ jobs
[2]+ Running sleep 25&
[1]- Stopped vi
4 $ jobs -1
[2]+ 4538 Running sleep 25&
[1]- 4537 Stopped vi

5 $ jobs %%
[2]+ 4538 Running sleep 25&

6 $ fg %1

7 $ jobs –x echo %1
4537

8 $ kill %1 or kill 4537

[1]+ Stopped vi
Vim: Caught deadly signal TERM
Vim: Finished.
[1]+ Exit 1 vi

说明
1 调用 vi 编辑器后,你可以用^Z(Control-Z)将 vi 会话挂起。编辑器将在后台被挂起,在显示 stopped
信息后,Shell 提示符将立即显示出来。
2 命令后的&让参数为 25 的 sleep 命令在后台中执行,符号[2]的意思是这是第二个在后台中运行的
作业,该作业的 PID 为 4538。
3 job 命令显示了在当前在后台中运行的作业。
4 带-l 选项的 jobs 命令显示在后台运行的进程(作业)及其 PID。
5 %%参数让 jobs 命令显示在作业表中最近执行的命令。
6 fg 命令后紧跟着一个百分号和一个数字可以把该数字指定的作业送到前台执行。如果没有给出数
字,则将最近执行的后台作业送到前台执行。
7 -x 选项用于只显示作业的 PID。%1 指的是本例中的第 12 页中停止的 vi 会话。
8 kill 命令将 TERM 信号发送给进程,并中止该进程,因此 vi 程序被中止。可以指定 kill 命令的作
业号或 PID。

表 8.4 作业控制

命令 含义
jobs 列出所有正在运行的作业
^Z(Ctrl-Z) 停止(挂起)作业;并在屏幕上显示提示符
bg 启动后台中停止的作业
fg 将后台程序送到前台执行

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 217

续表
命令 含义
stop 挂起一个后台作业
stty tostop 如果后台作业将结果输出到终端,将其挂起
kill 向指定的作业发送 kill 信号
wait [n] 等待指定的作业及其返回值,n 为 PID 或作业号

jobs 命令的参数 含义
%n n 为作业号
%string 以 string 开始的作业名
%?string 含 string 的作业名
%% 当前作业
%+ 当前作业
%- 上一个作业,即当前作业的前一个作业
-r 列出所有正在运行的作业
-s 列出所有挂起的作业

新 jobs 选项。在 bash 2.x 版本中的 jobs 命令添加了两个新的选项,它们是-r 和-s 选项。


-r 选项是列出所有正在运行的作业,而-s 选项则列出所有挂起的作业。
内置命令 disown。内置命令 disown(bash 2.x 版本)将指定的作业从作业表中移除。当
作业被移除后,shell 就不会将其识别为可用的作业,因此它也不能通过其进程 id 号来被引
用。

8.2 命令行快捷键

8.2.1 命令和文件名自动填充
为了减少键入,bash 实现了命令和文件名的自动填充,该机制可以让你输入命令和文件
名的部分,然后按 Tab 键即可由系统自动完成余下的部分。
如果你键入了命令的第一个字符,然后键入 Tab 键,bash 将试图填完该命令并执行之。
如果由于文件名和命令都不存在,bash 无法完成自动填充,那么终端将继续保持原状态,光
标也将停在命令的末尾。如果有一个以上的命令符合开头键入字符,然后你接着又键入了
Tab 键,那么所有以这些字符开头的命令都会被列出来。
如果还有多个以相同字符开头的文件,bash 将填入最短的匹配文件名,直到遇上不同的
字符才将文件名完全填入,最后刷新光标。

实例 8.27

1 $ ls
file1 file2 foo foobarckle fumble

PDF created with pdfFactory Pro trial version www.pdffactory.com


218 第8章

2 $ ls fu[tab] expands to filename to fumble

3 $ ls fx[tab] terminal beeps, nothing happens

4 $ ls fi[tab] expands to file_ (_ is a cursor)

5 $ ls if[tab][tab] lists all possibilities


file1 file2

6 $ ls foob[tab] expands to foobarckle

7 $ da[tab] completes the date command


date
Tue Feb 29 18:53:40 PST 2000

8 $ ca[tab][tab] lists all commands starting with ca


cal captoinfo case cat

说明
1 列出所有当前目录下的文件。
2 键入 fu 后,接着按下 Tab 键将会完成文件名的自动拼写——fumble,然后显示该文件。
3 由于没有以 fx 开头的文件,终端将响铃且光标也保持原样(如果响铃功能已被禁止,则终端不
会发出声音)。
4 这里有数个以 fi 开头的文件,文件名将在没有字符相同的时候完成自动填充。如果再按下 Tab
键,将列出所有以 fi 开头的文件。
5 通过按下两次 Tab 键可以列出所有以 file 开头的文件。
6 如果按下 Tab 键,文件名将被扩展为 foobarckle。
7 如果在 da 后按下 Tab 键,由于以 da 开头的命令只有 date 命令,该命令被显示并执行。
8 如果在 ca 后按下 Tab 键,由于以 ca 开头的命令不止一个,没有命令会被执行。按下两次 Tab 键
将列出所有以 ca 开头的命令。

8.2.2 历史(命令)
历史机制在历史列表中记录了你在命令行中键入的大量命令。在登录会话中,你所键入
的命令都将被存储在 Shell 命令列表内存中,当你退出时,这些命令将被追加到历史文件中。
你可以从历史列表中调入命令并重新执行之,而无需重新键入。内置命令 history 用于显示
历史列表。默认的历史列表文件为.bash_history,在 home 目录下。
当 bash 开始访问历史文件时, HISTSIZE 变量指定了可以从历史文件中调入多少条命令。
默认值为 500。HISTFILE 变量执行了历史命令文件的名字(默认值为~/.bash_history),这是
命令存储的文件。
历史命令文件在一次次的登录会话中增长。HISTFILESIZE 变量用于控制历史文件所能存
储的最大行数。如果设置了该变量,在超过该数量行数时,将截断历史文件。默认大小为 500。
fc –l 命令可用于显示和编辑历史列表。
表 8.5 历史变量

变量 含义
FCEDIT 使用 fc 命令的 Linux 编辑器的路径名

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 219

续表
变量 含义
HISTCMD 当前命令的历史号,即在历史列表中的索引号。如果没有设置 HISTCMD,即
使跟着重置,其功能也将失效
HISTCONTROL 如果赋值为 ignorespace,则以空格开头的行将不会进入历史列表。如果赋值为
ignoredups,与上一条命令相同的命令行也不会进入到历史列表中。如果赋值
为 ignoreboth 则综合了这两个选项。如果不设置或没有设置为上述值,所有经
分析器读入的命令行都会存储到历史列表中
HISTFILE 指定用于存储历史列表的文件。默认值为~./bash_hostory。如果不设置的话,
在交互式 Shell 退出的时候,将不会存储命令历史
HISTFILESIZE 历史文件可存储的最大历史行数。如果该变量被赋值,那么在需要的时候,历
史文件将被截断以限制在指定的行数内。默认值为 500
HISTIG NORE 这是一个用冒号进行分隔的模式列表,该模式列表用于指定什么样的命令行可
以被存储到历史列表中。每个模式都指定了以该模式开头的命令行和匹配字符
的普通 Shell 模式。可在模式中使用&,
以使 history 命令忽略重复命令;
如 ty??:&
将匹配那些以“ty”开头接着两个字符和重复的命令行。这些匹配的命令都不会
被存入历史列表中
HISTSIZE 在命令历史中可以存储的命令条数。默认值为 500

8.2.3 从历史文件中访问命令
箭头键。要访问历史文件中的命令,你可以用键盘上的箭头键在历史文件中上下左右地
进行移动,见表 8.6。你也可以用标准的删除、修改及退格等键来对历史文件中的命令行进
行编辑。在编辑命令行的时候,按回车键即可重新执行该命令。
表 8.6 箭头键

箭头 含义
↑ 上移历史列表
↓ 下移历史列表
→ 在历史命令中向右移动光标
← 在历史命令中向左移动光标

内置命令 history。内置命令 history 用于显示已键入的命令历史,命令前有 event number


(事件号)。

实例 8.28
1 $ history
982 ls
983 for i in 1 2 3
984 do
985 echo $i
986 done
987 echo $i
988 man xterm

PDF created with pdfFactory Pro trial version www.pdffactory.com


220 第8章

989 adfasdfasdfadfasdfasdfadfasdfasdf
990 id –gn
991 id –un
992 id –u
993 man id
994 more /etc/passwd
995 man ulimit
996 man bash
997 man baswh
998 man bash
999 history
1000 history

说明
1 内置命令 hisotry 显示了历史列表中带序号的命令,以*号开头的命令行是被修改过的命令行。

fc 命令。fc 命令,也称为 fix 命令,有两种用法:1)从历史列表中选择命令;2)在 vi、


emacs 或系统的其他编辑器中编辑命令。
第一种用法,带-l 选项的 fc 命令可从历史列表中选择指定的命令行或某个范围的命令
行。如果打开-l 开关,结果将输出到屏幕。如 fc –l,默认情况下将显示历史列表中的最后 16
行,fc –l 10 则显示在整个历史列表中序号为 10 的命令行,fc –l –3 选择最后 3 行。-n 开关
可用于关闭历史列表中序号功能。如果打开了该开关,你就可以选择某个范围的命令行并把
它们重新定向到文件中,该文件可作为一个脚本被执行。-r 开关用于反转命令的顺序。
第二种用法在后面的“命令行编辑”中进行描述。
表 8.7 fc 命令

fc 参数 含义
-e editor 将历史列表调入编辑器
-l n-m 列出从 n~m 范围的命令
-n 关闭历史列表的序号功能
-r 反转命令的顺序
-s string 访问以 string 开头的命令

实例 8.29

1 $ fc -1
4 ls
5 history
6 exit
7 history
8 ls
9 pwd
10 clear
11 cal 2000
12 history
13 vi file
14 history
15 ls -1

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 221

16 date
17 more file
18 echo a b c d
19 cd
20 history
2 $ fc –1 -3
19 cd
20 history
21 fc -1
3 $ fc -ln
exit
history
ls
pwd
clear
cal 2000
history
vi file
history
ls –1
date
more file
echo a b c d
cd
history
fc –1
fc –1 –3

4 $ fc –ln –3 > saved

5 $ more saved
fc –1
fc –1 –3
fc –ln
6 $ fc –1 15
15 ls -1
16 date
17 more file
18 echo a b c d
19 cd
20 history
21 fc -1
22 fc –1 -3
23 fc -ln
24 fc –ln –3 > saved
25 more saved
26 history
7 $ fc –1 15 20
15 ls -1
16 date
17 more file
18 echo a b c d
19 cd
20 history

说明
1 fc-l 从历史列表中选择最后 16 行。
2 fc -l –3 从历史列表中选择最后 3 行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


222 第8章

3 带-ln 选项的 fc 命令列出没有序号的历史命令。


4 不带序号的最后三个命令被重新定向到 saved 文件中。
5 列出 saved 文件的内容。
6 列出从 15 号开始的命令。
7 显示从号数 15~20 的命令。

如果 fc 带有-s 选项,可用一个模式字符串来重新执行先前的命令。如 fc –s rm 将重新执


行匹配 rm 模式的最近执行的命令。你可以通过创建一个名为 r 的 bash 别名 , 如 alias r='fc –s'
来模拟 Korn Shell 的 redo 命令,这样你就可以在命令行下键入 r vi 来执行含该模式最近执行
的命令。在这种情况下,vi 编辑器就会像上次一样带有参数启动。

实例 8.30

1 $ history
1 ls
2 pwd
3 clear
4 cal 2000
5 history
6 ls -1
7 date
8 more file
9 echo a b c d

2 $ fc –s da
data
Thu Ju1 15 12:33:25 PST 1999

3 $ alias r="fc –s"


4 $ date +%T
18:12:32

5 $ r d
date +%T
18:13:19

说明
1 内置命令 history 显示历史列表。
2 带-s 选项的 fc 命令查找以 da 字符串开头的最近执行的命令。在历史列表中找到 date 命令并执行
之。
3 名为 r 别名,即用户自定义的昵称,被赋值为命令 fc –s,这意味着每次在命令行下键入 r 都将被
替换成 fc –s。
4 执行 date 命令,它将显示当前时间。
5 别名被用作 fc –s 的快捷键。执行以 d 开头的最近执行的命令。

重新执行历史命令(bang!bang!)
。要执行命令列表中的命令,须使用感叹号(称为 bang)。
如果你用两个感叹号(!!)bang,bang,则将重新执行历史列表中最后的命令。如果你在感
叹号后跟一个数字,将执行该数字指定的命令。如果键入的是感叹号加一个字符或字符串,
那么以该字符或字符串开头的、最近的命令就会被重新执行。 (^)也被用作编辑先前命令的
一个快捷方法。!的具体用法见表 8.8。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 223

表 8.8 替换与历史

事件指示符 含义
! 标明历史替换的起始标志
!! 重新执行上一条命令
!N 重新执行历史列表中的第 N 条命令
!-N 重新执行当前命令的倒数第 N 条命令
!string 重新执行以 string 开头的最近一条命令
!?string? 重新执行包含 string 的最近一条命令
!?string?% 重新执行历史列表中含 string 最近的命令行参数
!$ 在当前命令行中使用最近执行命令的最后参数
!!string 将 string 追加到前一条命令中,并执行之
!Nstring 将 string 追加到第 N 条命令中,并执行之
!N:s/old/new 替换第 N 条命令中第一个 old 字符串为 new
!N:g/old/new 将第 N 条命令中所有 old 字符串替换为 new
^old^new^ 替换最近执行命令的 old 字符串为 new
comnand!N:wn 执行当前命令,其参数由第 N 条命令的参数(wn)提供。wn 为由 0,1,..
开始的数字,这些数字表示前面命令的单词标识。单词 0 为命令本身,1 为它
的第一个参数(见实例 8.32)

实例 8.31

1 $ date
Mon Jul 12 12:27:35 PST 1999

2 $ !!
date
Mon Jul 12 12:28:25 PST 1999

3 $ !106
date
Mon Jul 12 12:29:26 PST 1999

4 $ !d
date
Mon Jul 12 12:30:09 PST 1999

5 $ dare
dare: Commmand not found.

6 $ ^r^t
date
Mon Jul 12 12:33:25 PST 1999

说明
1 在命令行下执行 Linux 的 date 命令。历史列表作相应更新,这就是列表中最近执行的命令。
2 !!(bang bang)从历史列表中获得该命令,并重新执行之。

PDF created with pdfFactory Pro trial version www.pdffactory.com


224 第8章

3 重新执行历史列表中的 106 号命令。


4 重新执行命令列表中以字符 d 开头的最近执行的命令。
5 错误键入命令,应该是 date 而不是 dare。
6 ^用于替换命令列表中最近执行命令的字符。第一处 r 被替换为 t,即 dare 改为 date。

实例 8.32

1 $ ls file1 file2 file3


file1 file2 file3

$ vi !:1
vi file1

2 $ ls file1 file2 file


file1 file2 file3

$ ls !:2
ls file2
file2

3 $ ls file1 file2 file3


$ ls !:3
ls file3
file2

4 $ echo a b c
a b c
$ echo !$
echo c
c

5 $ echo a b c
a b c
$ echo !^
echo a
a

6 $ echo a b c
a b c
$ echo !*
echo a b c
a b c

7 $ !!:p
echo a b c

说明
1 ls 命令列出文件 file1、file2 和 file3,同时历史列表也相应更新。命令行被分成以 0 为始的多个单
词。通过在单词序号前添加冒号,就可以把单词从历史列表中提取出来。!:1 符号的意思是:从
历史列表的最近执行的命令取第一个参数,并替换到命令串中。最近执行命令的第一个参数是
file1(单词 0 是命令本身) 。
2 !:2 被替换为最近执行命令的第二个参数 file2,并作为 ls 的参数给出。结果是显示 file2(file2 是
第三个单词)。
3 ls !:3 的意思是:从历史列表中取得最近执行命令的第四个参数(单词序号从 0 开始) ,并把它传

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 225

给 ls 命令作为参数。
(file3 是第四个单词)。
4 感叹号(!)加上美元符号($)指的是命令列表中最近执行命令的最后一个参数。最后的参数是
c。
5 脱字符(^)代表命令后的第一个参数。感叹号(!)加上插入记漏字符号(^)指的是历史命令
列表中最近执行命令的第一个参数。最近执行命令的第一个参数是 a。
6 星号(*)代表命令后的所有参数。感叹号(!)加上星号(*)指的是历史命令列表中最近执行
命令的全部参数。
7 显示但不执行历史列表中最近执行命令,历史列表被更新。现在你可以在该行中进行脱字符(^)
替换了。

命令行编辑。bash Shell 提供了两个内置编辑器,emacs 和 vi,因此你可以以交互模式对


命令列表进行编辑。这项特性是通过称为 Readline 库的软件包实现的(参见“Readline 库和
绑定键” 以获得关于 readline 的函数)。当你使用命令行编辑的功能时, 不管是在 vi 还是 emacs
模式中,都是 readline 函数决定了哪一个键对应哪一项功能。例如,如果使用 emacs,Ctrl-P
则允许你在历史列表中向上滚动。 而如果使用的是 vi, 则用 K 键在历史列表中向上移。 readline
还控制箭头键、光标移动、修改、删除、插入文本、重做或撤销更正。readline 的另外一个
功能是在前面“命令和文件名自动填充”中提到的自动填充功能。这项特性能让你只键入命
令、文件和变量的部分,然后通过键入 Tab 键来让系统自动完成剩下的部分。除此以外,还
有许多由 Readline 库提供的用于帮助管理命令行的功能。
内置 emacs 编辑器是默认的内置编辑器, 是非模态的。 而内置 vi 编辑器在两种模式下工作,
一种模式是命令行模式,另一种模式是键入文本模式。如果你用的是 UNIX 或是 Linux,就可
能对其中的一个编辑器很熟悉。要激活 vi 编辑器在~./bashrc 文件中填入下面列出的 set 命令
行 4。要设置 vi,既可以在提示符下键入,也可以在~/.bashrc 文件中添加下面例子中的命令。
4.如果没有设置 set –o(编辑器) ,但是 EDITOR 变量已经被设置为 emacs 或 vi,那么
bash 将使用变量中的定义。

实例 8.33
set –o vi

说明
将 vi 设置为内置编辑器,用于对命令列表进行编辑。

如果要换成 emacs,则要键入:

实例 8.34
set –o emacs

说明
将 emacs 设置为内置编辑器,用于对命令列表进行编辑。

vi 内置编辑器。要对历史列表进行编辑,应在命令行下键入 ESC 键,然后用 K 键在历

4.如果 set –o(编辑器)没有被设置,而 EDITOR 变量被设置为 emacs 或 vi,Bash 将使用这个定义。

PDF created with pdfFactory Pro trial version www.pdffactory.com


226 第8章

史列表中向上翻动,J 键用于向下翻动 5,就像标准的 vi 移动键一样。当你找到需要编辑


的命令行时,可以用你在 vi 中使用的标准键来进行左右移动、删除、插入和修改文本(参
见表 8.9)
。完成编辑后,键入回车键即可执行该命令,该命令最后被追加到命令列表的末
尾。

表 8.9 vi 命令

命令 功能
在历史列表文件中移动
ESC k 或+ 在历史列表中向上移动
ESC j 或- 在历史列表中向下移动
G 移到历史列表的第一行
5G 移到历史列表的第五行
/string 向上搜索历史列表
? 向下搜索历史列表
在一行中移动
h 在一行中向左移动

l 在一行中向左移动
b 向后移动一个单词
e或w 向前移动一个单词
^或 0 移到一行中第一个字符
$ 移到行末
vi 中的编辑
aA 追加文本
iI 插入文本
dd dw x 删除文本并把它放到缓冲区(行、单词或字符)
cc C 修改文本
uU 撤销操作
yy Y Yank(将一行拷贝到缓冲区中)
pP 将复制和删除的行粘贴在某行前面或后面
rR 替换行文本中的一个字母或任意文本

emacs 内建编辑器。如果使用的是 emacs 内置编辑器,就像 vi 一样,也是从命令行开


始。要在历史列表文件中向上移动,键入^P。如果向下移动,键入^N。要用 emacs 来修改、
更正命令文本,然后键入回车键,命令就会被重新执行。见表 8.10。

5.vi 是大小写敏感的,大写 J 和小写 j 是不同的命令。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 227

表 8.10 emacs 命令

命令 功能
Ctrl-P 向上移动历史列表文件
Ctrl-N 向下移动历史列表文件
ESC < 移到历史列表文件的第一行
ESC > 移到历史列表文件的最后一行
Ctrl-B 向前移动一个字符
Ctrl-R 向后移动一个字符
ESC B 向后移动一个单词
Ctrl-F 向后移动一个字符
ESC F 向前移动一个单词
Ctrl-A 移至行首
Ctrl-E 移至行末
ESC < 移到历史列表的第一行
ESC > 移到历史列表文件的最后一行
emacs 编辑
Ctrl-U 删除行
Ctrl-Y 粘贴回行
Ctrl-K 从光标开始删除到行末
Ctrl-D 删除一个字母
ESC D 向前删除一个单词
ESC H 向后删除一个单词
ESC space 在光标处做一个标志
Ctrl-X Ctrl-X 交换标志和光标
Ctrl-P Ctrl-Y 将从光标处到标志处的区域放入缓冲区中(Ctrl-P)然后粘贴出来

FCEDIT 和编辑命令。如果在 fc 命令的-e 参数后面跟着一个 Linux 编辑器的名字,即可


在将历史列表中选定的命令行调入编辑器中。比如,fc –e vi –l –3 将调用 vi 编辑器,并在/tmp
目录中创建临时文件,将历史列表的倒数三个命令调入 vi 的缓冲区中。可以对这些命令进
行编辑也可以注释掉(在命令行前加#即可注释之) 。如果用户退出编辑器,命令将被回显并
6
执行。
如果没有给出编辑器名字,则使用 FCEDIT 变量的值(常在初始化文件中设置,要么是
bash_profile 要么是.profile)。如果没有设置 FCEDIT 则使用 EDITOR。如果结束编辑,并退
出编辑器,则所有编辑过的命令都将被回显和执行。

实例 8.35
1 $ FCEDIT=/bin/vi

6.无论是保存退出还是直接退出编辑器,这些命令都将被执行,除非它们被注释掉或者删除掉。

PDF created with pdfFactory Pro trial version www.pdffactory.com


228 第8章

2 $ pwd
3 $ fc
< Starts up the full screen vi editor with the pwd command on line 1>

Pwd
~
~
~ vi 编辑器
~
~
~

4 $ history
1 date
2 ls –1
3 echo "hello"
4 pwd

5 $ fc –3 –1 启动 vi,编辑,保存退出,执行最后三个文件:

说明
1 FCEDIT 变量可以被赋值为系统中任何 Linux 文本编辑器的路径名,如 vi、emacs 等。如果没有
设置,则使用默认编辑器 vi 编辑器。
2 在命令行中键入 pwd 命令,它将被放入历史列表中。
3 fc 命令使得编辑器(在 FCEDIT 中指定)将最近键入的命令调入编辑窗口。用户写入并退出编辑
器后,键入的命令将被执行。
4 history 命令列出最近键入的命令。
5 fc 命令用于启动编辑器并从历史列表中调入最后三条命令到编辑器的缓冲区。

8.2.4 Readline 库和绑定键


Readline 库。Readline 库(键入:man readline)是一系列的例程库,自由软件组织(Free
Software Foundation)拥有其版权,这些例程用于控制在命令行键入文本时的键功能。当任何
使用 Readline 库的程序启动时,比如 Shell 程序,即读入一个初始化文件(通常为~/.inputrc) ,
然后 readline 提示用户输入。然后从终端读入一行并返回。你也可以键入不同的组合键来编辑
你先前输入的命令。readline 使用 emacs 风格的符号来标识组合键。比如 C-h 表示 Contorl-H,
M-x 表示 Meta-x(如果你的键盘上没有 Meta 键,可以使用 Escape 键或 Alt 键代替) 。如果用
vi 绑定键,而不是用默认的 emacs 绑定,则只需用在“命令行编辑”列出的 set –o vi 命令来打
开 vi 命令编辑器,或是在初始化文件~/.inputrc 中添加下面一行:
set editing-mode vi

为了配置 readline,在名为~/.inputrc 的文件中写入控制键的功能(如果设置了 INPUTRC


环境变量,文件名可以不同)。当启动 Shell 或其他使用 Readline 库的程序时,即读入.inputrc
文件、文件中定义的键和变量。
你可以通过内建命令 bind 的输出结果来查看键的绑定情况。如果想获得键绑定和定义
的完整列表,可参见 readline 的帮助手册。它列出了所有的键绑定及其控制功能。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 229

绑定键。在.inputrc 文件中有几种方式可以进行键绑定。可以用键的英文名,比如 Ctrl-T


代表 Control 和 T 字母,也可以用转义序列\C-t。要给键赋值,可以使用像 backward-kill-line
一样的 readline 内建命令、宏或 readline 变量。emacs 和 vi 的功能,通过内建命令 bind7 的输
出结果可以看到。在 bash 提示符下键入:
find –v8 or bind –p

l 英语格式要以英语格式绑定键,需要顺序拼出键序列、冒号以及定义键序列行为
的命令或宏。在下面的例子中,C-u 绑定为 universial-argument 功能,C-o 绑定为
运行宏“>& output” 9。

实例 8.36
(English format for binding keys)
(from ~/.inputrc file)
1. control-U: nuiversal-argument
2. Meta-Robout: backward-kill-word
3. Control-O: ">& output"a

说明
1 Control-U 被设置为全体参数。通过键入 Control 键和 U 键,全体参数都将被设置到在命令行中键
入的下一条命令。例如,如果键入 Control-U,你将看到一行自身: (arg:4) ,然后如果你用的是
能取得一个参数的 emacs 命令,比如 Control-P,而不是在历史列表中向上移动一行,你将向上
移动 4 行。如果你在看到的字符串(arg:4)后键入数字,键入的数字会把 4 替换为新的参数个数。
比如键入了 6, 将会将全体参数(universal argument)变为:(arg:6)。在此之后, 每次键入 Control-U,
在提示符(arg:#)下该数字将会乘以 4,得出(arg:24)。总是乘以 4。
要测试新的设置,输入一行文本,然后键入 Control-U,接下来键入左、右移键。你就可
以向左或向右移动一定长度的字符,长度为在全体参数提示符显示的数字,默认情况下是 4 的
倍数。
2 Meta 和 Rubout key(Alt 和 Del 键)被绑定至 readline 命令:backward-kill-word。用这个组合键
可以向后擦除从光标开始的单词。
3 Control-O 键序列被赋为一个由文本串表达的宏。当按下 Control-O 键时,将在屏幕上显示
“>&ouput”。这可用于将命令的标准输出和标准错误重定向到文件 ouput 中。例如,如果你在命令
行提示符下键入:find / -type d –print,然后键入 Control-O,则将看到 find / -type d –print >& output。
a.为了测试这些键绑定,要将它们写入~/.inputrc 文件中。可以通过下列方法之一测试一个绑定:1.注销并重新登录。
2.启动一个新的 bash Shell。或 3.键入 Control-X,Control-R。

l 转义格式。如果要使用的是转义键序列,键序列将用双引号括起来。在下面的例子
中,\C-u 代表 Control-U,\C-x\c-r 代表 Control-X 紧接着 Control-R,最后一个例子
显示了绑定 F1 功能键的转义序列,如果该键在 Linux 控制窗口键入的话。

实例 8.37

(The escape sequences)


(from ~/.inputrc file)

7.要取得 bind 的完整参数列表,键入:help bind。


8.在 bash2.x 中。
9.这些键绑定的例子中部分是从 bash 的帮助手册中来的,手册对 readline 进行了定义。

PDF created with pdfFactory Pro trial version www.pdffactory.com


230 第8章

1.\C-u: universal-argument
2.\C-x\C-r:re-read-init-file
3.\e[11~:Function Key 1

说明
1 转义序列被括在双引号中。该语法产生与前面例子中英语符号格式:Control-U:universal-argument
相同的绑定结果。
2 如果你编辑了~/.inputc 文件,转义序列\C-x\C-r 可用于重新读入它,与 source .bashrc 文件类似。
否则,改动不会起作用,直到你启动另一个 Shell。
3 这个转移序列被用于终端上的功能键 1,终端类型为 Linux。在这个例子中,功能键被绑定到一
个字符串中。毫无疑问,你可能想做一些更有意思的事情。但是,在绑定功能键的时候一定要小
心。某些功能键可能已经有了特定的功能,比如关闭显示器或锁住屏幕。

l 宏和转义序列。宏是由双引号括起的文本串,它可以赋予键序列用于定义键的作用。
例如,序列\C-o 被赋予宏字符串“|more”,每次当用户按下 Control-D 键的时候,
宏就会被扩展为“|more”,在键序列和宏定义中,你还可以嵌入转义序列来代表表
8.11 中列出的特殊字符。反斜杠也能用于其他字符,这样它们就可以被视为普通文
本。
表 8.11 转义序列

\C- Control 键前缀


\M- Meta 键前缀
\e 转义字符
\\ 反斜杠
\” 双引号
\’ 单引号
\a 警告声
\b 退格符
\d 删除
\f 换页
\n 换行符
\r 回车符
\t 水平制表符
\v 垂直制表符
\nnn 代表相应字符的 ASCII 码的八进制数
\xnnn 代表相应字符的 ASCII 码的十六进制数

readline 变量。可通过在~/.inputrc 文件设置一些 readline 变量来定制 readline 的行为。


要设置 readline 变量,格式为:
set variable value

对于一些变量,要么赋予 On,要么赋予 Off。变量列表及其值列在表 8.12 中。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 231

格式
set variable value

实例 8.38
1 设置 vi 编辑器的编辑模式。
2 打开波浪线扩展。

表 8.12 readline 变量

bell-style(audible) 控制终端响铃,如果没有设置,将不会响铃。如果设置为可见,在可用
的情况下会使用一个可见响铃。如果设置为 audible,readline 将试图激活
终端的响铃
coment-begin 为 readline 的 insert-comment 所使用,默认值为一个#
completion-query-items 控制用户可要求查看的自动填充数目,默认值为 100
convert-meta(On) 通过去掉有第八位字符的第八位并添加转义字符(通常使用转义符作为
meta 前缀)
,将其转换为 ASCII 键序列
disable-completion(Off) 如果设置为 On,则禁止单词自动填充
editing-mode(emacs) 将命令行键绑定设置为 emacs 或 vi。emacs 为默认值
enable-keypad(Off) 如果设置为 On,readline 将在需要调用的时候激活应用程序键盘。某些
系统需要通过这样来激活箭头键
例子 pand-tidle(Off) 如果设置为 On,当 readline 试图完成单词自动填充的时候就会进行 tilde
扩展
horizontal-scroll-mode(Off) 如果设置为 On,则让 readline 以单行显示(尽管键入的内容超过了屏幕
的边界)
。当输入超过屏幕的宽度时,在单行屏幕上进行翻动而不是折出
新行
input-meta(Off) 如果设置为 On,readline 将允许“八位”输入(即去掉读入字符的高位)

而不管终端声明它所支持的。meta-flag 名字与该变量同义
isarch-terminators(') 字符串应该能中断 incremental 搜索而不是作为一个命令顺序执行。如果
该变量没有被赋值,ESC 字符和 C-J 将用于中断一个 incremental 搜索
keymap(emacs) 设置当前 readline 映射键盘。合法的映射键盘名为 emacs、emacs-standard、
emacs-meta、emacs-ctlx、vi、vi-command 和 vi-insert。vi 等同于 vi-command
和 emacs 到 emacs-standard。默认值为 emacs。editing-mode 的值也会影响
到默认的映射键盘
mark-directories(On) 如果设置为 On,用反斜杠作为目录名自动填充符号
mark-modified-lines(Off) 如设置为 On,经修改的历史命令行将星号作为前导标识
meta-flag(Off) 如果设置为 On,则允许“八位”输入
output-meta(Off) 如果设置为 On,则以直接八位字符集显示而不是作为 meta 前缀的转义
序列来显示
print-completions-ho 如果设置为 On,readline 将以符合字母顺序垂直显示自动填充内容,而
不是以屏幕顺序显示
show-all-if-ambiguous(Off) 如果设置为 On,则在出现超过一个填充可能的单词时,立即列出所有匹
配内容而不是发出响铃声
visible-stats(Off) 如果设置为 On,则当进行自动填充时,在文件名后追加一个字符以表示
文件类型(可由系统调用 stat 列出)

PDF created with pdfFactory Pro trial version www.pdffactory.com


232 第8章

若想查看 readline 变量,请参见用在例 8.39 中的 bind 内置命令。

实例 8.39

1 $ bind -V
completinon-ignore-case is set to 'off'
convert-meta is set to 'on'
disable-completion is set to 'off’
enable-keypad is set to 'off'
expand-tilde is set to 'off'
horizontal-scroll-mode is set to 'off'
input-meta is set to 'off'
mark-directories is set to 'on'
mark-modified-lines is set to 'on'
meta-flag is set to 'off'
output-meta is set to 'off'
print-completions-horizontally is set to 'off'
show-all-if-ambiguous is set to 'off'
visible-stats is set to 'off'
bell-style is set to 'none'
comment-begin is set to ''
completion-query-items is set to '100'
editing-mode is set to 'vi'
keymap is set to 'vi-insert'

简单条件。这里介绍了几个 readline 条件指令,可以将它们写入.inputrc 文件中,以在设


置键绑定前检验特定的条件。例如,只有使用了 emacs 内置编辑器、终端类型为 Linux 或是
在特定的应用程序,如 Shell 或 xterm 下,某些键绑定才能正常工作。
readline 用于构造条件表达式的指令有:$if、$else、$endif 和$include。
$if 指令允许基于某种编辑模式、使用终端类型或是使用 readline 的应用程序设置键绑定
$endif 是$if 的结构的终止位置,$else 用来引导判断为假的时候的语句分支。
$endif 用于结束$if 和$else 指令(用于测试失败时的分支) 。
$include 指令把单个文件名作为参数,以指定从该文件中读入命令和键绑定。

表 8.13 $if 测试

测试 例子 含义
mode=editor $if mode=emacs 测试 readline 是否工作在 emacs 模式下,也可以进行 vi
模式测试
term=terminal $if term=sun 测试终端为 sun 还是 sun-cmd 类型
application $if bash 测试正在使用 readline 的应用程序是否为 bash shell
filename $include /etc/inputrc 从/etc/inputrc 文件中读入键绑定

设置.inputrc 文件。下面的例子演示了如何使用在本节中描述的 readline 命令和变量设


置一个.inputrc 文件,该文件在你的主目录下。要在你创建/修改该文件后重新读入它,只需
键入 Ctrl-x、Ctrl-r 序列。或者为了测试,你可以在提示符下键入“bash”来启动一个新的
shell。.inputrc 文件将被 Readline 库重新读入。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 233

实例 8.40

(Sample .inputrc file)

1 # set editing-mode vi value of editing-mode variable also


# affects the default keymap.
2 set bell-style none
3 set mark-directories On
4 set mark-modified-lines On
5 Control o: "| more"
6 "C-x\C-r": re-read-init-file
7 $if mode=emacs
8 Control-t: universal-argument

9 $endif
10 $if term=linux
"\e[12~": "Welcome to the Linux World!\n"
$endif

说明
1 在你将编辑模式设置为 vi 后,当进行命令行编辑时,vi 编辑器的键绑定即起作用。在这里,它
被注释起来了。
2 关闭终端响铃。现在,即使你到达了一行的末尾或是无法进行文件名自动填充时,也不会响铃。
3 如果将 mark-directories 变量设置为 on,当进行文件名自动填充时,将追加反斜杠。
4 历史列表中的文本行是先前曾经输入的命令。假如设置 mark-modified-lines 为 on 的话,如果任
意命令被修改,则在含修改命令的行前加上星号。
5 在这个例子中,英文格式的键序列被绑定为一个宏。如果同时按下 Control 键和 O 键,该宏将会
被扩展。例如,如果键入 ls Ctrl-O,你就会看到:ls |more。
6 该例使用 emacs 风格的转义序列格式来代表键序列。 如果键入 Control-x,
跟着 Control-r,
则 readline
将重新读入~/.inputrc 文件。
7 $if 标识符用于测试 readline 正在使用的编辑器是否为 emacs。
8 如果编辑器为 emacs,Control-T 将被绑定为 universal argument,一条 readline 命令。如果输入
Control-T 键序列,universal argument 将用于修改最近键入的命令(参见实例 8.36 关于 universal
argument 的解释)。
9 $endif 指令用于结束$if 指令及其语句。
10 如果终端为虚拟控制台 Linux,则当按下 F1 功能键时,会显示“Welcome to the Linux World!”字样。

8.2.5 别名
别名是用户对命令定义的缩写,它在命令有很多参数和选项或语法难以记忆的情况下非
常有用。在命令行下进行的别名设置不会被子 Shell 所继承。别名通常在.bashrc 文件中进行
设置。因为在启动一个新的 Shell 的时候,总是要先执行.bashrc 文件,所以在新的 Shell 中
所有的别名都会被重置。别名也能在 Shell 脚本中使用,除非在脚本中直接设置,否则会导
致一些潜在的移植性问题。
列出别名。内置命令 alias 列出所有设置的别名。首先显示别名,接着是它所代表的真
正的命令。

实例 8.41

$ alias

PDF created with pdfFactory Pro trial version www.pdffactory.com


234 第8章

alias co='compress'
alias cp='cp –i'
alias more='more'
alias mv='mv –i'
alias ls='ls -–colorztty'
alias uc='uncompress'

说明
alias 命令列出的是命令的别名,该别名所代表的真正命令列在等号(=)后面。

创建别名。alias 命令用于创建别名。第一个参数为别名的名称,即命令的昵称。余下的
部分是在执行别名时所包含的一个或多个命令。bash 别名不能带参数(参见“定义功能” )
多个命令间用分号进行分隔,含空格和元字符的命令则用单引号括起来。

实例 8.42

1 $ alias m=more
2 $ alias more=more
3 $ alias lF='ls –alF'
4 $ alias r='fc –s'

说明
1 more 命令的昵称被设置为 m。
2 more 命令的别名被设置为 mroe,在你不会拼写的时候,这很方便。
3 由于存在空格所以别名定义被引号括起来。lF 别名为命令 ls -alF 的昵称。
4 r 别名将用于替代 fc -s 命令,通过某个指定的模式来重新执行历史列表中的命令。比如 r vi 将重
新执行含 vi 模式中最近执行的命令。

删除别名。unalias 命令用于删除一个别名。要临时禁止别名,可在别名前加一个反斜
杠。

实例 8.43

1 % unalias mroe
2 % \ls

说明
1 unalias 命令从定义的别名列表中删除别名 mroe。
2 ls 别名被临时禁止,而只执行本身。

8.2.6 管理目录栈
如果你工作时需要 cd 上下某些相同的目录,就可以通过把这些目录放入目录栈并管理
它来使得访问这些目录更加容易。pushd 内置命令将目录放入一个栈中,并用 popd 命令移出
它们(见实例 8.44)这是一个目录列表,栈最左边的目录是最近放入栈的目录,可用内置命
令 dirs 来列出这些目录。
dirs 内置命令。带-l 选项的内置命令 dirs 以全路径名的格式显示目录栈。如果没有选

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 235

项,dirs 命令用一个 tilde 符号来标识 home 目录。如果带有一个+n 选项,dirs 显示从目录


列表左边数起第 n 个目录项,以 0 为始。带-n 参数的情况也类似,只不过从右边以 0 为始
数起。
pushd 和 popd 命令以一个目录作为参数的 pushd 命令将把新的目录添加至目录栈中,
同时切换到该目录。如果参数为+n,n 为数字,pushd 将 rotate 目录栈使得栈左数第 n 个目
录放到栈顶。如果是-n 选项,则做相似的事情,不过是从右边数起。当没有参数时,pushd
将交换栈顶的两项,以使得在目录间前进和后退更加简单。
popd 命令从栈顶移除一个目录项,并切换到该目录。如果带有+n 选项,n 为数字,popd
移除从 dirs 命令显示列表的左数第 n 项。

实例 8.44

1 > pwd
/home/ellie

> pushd ..
/home ~

> pwd
/home

2 > pushd # swap the two top directories on the stack


~ /home

> pwd
/home/ellie

3 > pushd perlclass


~/perlclass ~ /home

4 > dirs
~/perlclass ~ /home

5 > dirs -1
/home/ellie/perlclass /home/ellie /home

6 > popd
~/home

> pwd
/home/ellie

7 > popd
~ home

> pwd
/home

8 > popd
bash: popd: Directory stack empty.

PDF created with pdfFactory Pro trial version www.pdffactory.com


236 第8章

说明
1 首先,pwd 命令显示当前的工作目录为/home/ellie,接下来带..参数的 pushd 命令将父目录推入目
录栈中。pushd 命令的输出结果提示/home 目录位于目录栈的栈顶(从显示列表的左边开始)且
用户主目录(以波浪符~代表)位于栈底。pushd 命令还将目录切换至被推入栈的目录,即切换至
/home,新的目录用第二个 pwd 命令显示出来。
2 不带参数的 pushd 命令把栈顶的两项进行交换,并切换至交换的目录。在本例中,目录被切换回
用户的主目录/home/ellie。
3 pushd 命令将其参数~/perlclass 推入栈中,并切换至该目录。
4 内置命令 dirs 显示目录栈,栈顶从左边开始。波浪符代表用户的主目录。
5 带-l 选项的 dirs 命令以全路径名显示目录栈,而不是使用波浪符扩展。
6 popd 命令从栈顶移除一个目录项,并切换到该目录。
7 popd 命令从栈顶中移除另一个目录,并切换到该目录。
8 由于栈已空,popd 命令无法移除任何目录项,于是报告栈空错误信息。

8.2.7 元字符(通配符)
元字符是用于表示某些特定而非其自身含义的特殊字符,Shell 元字符称为通配符
(wildcard)。表 8.14 列出了元字符及其含义。
表 8.14 元字符

元字符 含义

\ 按文本含义解释后面接着的字符

& 在后台运行进程

; 命令分隔符

$ 变量替换

? 匹配一个字符

[abc] 匹配一个字符集中的一个字符;如 a、b 或 c

[!abc] 匹配一个字符集外的一个字符;如非 a、b 或 c

* 匹配零或多个字符

(cmds) 在子 shell 中执行命令

{cmds} 在当前 shell 中执行命令

8.2.8 文件名替换
当对命令行求值的时候,Shell 用元字符来缩写与某些字符集匹配的文件名或路径名。
在表 8.15 中列出的文件名替换元字符被扩展为文件名字母列表。将元字符扩展为文件名的
过程也称为文件名替换或 globbing。如果使用了元字符而又没有任何文件名可匹配,Shell
将把该元字符视为文本字符。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 237

表 8.15 shell 元字符和文件名替换

元字符 含义
* 匹配零或多个字符
? 匹配一个字符
[abc] 匹配一个字符集中的一个字符;如 a、b 或 c
[!abc] 匹配一个字符集外的一个字符;如非 a、b 或 c
{a,ile,ax} 匹配一个字符或一个字符集
[!a-z] 匹配从 a~z 范围以外的字符
\ 转义或禁止元字符

星号。星号是匹配文件名中零个或任意个字符的通配符。

实例 8.45

1 $ ls *
abc abc1 abc122 abc123 abc2 file1 file1.bak file2 file2.bak none
nonsense nobody nothing nowhere one
2 $ ls *.bak
file1.bak file2.bak
3 $ echo a*
ab abc1 abc122 abc123 abc2

说明
1 星号被扩展为当前工作目录下的所有文件。所有文件都被作为 ls 命令的参数,并显示出来。
2 匹配所有以零个或多个字符并以.bak 结尾的文件,并列出这些文件。
3 匹配所有以 a 开头且后面接着零个或多个字符的文件,这些文件名作为参数传送给 echo 命令。

实例 8.46

1 $ ls
abc abc122 abc2 file1.bak file2.bak nonsense nothing one
abc1 abc123 file1 file2 none noone nowhere

2 $ ls a?c?
abc1 abc2

3 $ ls ??
ls: ??: No such file or directory

4 $ echo abc???
abc122 abc123

5 $ echo ??
??

说明
1 列出当前目录下的文件。
2 列出所有以 a 开头的,后面是一个字符,且跟着 c 和一个字符的文件名。

PDF created with pdfFactory Pro trial version www.pdffactory.com


238 第8章

3 如果有的话,列出含且只含两个字符的文件名。如果没有任何两个字符的文件,问号被视为普通
的文件名。若这样的文件一个也没有找到,则显示错误信息。
4 echo 命令显示以 abc 开头且后面跟着三个字符的文件名。
5 在本目录内没有两个字符的文件。如果没有匹配的话,Shell 将把问号视为普通的文本问号。

方括号。这种括号用于匹配包含某个字符集或某个字符范围内的一个字符的文件名。

实例 8.47

1 $ ls
abc abc122 abc2 file1.bak file2.bak nonsense nothing
one abc1 abc123 file1 file2 none noone nowhere

2 $ ls abc[123]
abc1 abc2

3 $ ls abc[1-3]
abc1 abc2

4 $ ls [a-z][a-z][a-z]
abc one

5 $ ls [!f-z]???
abc1 abc2

6 $ ls abc12[23]
abc122 abc123

说明
1 列出当前工作目录下的所有文件的文件名。
2 列出所有以 abc 开头,接着是字符 1、2 或 3 的四个字符的文件名。这里只匹配方括号中字符集
的一个字符。
3 列出所有以 abc 开头,接着是 1~3 范围内的一个数的四个字符的文件名。
4 列出所有含且只含三个小写字母的文件名。
5 列出所有首字符不包含在 f~z([!f-z])范围内、后面跟着三个任意字符的文件名。这里的?代表
一个字符。
4 列出以 abc12 开头,接着是字符 2 或 3 的文件名。

实例 8.48

1 $ ls
a.c b.c abc ab3 ab4 ab5 file1 file2 file3 file4 file5 foo
faa fumble

2 $ ls f{oo,aa,umble}
foo faa fumble

3 $ ls a{.c,c,b[3-5]}
a.c ab3 ab4 ab5

4 $ mkdir /usr/loca1/src/bash/{old,new,dist,bugs}

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 239

5 $ chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}

6 $ echo fo{o, um}*


fo{o, um}*

7 $ echo {mam,pap,ba}a
mama papa baa

8 $ echo post{script,office,ure}
postscript postoffice posture

说明
1 列出当前目录下的所有文件。
2 列出以 f 开头,接着是 oo、aa 或 umble 字符串的文件。花括号中若有空格则会导致出错信息:
Missing }。
3 列出以 f 开头,接着是.c、c、b3、b4 或 b5 字符串的文件(方括号能在花括号中使用) 。
4 在目录/usr/local/src/bash 中建立四个新的目录:old、new、dist 和 bugs。
5 赋予文件被根权限。包括./usr/ucb 目录下的 ex 和 edit,文件名 ex 后面是一个字符,一个点然后
是至少一个字符的文件,以及/usr/lib/目录下的 how_ex 文件。
6 如果在括号中有任何一个没有用引号括起来的空格,就不会进行括号扩展。
7 括号并不总是导致文件名扩展。在本例中,经过扩展后,a 就被加到括号中的每一个字符串后面。
8 前导字符串为 post,跟着的是括号中的以逗号进行分隔的一个列表。进行括号扩展,并把结果显
示出来。

转义元字符。要将元字符视为普通文本字符,可以用反斜杠来禁止元字符被解释扩展。

实例 8.49

1 $ ls
abc file1 youx

2 $ echo how are you?


How are youx

3 $ echo How are you\?


How are you?

4 $ echo When does this line \


> ever end\?
When does this line ever end?

说明
1 列出当前目录的文件(注意文件 youx) 。
2 Shell 会对?进行文件名扩展。任何当前目录下以 y-o-u 开头并跟着一个字符的文件名将被替换到
字符串中。youx 文件名被替换到字符串中,成为“How are youx”(这可能不是你所期望的结果)

3 通过在问号前面添加一个反斜杠,问号将被转义,这意味着 Shell 不会把它视为通配符进行解释
扩展。
4 通过添加前导反斜杠来转义换行符。次提示符将一直显示直至字符串被换行符所断开。问号(?)
被转义以防止进行文件名扩展。

波浪号和 Hyphen 扩展。波浪字符被 bash Shell(由 C Shell 而来)采纳为路径名扩展字

PDF created with pdfFactory Pro trial version www.pdffactory.com


240 第8章

符。波浪号本身代表用户主目录的全路径名 10。当把波浪号加在一个用户名前,则表示该用
户的全路径名。
当波浪号后面跟着加号时,PWD(present working directory,代表当前工作目录)的值
将替换波浪号所代表的目录名。波浪号后面跟着减号时,波浪号所代表的目录名将被替换为
先前的工作目录。OLDPWD 指的是先前的工作目录。

实例 8.50

1 $ echo ~
/home/jody/ellie

2 $ echo ~joe
/home/joe

3 $ echo ~+
/home/jody/ellie/perl

4 $ echo ~-
/home/jody/ellie/prac

5 $ echo $OLDPWD
/home/jody/ellie/prac

6 $ cd -
/home/jody/ellie/prac

说明
1 波浪号等同于用户主目录的全路径名。
2 波浪号后面跟着用户名表示 joe's 的主目录的全路径名。
3 ~+符号表示工作目录的全路径名。
4 ~-符号表示先前的工作目录的全路径名。
5 OLDPWD 变量含先前工作目录名的值。
6 减号引用了先前工作目录,cd 命令切换到并显示先前的工作目录。

控制通配符(globbing)。如果设置了 bash noglob 变量或者使用了带-f 选项的 set 命令,


称为 globbing 的文件名替换就会被禁止。这意味着所有元字符将表示其自身,而不再被视为
通配符。这可能在使用 grep、sed 或 awd 程序搜索含元字符的模式时非常有用。如果没有设
置 globbing,所有元字符必须用反斜杠进行转义以关闭通配解释。
内置 shopt 命令(bash 2.x 版本)也支持控制 globbing 的选项。

实例 8.51

1 $ set noglob or set -f

2 $ print * ?? [] ~ $LOGNAME
* ?? [] /home/jody/ellie ellie

3 $ unset noglob or set +f

10.不论是用单引号还是双引号引用,波浪符号都不会被扩展。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 241

4 $ shopt –s dotglob # Only available in bash versions 2.x

5 $ echo *bash*
.bash_history .bash_logout .bash_profile .bashrc bashnote
bashtest

说明
1 -f 选项作为 set 命令的一个参数,它的作用是关闭用于文件名扩展的代表特殊含义通配符功能。
2 文件名扩展元字符没有被扩展解释而是视为它们本身显示出来。注意由于波浪号和美元符号没有
被用于文件名扩展,因此它们仍被扩展解释了。
3 如果既没有设置 noglob 也没有设置-f 选项,文件名元字符将被扩展解释。
4 内置命令 shopt 允许你设置 shell 的选项。dotglob 选项允许文件名匹配那些以点字符开头的
globbing 元字符。通常情况下,以点字符开头的文件是不可见的,且在进行文件名扩展时被忽略。
5 由于在第 4 行设置了 dotglob 选项,所以当使用通配符*进行文件名扩展时,那些匹配模式且以点
字符开头的文件名也被扩展出来。

扩展文件名 globbing(bash 2.x)。沿袭 Korn Shell 的模式匹配,bash 2.x 也有扩展的功


能,允许正则表达式类型语法(参见表 8.16) 。除非用 shopt 命令的 extglob 选项打开该功能,
否则无法识别正则表达式操作符:
shopt -s extglob

表 8.16 扩展模式匹配

正则表达式 含义
abc?(2|9)l ?匹配括号里的零个或一个字符。竖线代表 OR 条件:即 2 或 9。匹配的是 abc2l、abc9l
或 abcl
abc*([0-9]) *匹配括号里的零个或多个字符。匹配以 abc 开头,接着是零个或多个数字的模式。
比如 abc、abc1234、abc3、abc2 等等
abc+([0-9]) +匹配括号里的一个或多个字符。匹配以 abc 开头,接着是一个或多个数字的模式。
比如 abc3、abc123 等等
no@(one|ne) @匹配括号里的一项。匹配 noone 或 none
no!(thing|where) !匹配除了括号里模式的所有字符串。匹配 no、nobody 或 noone,但不匹配 nothing 或
nowhere

实例 8.52

1 $ shopt –s extglob

2 $ ls
abc abc122 f1 f3 nonsense nothing one
abc1 abc2 f2 none noone nowhere

3 $ ls abc?(1|2)
abc abc1 abc2

4 $ ls abc*([1-5])
abc abc1 abc122 abc2

5 $ ls abc+([0-5])

PDF created with pdfFactory Pro trial version www.pdffactory.com


242 第8章

abc1 abc122 abc2

6 $ ls no@(thing|ne)
none nothing

7 $ ls no!(thing)
none nonsense noone nowhere

说明
1 shopt 内置命令用于设置 glob(扩展后的 globbing)选项,以允许 bash 识别扩展模式匹配的字符
串。
2 列出当前目录下的所有文件。
3 匹配所有以 abc 开头,后面接着括号中零个或一个字符的文件名。匹配 abc、abc1 或 abc2。
4 匹配以 abc 开头,且后面跟着零个或多个 1~5 范围内数字的文件名。匹配 abc、abc1、abc122、
abc123 和 abc2。
5 匹配以 abc 开头,且后面跟着一个或多个 0~5 范围内数字的文件名。匹配 abc1、abc122、abc123
和 abc2。
6 匹配以 no 开头,后面跟着 thing 或 ne 的文件名。匹配 nothing 或 none。
7 匹配以 no 开头, 后面跟着除了 one 和 nsense 以外的字符串的文件。匹配 none、nothing 和 nowhere。
!
表示非。

8.3 变 量
变量类型。有两种类型的变量:局部变量和环境变量。局部变量仅在创建它的 Shell 中
有效,环境变量则对所有创建它的 Shell 所派生出来的子进程都有效。某些变量由用户来创
建,而另一些则是 Shell 的特殊变量。
命名规则。变量名必须以字母或下划线开始,其余部分则可以由字符、数字(0~9)或
下划线字符构成,而其他字符均可作为变量名的结束标志。名字是大小写敏感的。当给一个
变量赋值时,不要在等号两边留下空格。如果要将变量赋值为空,要在等号后直接跟一个换
行符。创建局部变量的最简单方法是以下面的格式把值赋予变量:

格式
variable=value

实例 8.53
name=Tommy

declare 内置命令。有两个内置命令 declare 和 typeset 可用于创建变量,通过选项还可


以控制设置变量的方式。typeset 命令(从 Korn Shell 而来)与 declare 命令(bash 版)完全
一样。bash 的文档中写道:“typeset 命令完全兼容 Korn Shell;但是,但是该命令并不支持
内建命令声明”11。因此,我们将使用 declare 内置命令(尽管选择 typeset 也是一样的容易) 。
没有参数的 declare 命令将列出所有设置的变量。通常只读变量是不能重新赋值或撤销

11.bash 参考手册,http://www,delorie.com/gnu/docs/bash/bashref_56.html。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 243

的。如果用 declare 命令来创建只读变量,它们不能被撤销,但可以被重新赋值。整型变量


也能用 declare 来赋值。

格式
declare variable=value

实例 8.54
declare name=Tommy

表 8.17 declare 选项

选项 含义
-f 列出函数名及其定义
-r 将变量设置为只读
-x 将变量名导出到子 Shell 中
-i 将变量置为整型
a
-a 将变量当作数组,即给元素赋值
-F 只列出函数名
a. -a 和-F 只在 bash 2.x 版本实现。

8.3.1 局部变量和范围
变量的范围是指变量在一个程序中的什么地方是可见的。对于 Shell 而言,局部变量的
的范围限于创建变量的 Shell。
当给一个变量赋值的时候,不要在等号两边留下空格。如果要将一个变量设置为空,在
等号后面跟一个换行符即可。12
变量前面的美元符号用于提取其存储的值。
局部函数可用于创建局部变量,但这些变量只能在函数内使用(见“定义函数” )。
设置局部变量。局部变量可通过将值赋予一个变量名来设置,或者使用在实例 8.55 中
所示的内置命令 declare 来设置。

实例 8.55

1 $ round=world or declare round=world


$ echo $round
world

2 $ name="Peter Piper"
$ echo $name
Peter Piper

3 $ x=

12.一个被赋值的变量,不论其值是否为空,都可以通过 set 命令显示出来,但一个被 unset 的变量,其值是无法看到的。

PDF created with pdfFactory Pro trial version www.pdffactory.com


244 第8章

$ echo $x

4 $ file.bak="$HOME/junk"
bash: file.bak=/home/jody/ellie/junk: not found

说明
1 局部变量 round 被赋值为 world。当 Shell 遇到该变量名前加一个美元符号时,即进行变量替换。
变量的值被显示在屏幕上(不要混淆提示符($)和用于变量替换的$) 。
2 局部变量 name 被赋值为"Peter Piper"。引号用于隐藏空格,以免 Shell 在分析该命令行时,把该
字符串分割成两个单词,最后显示变量的值。
3 由于局部变量 x 没有被赋值,所以它将被赋为空值。于是显示空值,即空字符串。
4 在变量名中使用点字符是非法的。变量名中的字符只能是数字、字母和下划线。在这里,Shell
将试图把该字符串当做命令来执行。

实例 8.56

1 $ echo $$
1313
2 $ round=world
$ echo $round
world
3 $ bash Start a subshell

4 $ echo $$
1326
5 $ echo $round
6 $ exit Exits this shell, returns to parent shell

7 $ echo $$
1313
8 $ echo $round
world

说明
1 双美元符号的值等于当前 Shell 的 PID,即 1313。
2 round 局部变量被赋值为 world,然后显示该变量的值。
3 启动一个新的 bash Shell,这被称为 subShell 或 child Shell。
4 这个 Shell 的 PID 为 1326,其父 Shell 的 PID 为 1313。
5 局部变量 round 在本 Shell 中没有定义,所以显示一个空行。
6 exit 命令终止本 Shell 并返回其父 Shell(Control-D 也能用来退出 Shell)

7 父 Shell 返回,并显示其 PID。
8 显示 round 变量的值,它是本 Shell 的局部变量。

实例 8.57
1 $ name=Tom
2 $ readonly name
$ echo $name
Tom

3 $ unset name
bash: unset: name: cannot unset: readonly variable
4 $ name=Joe

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 245

bash: name: readonly variable

5 $ declare –r city='Santa Clara'


6 $ unset city
bash: unset: city: cannot unset: readonly variable

7 $ declare city='San Francisco' # What happened here?


$ echo $city
San Francisco

说明
1 将局部变量 name 赋值为 Tom。
2 以只读方式创建变量。
3 无法设置只读变量。
4 不能重定义只读变量。
5 用 declare 命令将只读变量 city 赋值为 Santa Clara。当值为含空格的字符串时,必须使用引号。
6 由于变量为只读,无法设置。
7 由 declare 命令创建的只读变量无法重新设置,但可以重新赋值。

8.3.2 环境变量
环境变量是能为创建它的 Shell 及其派生子进程所用的变量,它们也经常被称为全局变
量以区分于局部变量。一般约定环境变量为大写,它们是那些可以通过内置命令 export(参
见表 8.18)导出的变量。
创建环境变量的 Shell 称为父 Shell。从 Shell 中启动的新 Shell 称为子 Shell。环境变量
被传送给任何从创建该变量的 Shell 中派生的 Shell。这些变量可以从父 Shell 传给子 Shell,
再传给孙 Shell,以此类推,但反方向则不行。即,Shell 能创建变量,但是它无法将变量传
送给其父 Shell,而只能传给它的子 Shell13。某些环境变量,如 HOME、LOGNAME、PATH
以及 SHELL,是在登录前由/bin/login 程序来设置的。通常,环境变量在用户主目录下
的.bash_profile 文件中定义。环境变量列表见表 8.19。
设置环境变量。要设置环境变量,必须在给变量赋值或设置了变量后使用 export 命令。
内置命令 built-in 加上-x 选项也可以完成同样的事情(在导出一个变量时,不要用美元符
号)。

格式

export variable=value
variable=value; export variable
declare –x variable=value

实例 8.58
export NAME=john
PS1= ‘\d:\W:$USER> ‘ ; export PS1
declare –x TERM=linux

13.就像 DNA 一样,继承是单向的,只能由父传子。

PDF created with pdfFactory Pro trial version www.pdffactory.com


246 第8章

表 8.18 export 命令及其选项

选项 值
-- 选项段的结束标志,余下的都是参数
-f name-value 形式被视为函数而不是变量
-n 将全局变量(已导出的)转换为局部变量,该变量将不会导出到子进程中
-p 显示所有的全局变量

实例 8.59

1 $ export TERM=Linux or declare –x TERM=linux


2 $ NAME="John Smith"
$ export NAME
$ echo $NAME
John Smith
3 $ echo $$
319 pid number for parent shell

4 $ bash Start a subshell


5 $ echo $$
340 pid number for new shell
6 $ echo $NAME
John Smith
7 $ declare –x NAME="April Jenner"
$ echo $NAME
April Jenner
8 $ exit
Exit the subshell and go back to parent shell
9 $ echo $$
319 pid number for parent shell
10 $ echo $NAME
John Smith

说明
1 TERM 变量被赋值为 Linux,同时被导出。现在,从本 Shell 中启动的进程可以继承该变量了。
你也可以用 declare –x 来完成同样的事情。
2 TERM 变量被定义并导出,这样由本 Shell 启动的子 Shell 就可以使用它了。
3 显示本 Shell 的 PID 值。
4 启动一个新的 bash Shell,新 Shell 被称为子,而原 Shell 为其父。
5 $$变量存有新 bash Shell 的 PID,其值显示在屏幕上。
6 在父 Shell 中设置的变量被导出并显示在子 Shell 中。
7 内置 declare 函数是设置变量的另一方法。declare 能通过用-x 选项导出变量。变量被重新设置为
April Jenner,并导出给所有子 Shell,但不会影响到其父 Shell。导出变量不会向上传递给父 Shell。
8 退出子 bash Shell。
9 再次显示父 Shell 的 PID。
10 NAME 变量仍是原值。变量在从父 Shell 导出到子 Shell 后保持原来的值。子进程无法改变父进
程变量的值。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 247

表 8.19 bash 环境变量(用*标注的变量名仅限于 bash 2.x 版本)

变量名 含义
_(下划线) 上一条命令的最后一个参数
BASH 表示调用 bash 的实例的完整路径名
BASH_ENV* 同 ENV,但只能在 bash 2.0 以上版本设置
BASH_VERSION 表示 bash 实例的版本号
BASH_VERSINFO* 如果 bash 的版本号为 2.0 以上,表示版本信息
CDPATH cd 命令的搜索路径。这是一个用分号来分割的目录列表,cd 命令通过搜索这些
指定目录来查找目标。这是一个简单的例子:.:~:/usr
COLUMNS 该变量定义了 Shell 编辑模式下的编辑窗口和命令选择的宽度
DISTRACT* 当前目录栈的内容(适用于 bash 2.0 及以上版本)
EDITOR 内置编辑命令的路径名:emacs、gmacs 或 vi
EUID 扩展当前用户的有效用户 ID,由 shell 在启动时初始化
ENV 在启动一个新的 bash Shell 时执行的环境文件名(含脚本)。通常情况下,赋予
该变量的文件名为.bashrc。ENV 的值在被解释为路径名前服从于参数扩展,命令
替换和算术扩展
FCEDIT fc 命令的默认编辑器的名字
FIGNORE 在文件扩展中将被忽略的后缀列表用冒号分隔保存在这个变量中
GLOBIGNORE* 在文件扩展中将被忽略的文件列表
GROUPS 保存当前用户组的数组
HISTCMD 历史个数或者历史清单索引
HISTCONTROL 如果 ignorespace 被设置,任何以空格开头的命令都不会出现在历史记录中;如
果 ignoredups 被设置,与最后一个历史项相同的行不会出现在历史记录中。
Ignoreboth 如果被设置,则以上两项同时有效
HISTFILE 指定保存历史记录的文件,默认为~/.bash_history,如果被 unset,就不保存记录
HISTFILESIZE 历史文件的最大行数,一旦被赋值,历史文件的尺寸就确定,默认值是 500
HISTSIZE 历史记录文件中最多的记录数,默认值是 500
HOME 主目录,在未指定目录时被 cd 引用
HOSTFILE 在 Shell 主机名自动完成时参考的文件。这个文件的格式与/etc/hosts 一样,该文
件可用交互式修改,并在下一次主机名完成时,bash 会尝试读入更新
HOSTTYPE 自动设置 bash 所在主机的类型。默认为系统依赖型(system_dependent)
IFS 内部的域分隔符,通常是空格、制表符或换行符,用于命令替换后的域分隔,循
环结构或读取输入
IGNOREEOF 控制输入中出现 EOF 符号的时候 Shell 的动作。这个值是 Shell 在退出以前将接
收到的以 EOF 符号开头的行数。如果这个变量存在但是没有被设置,默认值是
10。如果不存在这个变量,那么一旦出现 EOF 符号,输入就结束了。这个变量
只在交互模式下有效
INPUTRC 保存 readline 启动文件的名字,覆盖默认值~/.inputrc
LANG* 用于决定那些没有选择以 LC 开头的变量特殊的类,使用什么本地术语集
LC_ALL 覆盖 LANG 和其他 LC_变量的值
LC_COLLATE* 决定文件名扩展的整理顺序、范围表达式和等式的动作,以及匹配路径名的整理
顺序

PDF created with pdfFactory Pro trial version www.pdffactory.com


248 第8章

续表
变量名 含义
LC_MESSSAGES* 用来决定翻译以$开头的双引号内的字符串用什么本地术语集
LINENO 每次参考这个变量,Shell 就在脚本或者函数内把所有的行用从 1 开始的连续整
数编号
MACHTYPE* 包含一个描述 Shell 所在的系统的字符串
MAIL 如果这个变量被设置为一个 mail 文件的名字,
但是变量 MAILPATH 却没有设置,
系统就通知用户在这个指定的文件中有新的邮件到了
MAILCHECK 这个变量决定检查邮件的频率,默认是 600 秒,如果设为 0,将在每次主提示符
出现前检查邮件
MAILPATH 一个用冒号分隔的文件名列表。一旦这个变量被设置,Shell 就会通知用户其中
的哪一个文件中有新的邮件到达
MAIL_WARNING 如果被设置, 一旦 Shell 发现邮件被访问过就打印“The mail in [filename where mail
is stored] has been read”
OLDPWD 最后的工作目录
OPTARG 内建命令 getopts 最后处理的参数
OPTERR 如果设置为 1,显示 getopts 的错误信息
OPTIND Getopts 需要处理的下一个参数的索引
OSTYPE 自动设置的,描述当前操作系统的字符串
PATH 命令搜索路径,彼此之间用冒号分隔,例如/usr/gnu/bin:/usr/local/bin:
PIPESTATUS 包含前台作业的管道中的进程的退出状态值的数组
PROMPT_COMMAND 这个变量中保存的命令在提示符出现以前执行
PPID 父进程的 PID
PS1 主提示符,默认$
PS2 次要提示符,默认为>
PS3 select 命令的选择提示符的字符串,默认为#?
PS4 在跟踪功能被打开时的调试提示符,默认为+。Set-x 可以打开跟踪功能
PWD 当前工作目录,用 cd 命令设置
RANDOM 用来产生一个随机整数。如果 unset 该变量,就将失去随机数特性
REPLY 当 read 没有参数时,这个变量被设置
SECONDS 这个变量负责记录 Shell 启动到现在的秒数。如果这个变量被赋值,那么再次访
问的时候,返回的是赋值到现在的秒数加赋给变量的数字。如果这个变量被清空
过,那么它的特性就消失了
SHELL Shell 一启动就寻找这个名字的环境变量并给变量 PATH、PS1、 PS2、
MAILCHECK
和 IFS 赋默认值。HOME 和 MAIL 由 login(1)设置
SHELLOPTS 包括打开的 Shell 选项的列表,例如 brace expard,hashall,monitor 等
SHLVL Shell 每启动一次就累计增加一个 1
TMOUT 控制在退出以前等待输入的秒数
FORMAT 用于控制在命令管道中 time 翻译时间单词的格式
UID 在 Shell 启动的时候初始化用户的 ID

清空变量。如果未被设置为只读属性的话,本地变量和环境变量都可以通过使用 unset
命令清空。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 249

实例 8.60
unset name; unset TERM

说明
unset 命令把变量从 shell 内存中删除

打印变量的值:echo 命令。内建命令 echo 的作用是将其参数打印到标准输出。-e 选项使


得 echo 命令可以无限制地使用转义序列控制输出的效果。表 8.20 给出了 echo 命令的选项和
转义序列。

表 8.20 echo 命令选项和转义序列

选项 解释
-e 允许翻译如下所有的转义序列
-n 把换行符压缩到输出行的末尾
-Ea 关闭对转义符号的翻译,包括关闭对那些默认情况下翻译的转义符号的
翻译
转义序列
\aa 警告(铃声)
\b 退格
\c 不换行打印
\f 填表
\n 换行
\r 返回
\t 制表符
\v 垂直制表符
\\ 反斜杠
\nnn ASCII 代码为 nnn 的符号
a. 在 bash 2.x 以前的版本中不能运行。

在使用转义序列的时候,必须使用-e 开关。

实例 8.61

1 $ echo The username is $LOGNAME.


The username is ellie.
2 $ echo –e "\t\tHello there\c"
Hello there$
3 $ echo –n "Hello there"
Hello there$

说明
1 echo 命令将其参数打印到屏幕上。在 echo 命令执行以前 Shell 先做了变量替换。

PDF created with pdfFactory Pro trial version www.pdffactory.com


250 第8章

2 echo 命令跟 C 语言一样支持转义序列,$在这里表示提示符。


3 当-n 选项打开,打印行而不换行。这个版本的 echo 不支持转义序列。

printf 命令。GNU 版本的 printf14 命令可以用来格式化输出,其作用是打印格式化的字符


串,效果类似 C 语言的 printf 函数。格式包括字符串本身和描述打印效果的字符。定义格式
的方法是在%后面跟一个说明符,例如%f 表示后面是一个浮点数,而%d 表示一个整数。
要了解 printf 函数说明符的完整用法,可以在命令行上输入 printf–help。想要了解 printf
函数的版本,可以在命令行上输出 printf –version。如果你使用的是 bash 2.x 版本,内建的
printf 命令跟.usr/bin 下的命令 printf 的使用方法是一样的。

格式
Printf format [argument...]

实例 8.62
printf "%10.2f%5d\n" 10.5 25

表 8.21 printf 命令的格式说明符

格式说明符 解 释
\" 双引号
\0NNN 八进制数,N 表示 0~3 之间的数字
\\ 反斜杠
\a 警告(铃声)
\b 退格
\c 不再做后面的输出
\f 填表
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\xNNN 十六进制数,N 表示 1~3 之间的数字
%% 单%
%b 对带有“\”参数的字符串参数做转义解释

实例 8.63

1 $ printf --version
printf (GNU sh-utils) 1.16

2 $ type printf

14.在 bash 2.x 版本中 printf 是内建命令。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 251

printf is a shell builtin

3 $ printf "The number is %.2f\n" 100


The number is 100.00

4 $ printf "%-20s%-15s%10.2f\n" "Jody" "Savage" 28


Jody Savege 28.00

5 $ printf "|%-20s|%-15s|%10.2f|\n" "Jody" "Savage" 28


Jody /Savage / 28.00/

6 $ printf "%s's average was %.1f%%.\n" "Jody" $(( (80+70+90)/3 ))


Jody's average was 80.0%

说明
1 打印 Gnu 版本的 printf 命令的版本。这不是内建命令,但是可以在/usr/bin 下找到。
2 如果使用 bash 2.x 版本,那么 printf 就是一个内建命令。
3 把参数 100 作为一个小数点后面保留 2 位的浮点数打印。格式说明符%.2f 表示的就是小数点后面
保留 2 位。注意,它跟 C 语言不同,其参数之间不需要逗号分隔。
4 在这行中有三个格式说明符起作用:一个是%-20s(表示一个左对齐,20 个字符的字符串),下
一个是%-15s(表示一个左对齐,15 个字符的字符串) ,最后一个是%10.2f(表示右对齐,10 个
字符长度的浮点数,其中一个字符是小数点,小数点后面有两位) 。每一个参数都按照顺序被相
应地格式说明符格式化。所以“Jody”被第一个格式说明符格式化,“Savage”被第二个格式说
明符格式化,数字 28 被第三个格式说明符格式化。
5 这行跟第四行是一样的,只是多了一个垂线用来显示左对齐与右对齐有什么不同。
6 用 printf 命令格式化输出 Jody 字符串和数学表达式的结果。

变量扩展修改符(参量扩展)。通过特定的修改符,可以检验和修改变量。这些修改符
提供了一个快捷的方法来检验变量是不是被设置过,并把输出结果输出到一个变量中。请参
考表 8.22。
表 8.22 变量修改符

修改符 解 释
${variable:-word} 如果变量被设置了而且非空(null)就替代变量的值,否则就替代 word
${variable:=word} 如果变量被设置了而且非空就替代变量的值,否则就把变量永久设置为 word
${variable:+word} 如果变量被设置了而且非空就替代 word,否则什么也不做
${variable:?word} 如果变量被设置了而且非空就替代变量的值,否则就打印 word,然后退出
Shell。如果 word 为空就打印“parameter null or not set”
${variable:offset} a 从 offset 位置开始提取变量的值的子字符串,如果 offset 为 0 就取整个字符串
${variable:offset:length} 从变量值的 offset 位置开始提取长度为 length 的子字符串
a. 在 bash 2.x 以前版本中不能运行。

使用冒号和修改符来检验变量是否被设置,是否为空。若没有冒号,即便设置为 null
的变量也被认为是已被设置过值。

实例 8.64

(Substitute Temporary Default Values)

PDF created with pdfFactory Pro trial version www.pdffactory.com


252 第8章

1 $ fruit=peach
2 $ echo ${fruit:-plum}
peach

3 $ echo ${newfruit:-apple}
apple
4 $ echo $newfruit

5 $ echo $EDITOR # More realistic example

6 $ echo ${EDITOR:-/bin/vi}
/bin/vi
7 $ echo $EDITOR

8 $ name=
$ echo ${name-Joe}
9 $ echo ${name:-Joe}
Joe

说明
1 变量 fruit 被赋值为 peach。
2 用特定的修改符检验变量 fruit 是否被设置过,如果设置过就打印设置的值,否则用 plum 替换变
量 fruit 中的值,并打印变量。
3 新变量 fruit 没有被设置过。apple 将被临时作为变量 newfruit 的值。
4 第三行的设置只是临时的,因此变量 newfruit 还是没有被设置过。
5 环境变量 EDITOR 没有被设置过。
6 修改符“-”用/bin/vi 替换变量 EDITOR 的值。
7 因为 EDITOR 没有被设置,因此打印结果是空。
8 变量 name 被设置为 null,而且修改符前没有冒号,因此即使变量被设置为 null,也被认为是设
置过的,因此 Joe 没有被赋值给变量 name。
9 冒号的作用是检验变量是否被设置为 null,两种情况下 Joe 都会替换变量 name 的值。

实例 8.65

(Substitute Permanent Default Values)


1 $ name=

2 $ echo ${name:=Peter}
Peter

3 $ echo $name
Patty

4 $ echo ${EDITOR:=/bin/vi}
/bin/vi

5 $ echo $EDITOR
/bin/vi

说明
1 变量 name 被赋值为 null。
2 特定的修改符:=将检验变量 name 是否被设置过, 变量将被赋值为等号右边的值。因为变量是 null,
因此 Peter 被赋值给变量 name。这个设置是永久的。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 253

3 变量 name 的值还是 Peter。


4 变量 EDITOR 的值被设置为/bin/vi。
5 显示变量 EDITOR 的值。

实例 8.66

(Substitute Temporary Alternate value)


1 $ foo=grapes

2 $ echo ${foo:+pears}
pears

3 $ echo $foo
grapes
$

说明
1 变量 foo 被赋值为 grapes。
2 特定修改符:+将检验变量是否被设置。如果被设置,那么 pears 将临时替换变量中的值;否则就
返回 null。
3 变量 foo 保持原来的值。

实例 8.67

(creating Error Messages Based on Default Values


1 $ echo ${namex:?"namex is undefined"}
namex: namex is undefined

2 $ echo ${y?}
y: parameter null or not set

说明
1 修改符:? 将检验变量是否被设置。如果没有被设置,问号右边的字符串将在变量名称后面,被
打印到标准错误中。如果这行代码出现在脚本中,脚本就会退出。
2 如果问号后面没有信息,shell 就在标准错误中打印默认信息。

实例 8.68

(Creating Substringa)

1 $ var=notebook

2 $ echo ${var:0:4}
note

3 $ echo ${var:4:4}
book

4 $ echo ${var:0:2}
no

PDF created with pdfFactory Pro trial version www.pdffactory.com


254 第8章

说明
1 变量被赋值为 notebook。
2 变量 var 的子字符串,offset 是 0,也就是从 n 开始,字符串长度是 4 也就是以 e 结束。
3 变量 var 的子字符串,offset 是 4,也就是从 b 开始,字符串长度是 4 也就是以 k 结束。
4 变量 var 的子字符串,offset 是 0,也就是从 n 开始,字符串长度是 2 也就是以 o 结束。

a. 在 bash 2.x 以前的版本中不能运行。

子字符串的变量扩展。模式匹配参数用来从字符串的前边或者后边,去掉特定的部分字
符串。最常用的方法就是从路径中去点路径名。参考表 8.23。
a
表 8.23 变量扩展字符串

表达式 功能
${variable%pattern} 变量的值与模式符合 smallest trailing portion 就删除它
${variable%%pattern} 变量的值与模式符合 largest trailing portion 就删除它
${variable#pattern} 变量的值与模式符合 smallest leading portion 就删除它
${variable##pattern} 变量的值与模式符合 largest leading portion 就删除它
${#variable} 替换变量中字母的个数。如果*或者@,长度就是位置参量的个数
a. 在 bash 2.x 以前版本中不能运行。

实例 8.69

1 $ pathname="/usr/bin/local/bin"
2 $ echo ${pathname%/bin*}
/usr/bin/local

说明
1 本地变量 pathname 被赋值为/usr/bin/local/bin。
2 %删除变量 pathname 中匹配模式/bin 的 smallest trailing portion,也就是删除/bin。

实例 8.70

1 $ pathname="usr/bin/local/bin"
2 $ echo ${pathname%%/bin*}
/usr

说明
1 本地变量 pathname 被赋值为/usr/bin/local/bin。
2 %%删除变量 pathname 中匹配模式/bin 的 largest trailing portion,也就是删除/bin/local/bin。

实例 8.71

1 $ pathname=/home/lilliput/jake/.bashrc
2 $ echo ${pathname#/home}
/lilliput/jake/.bashrc

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 255

说明
1 本地变量 pathname 被赋值为/home/lilliput/jake/.bashrc。
2 #删除变量 pathname 中匹配模式/home 的 smallest leading portion,也就是删除开头的/home。

实例 8.72
1 $ pathname=/home/liliput/jake/.bashrc
2 $ echo ${pathname##*/}
.bashrc

说明
1 本地变量 pathname 被赋值为/home/liliput/jake/.bashrc。
2 ##删除变量 pathname 中匹配模式/home 的 largest leading portion,也就是删除/home/lilliput/jake。

实例 8.73

1 $ name="Ebenezer Scrooge"
2 $ echo ${#name}
16

说明
1 变量 name 被赋值为 Ebenezer Scrooge。
2 ${#variable}语句显示赋值给变量 name 的字符串的字母个数,这里共有 16 个字母。

位置参量。通常情况下,特定的内建变量,被称为位置参量,它们被用于从命令行向脚
本传递参数,或者在函数中用于保存传递给函数的参数。这些变量被称作位置参量是因为它
们以数字 1、2、3……区分,这些数字与它们在参量清单中的位置有对应关系。参考表 8.24。
Shell 脚本的名字保存在变量$0 中,位置参量可以被 set 命令设置、重置和清空。
表 8.24 位置参量

表达式 功能
$0 当前脚本的名字
$1-9 位置参量 1~9
${10} 位置参量 10
$# 位置参量的个数
$* 向所有的位置参量赋值
$@ 同$*,有双引号时除外
“$*” 赋值到“$1 $2 $3”等等
“$@” 赋值到“$1”“$2”“$3”等等

实例 8.74

1 $ set punky tommy bert jody

PDF created with pdfFactory Pro trial version www.pdffactory.com


256 第8章

$ echo $* Prints all the positional parameters


punky tommy bert jody

2 $ echo $1 Prints the first position


punky

3 $ echo $2 $3 Prints the second and third position


tommy bert

4 $ echo $# Prints the total number of positional


4 parameters

5 $ set a b c d e f g h i j k l m
$ print $10 Prints the first positional parameter
a0 followed by a 0.

$ echo ${10} ${11} Prints the 10th and 11th positions


j k

6 $ echo $#
13

7 $ echo $*
a b c d e f g h i j k l m

8 $ set file1 file2 file3


$ echo \$$#
$3

9 $ eval echo \$$#


file3

10 $ set -- Unsets all positional parameters

说明
1 向所有的位置参量赋值,$*表示所有的位置参量。
2 显示第一个位置参量的值,punky。
3 显示第二个和第三个位置参量的值,tommy 和 bert。
4 $# 包括位置参量的个数。
5 用 set 命令重新设置所有的位置参量。原来的位置参量的值都被清除。打印所有超过 9 的位置参
量的时候,用大括号把两位数字括在一起。否则系统就会把$和第一个数字作为位置参量打印,
把第二个数字追加在它们的后面。
6 位置参量的个数是 13 个。
7 打印所有位置参量的值。
8 美元符号被转义$#表示参量的个数,echo 命令显示$3 变量。
9 eval 命令把命令行在执行以前做了再次分析。第一次 Shell 打印$3,
第二次在 eval 以后,打印$3 的
值,file3。
10 set 命令选项把所有的位置参量清空。

其他特殊变量。Shell 有一些由单个字符组成的变量,在这些变量前加上$后就能访问这
些变量。见表 8.25。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 257

表 8.25 特殊变量

变量 含义
$ Shell 的 PID
- 当前 sh 的选项
? 最后一个命令的退出状态值
! 最后一个放入后台作业的 PID 值

实例 8.75

1 $ echo The pid of this shell is $$


The pid of this shell is 4725

2 $ echo The options for this shell are $-


The options for this shell are imh

3 $ grep dodo /etc/passwd


$ echo $?
1

4 $ sleep 25&
4736
$ echo $!
4736

说明
1 $包含当前进程的 PID 值。
2 -变量列出了交互式 bash 的所有选项。
3 用 grep 在/etc/passwd 文件中查找 dodo。?变量包含最后一个命令的退出状态值。grep 的返回值
是 1,表示 grep 执行失败。退出状态值 0 表示成功。
4 !变量包含最后一个被放到后台作业的 PID 号码。&符号把 sleep 放到后台执行。

8.3.3 引用
引用可以保护特殊的元字符不被翻译,防止参数扩展。有三种引用的防法:反斜线、单
引号和双引号。表 8.26 中是那些对于 Shell 来说比较特殊的元字符,需要被引用。

表 8.26 需要引用的特殊元字符

元字符 含义
; 命令分隔符
& 后台处理
() 命令组,创建一个子 Shell
{} 命令组,但是不创建子 Shell
| 管道
< 输入重新定向

PDF created with pdfFactory Pro trial version www.pdffactory.com


258 第8章

续表
元字符 含义
> 输出重新定向
Newline 命令终止
Space/tab 单词分隔符
$ 变量替换
*[]? 用于文件名扩展的 Shell 的通配符

单引号或者双引号必须成对使用。单引号可以保护一些特殊的元字符不被翻译,例如$、
*、?、>和<。双引号也可以保护一些元字符不被翻译,但是允许变量和命令替换。单引号可
以保护双引号,双引号也可以保护单引号。
跟 Bourne Shell 不同,bash 尽量让你知道是不是丢失了引号。在交互状态下,如果丢失
了引号就会出现次要提示符,进而在脚本中检查整个文件,看看是不是缺少引号。如果出现
引号不匹配的情况,Shell 就会尝试用下一个合法的引号来匹配它。如果下一个合法的引号
不能匹配该引号,脚本就会终止。引用可能是程序员最大的敌人之一,请参考附录 C 中的
Shell 引用规则。
反斜线。反斜线用来保护一个字母不被翻译。如果把反斜线放在一对引号中,它就不被
翻译。反斜线可以保护美元符号、反引号和双引号中的反斜线。

实例 8.76

1 $ echo where are you going\?


Where are you going?

2 $ echo Start on this line and \


> go to the next line.
Start on this line and go to the next line.

3 $ echo \\
\

4 $ echo '\\'
\\

5 $ echo '\$5.00'
\$5.00

6 $ echo "\$5.00"
$5.00

7 $ echo 'Don\'t you need $5.00?'


>
>’

Don\t you need .00?

说明
1 反斜线防止 Shell 对问号做文件名替换。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 259

2 反斜线使得换行符被忽略,下一行可以作为这一行的一部分来理解。
3 因为反斜线是一个图书字符。因此它可以防止自己后面的反斜线被翻译。
4 单引号中的反斜线不被翻译。
5 所有在单引号中的字母都被看成没有特殊意义的字母本身。反斜线在这里没有任何特殊的含义。
6 在双引号中,反斜线可以防止美元符号被用做变量替换。
7 由于单引号中的反斜线不被翻译,所以 Shell 看到的是三个单引号。次要提示符出现。它停止所
有引用并把这些字符放在 echo 命令中。因为前两个单引号匹配,所以其他的部分就没有被引用。
Shell 就尝试给$5 赋值。又因为它是空的,最终打印.00

单引号。单引号必须成对使用,它可以保护所有的字符不被翻译。要打印一个单引号就
必须使用双引号或者反斜线来引用它。

实例 8.77

1 $ echo 'hi there


> how are you?
> When will this end?
> When the quote is matched
> oh'
hi there
how are you?
When will this end?
When the quote is matched
Oh

2 $ echo Don\'t you need '$5.00?'


Don't you need $5.00?

3 $ echo 'Mother yelled, "Time to eat!"'


Mother yelled, "Time to eat!"

说明
1 在本行中,单引号没有成对出现,于是出现了次要提示符,等待引号匹配。
2 单引号可以保护所有的字符不被翻译。“Don’t”中的省略符号借助反斜线忽略。否则它就会与
美元符号前面的那个单引号匹配,这样,字符串最后的那个单引号就会无法匹配。问号和美元符
号在一对单引号中,因此 Shell 不会尝试去翻译它们。
3 在这个字符串中,单引号保护双引号。

双引号。双引号必须成对出现,它们允许变量和命令替换,但是保护其他符号不被翻译。

实例 8.78

1 $ name=Jody

2 $ echo "HI $name, I'm glad to meent you!"


Hi Jody, I'm glad to meet you!

3 $ echo "Hey $name, the time is $(date)"


Hey Jody, the time is Web Jul 14 14:04:11 PST 2000

说明
1 变量 name 被赋值为 Joby。

PDF created with pdfFactory Pro trial version www.pdffactory.com


260 第8章

2 字符串中间的双引号保护所有的特殊符号都不被翻译,除了 name 前面的美元符号。在双引号间


的字符串中,变量替换过程不受双引号影响。
3 在这个双引号引用起来的字符串中,变量替换和命令替换同时发生。变量 name 被解释,圆括号
中的命令 date 被执行。

8.3.4 命令替换
在需要把命令的输出结果赋值给一个变量或者需要用字符串替换变量的输出结果时,我
们可以使用变量替换。所有的 Shell 都使用反引用的方法进行命令替换。bash 有两种命令替
换的方法:一种是老的方法把命令放在反引号中,另外一种是新的 Korn 风格,把命令放在
一对圆括号中见,并在前面缀上一个美元符号。
bash 做命令替换的方法是执行命令,并把其执行结果返回到标准输出。在传统的命令替
换方式下,反斜线除了在美元符号、单引号和斜线后面的情况以外,还都保持着自己的字面
意义。在 Korn 风格的方式下,括号中间的所有字符都被当作命令来看待。
命令替换是可以嵌套的,在使用的嵌套命令替换时,如果使用的是旧的风格,在内部反
引用前必须使用反斜线转义。

格式

‘Linux command’ Old method with back quotes

$(Linux command) New method

实例 8.79

(Old Way)
1 $ echo "The hour is ‘data +%H’"
The hour is 09

2 $ name=’awk –F: '{print $1}' database’


$ echo $name
Ebenezer Scrooge

3 $ ls ‘ls /etc’
shutdown

4 $ set ‘date’
5 $ echo $*
Web Jul 14 09:35:21 PDT 1999
6 $ echo $2 $6
Jul 1999

7 $ echo ‘basename \’pwd\’’


ellie

说明
1 data 命令的输出被替换到字符串中。
2 awk 命令的输出被赋值给变量 name,并显示出来。
3 在反引号中的,ls 命令的输出是/etc 目录下的文件清单。这些文件名将做为 ls 命令的参数。在/etc

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 261

目录下有同样名字的文件被列出来。
4 set 命令把 date 命令的输出结果赋值给位置参量。空格把相应的参量分隔开来。
5 $*表示所有的位置参量。date 命令的输出被保存在变量$*中。参量之间以空格分隔。
6 打印第二个和第六个参量。
7 把变量 dirname 设置为当前工作目录,使用命令替换嵌套。首先执行 pwd 命令,把当前目录的完
整路径传递给 Linux 命令 basename 作为参数。basename 命令把参数中除了最后一部分以外的所
有部分都剥离出去。在使用嵌套命令替换时,如果使用的是旧的风格,在内部反引用前必须使用
反斜线逃逸。

实例 8.80

(The New Way)


1 $ d=$(date)
$ echo $d
Web Jul 14 09:35:21 PDT 1999

2 $ lines = $(cat filex)

3 $ echo The time is $(data +%H)


The time is 09

4 $ machine=$(uname –n)
$ echo $machine
jody

5 $ pwd
/usr/local/bin
$ dirname="$(basename $ (pwd))" Nesting commands
$ echo $dirname
bin

6 $ echo $(cal) Newlines are lost


July 1999 S M Tu W Th F S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

7 $ echo "$(cal)"
July 1999
S M Tu W Th F S
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

说明
1 date 命令被放在一对括号中,命令的输出先被替换到一个表达式里,然后再赋值给变量 d,最终
显示出来。
2 cat 命令的输出被赋值给变量 lines。
3 再次把 date 命令放在一对括号中,date +%H 输出的结果是当前的小时,它被替换到一个表达式
中并显示在屏幕上。
4 变量 machine 保存的是命令 uname –n 的输出结果,也就是当前主机的名字。变量 machine 的值
被打印到屏幕上。
5 pwd 命令的输出结果是/usr/local/bin。嵌套使用命令替换以后的输出结果保存在变量dirname 当中。
$(pwd)是首先执行命令替换的。然后这个替换结果就被放入表达式中。命令 basename 利用替换

PDF created with pdfFactory Pro trial version www.pdffactory.com


262 第8章

后的表达式作为参数运行,结果是 basename /usr/local。


6 打印命令 cal 的输出结果到屏幕上。当命令替换的时候,字符串末尾的换行符被检测到。
7 当你把整个命令替换表达式放到双引号中,末尾的换行符应被保留,这样整个日历看起来才比较
规整。

8.3.5 数学扩展
Shell 通过运算数学表达式和替换结果来进行数学扩展。在没有双引号和表达式嵌套的
情况下,表达式可以被直接处理。关于数学表达式的细节请参考“let 命令”。
有两种计算数学表达式的格式。

格式

$[ expression ]
$(( expression ))

实例 8.81

echo $[ 5 + 4 – 2 ]
7
echo $[ 5 + 3 * 2]
11
echo $[ (5 + 3) * 2]
16
echo $(( 5 + 4 ))
9
echo $(( 5 / 0 ))
bash: 5/0: division by 0 ( error token is "0")

8.3.6 扩展的顺序
当你进行变量扩展、命令扩展、数学表达式扩展以及路径扩展的时候,Shell 是遵循一
定顺序的。假设变量没有被引用,那么 Shell 的处理顺序应该如下所示:
1.大括号扩展
2.Tilde 扩展
3.参数扩展
4.变量替换
5.命令替换
6.数学扩展
7.单词分割
8.路径扩展

8.3.7 数组(版本 2.x)


bash 2.x 版本提供了创建一维数组的能力。数组允许你把一串数字、一串名字或者一串
文件放在一个变量中。使用内建功能 declare –a 可以创建数组,或者在变量后面增加脚标直

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 263

接创建数组如 x[0]=5,数组的脚标是从 0 开始的整数。数组的尺寸没有限制,脚标也不必须


是一定顺序的数字。获取数组中某个元素的语法是${arrayname[index]}。如果 declare 命令后
面有-a 和-r 选项,那么就创建只读数组。

格式

declare –a variable_name
variable = ( item1 item2 item3 ... )

实例 8.82
declare –a nums=(45 33 100 65)
declare –ar names (array is readonly)
names=( Ton Dick Harry)
states=( ME [3]=CA CT )
x[0]=55
n[4]=100

向数组赋值的时候,脚标自动从 0 开始,每次增加 1。在每次赋值时,不一定要提供多


个值,也不需要按照顺序提供。清空一个数组,使用 unset 命令后面跟数组变量的名字。清
空一个元素则使用 unset 命令后面是 arrayname[subscript]。
declare、local 和 read-only 命令都可以使用-a 选项创建数组。read 命令通过-a 选项从标
准输入读入一组单词赋值给数组的元素。

实例 8.83

1 $ declare –a friends

2 $ friends=(Shery1 Peter Louise)

3 $ echo ${friends[0]}
shery1

4 $ echo ${friends[1]}
Peter

5 $ echo ${friends[2]}
Louise

6 $ echo "All the friends are ${friends[*]}"


All the friends are Shery1 Peter Louise

7 $ echo "The number of elements in the array is ${#friends[*]}"


The number of elements in the array is 3

8 $ unset friends or unset ${friends[*]}

说明
1 内建命令 declare 创建一个数组,但是这个步骤并不是必须的。任何变量如果使用脚标的话都可
以自动成为数组。
2 数组 friend 被赋值为一串值:Sheryl、Peter 和 Louise。
3 通过数组名和脚标,访问数组 friends 的第一个元素,打印第一个元素到屏幕。
4 用索引 1 访问第二个元素,打印第二个元素到屏幕。

PDF created with pdfFactory Pro trial version www.pdffactory.com


264 第8章

5 用索引 2 访问第三个元素,打印第三个元素到屏幕。
6 星号表示所有的元素,所以全部元素都被打印。
7 根据语法${#friend[*]}表示数组的尺寸,即元素个数,${#friend[0]}表示第一个元素的长度。
8 内建命令 unset 清空整个数组,要删除单个元素可以用 unset friend[1]。

实例 8.84

1 $ x[3]=100
$ echo ${x[*]}
100

2 $ echo ${x[0]}

3 $ echo ${x[3]}
100

4 $ states=(ME [3]=CA [2]=CT)


$ echo ${states[*]}
ME CA CT
5 $ echo ${states[0]}
ME
6 $ echo ${states[1]}

7 $ echo ${states[2]}
CT

8 $ echo ${states[3]}
CA

说明
1 数组 x 的第三个元素被赋值为 100。如果脚标为 3 也不会有任何问题,但是由于前两个元素都不
存在,所以数组的大小仍为 1。${x[*]}显示数组 x 中的那一个元素。
2 x[0]、x[1]和 x[2]都没有值。
3 x[3]的值为 100。
4 数组 states 中索引为 0 的元素被赋值为 ME,索引为 3 的元素被赋值为 CA,索引为 2 的元素被赋
值为 CT。从这个例子中你可以看到,Shell 并不关注脚标如何设置,是否按照顺序等问题。
5 打印第一个元素到屏幕。
6 states[1]中没有存储任何数据。
7 states 数组的第三个元素,states[2]被赋值为 CT。
8 states 数组的第四个元素,states[3]被赋值为 CA。

8.3.8 函数(介绍)
当前 Shell 中有一组组织在一起并被命名的命令叫作 bash 函数。它们看起来像脚本,但
是效率更高。一旦被定义,函数就成为 Shell 内存中的一部分,可以被调用,而不必从文件
中读取该段代码。函数通常在脚本的模块化书写风格中被使用。一旦被定义,函数就可以被
反复使用。虽然在交互的方式下函数可以在提示符下定义,但是最多的还是在用户的初始化
函数中被定义,bash_profile。在引用前,函数是必须被定义的。
定义函数。有两种方式可以声明函数。一种是旧的 Bourne Shell 的方式,函数名后面是

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 265

一对空括号,然后是函数的定义。新的方式是使用关键字 function,后面是函数名和函数定
义。如果使用新的方式,那么括号就变成可选的部分了。函数定义被放在一对大括号中间。
它包括一些用分号分隔的命令。最后一个命令后面也需要分号。大括号前后需要保留一些空
间。所有需要向函数传递的参数都被当作位置参量来处理。位置参量在函数中是本地变量。
内建的 local 命令允许你在函数内部创建本地变量。函数是可以递归的,例如不断地引用自
己直到一定的次数。

格式

function_name () { commands ; commands; }


function function_name { commands ; commands; }
function function_name () { commands ; commands }

实例 8.85

1 $ function greet { echo "Hello $LOGNAME, today is $(date)"; }


2 $ greet
Hello ellie, today is Wed Jul 14 14:56:31 PDT 1999
3 $ greet () { echo "Hello $LOGNAME, today is $(date)"; }
4 $ greet
Hello ellie, today is Wed Jul 14 15:16:22 PDT 1999
5 $ declare -f
declare –f greet()
{
echo "Hello $LOGNAME, today is $(date)"
}
6 $ declare –Fa
declare –f greet
7 $ export –f greet
8 $ bash Start subshell
9 $ greet
Hello ellie, today is Wed Jul 14 17:59:24 PDT 1999

说明
1 关键字 function 后面跟的是函数名字 greet。大括号里面是函数的定义,左边括号的后面必须有至
少一个空格。即使这个函数只有一行,语句后面也需要用分号结束。
2 当函数 greet 被调用,括号中的命令就被执行。
3 使用 Bourne Shell 的语法再次定义 greet 函数,首先是函数名,然后是一对括号,紧跟的花括号
内是函数的定义。
4 再次调用 greet 函数。
5 使用 declare –f 命令列表显示当前 Shell 中所有被定义的函数。
6 使用 declare –F 命令只列表显示当前 Shell 中所有被定义的函数的名字。
7 expor–f 命令把函数变成全局函数,允许所有子 Shell 调用。
8 启动一个新的 bash Shell。
9 函数可以被子 Shell 调用,因为这个函数已经被输出过。

a. 只在 bash 2.x 版本中有效。

PDF created with pdfFactory Pro trial version www.pdffactory.com


266 第8章

实例 8.86

1 $ function fun {
echo "The current working directory is $PWD."
echo "Here is a list of your files: "
ls
echo "Today is $(date +%A).";
}
2 $ fun
The current working directory is /home.
Here is a list of your files:
abc abc123 file1.bak none nothing tmp
abc1 abc2 file2 nonsense nowhere touch
abc122 file1 file2.bak noone one
Today is Wednesday.

3 $ function welcome { echo "Hi $1 and $2"; }


4 $ welcome tom joe
Hi tom and joe

5 $ set jane anna lizzy


6 $ echo $*
jane anna lizzy

7 $ welcome johan joe


hi johan and joe

8 $ echo $1 $2
johan joe

9 $ unset –f welcome # unsets the function

说明
1 定义函数 fun。关键字 function 后是函数的名字,然后是括号,括号中是函数的定义。括号中的
命令写在不同的行中,括号中的命令如果在同一行就需要分号分隔。左半边括号后面需要至少一
个空格。函数在使用前必须定义。
2 函数被调用时的动作就像脚本。函数中的每一个命令都被按照顺序执行。
3 在函数 welcome 中有两个位置参量。当参数传递给函数的时候,位置参量就被赋值。
4 作为参数 tom 和 joe 被赋值给$1,$2。这些位置参量只在函数内部使用,不能供函数外部使用。
5 在命令行设置一些位置参量,它们对于函数内部的位置参量没有影响。
6 $*显示当前被设置的位置参量的值。
7 调用函数 welcomn,Johan 和 joe 被赋值相应的位置参量。
8 函数中的变量使位置参量无效。
9 unset 命令使用-f 选项清空函数,这个函数不再被定义。

列出和清空函数。列出函数和它们的定义使用命令 declare 在 bash 2.x 及以上版本中


declare –F 只列出函数的名字,函数的定义将跟局部变量一齐送到标准输出。unset –f 命令可
以清空这些函数。

8.3.9 标准 I/O 和重新定向


当 Shell 启动,它继承三个文件:stdin、stdout 和 stderr。标准输入通常来自键盘。标准
输出和标准错误通常是屏幕。但是很多时候你也许想从文件中读取输入或者把输出保存在文

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 267

件中。这个时候你就可以使用 I/O 重新定向,见表 8.27。

表 8.27 重新定向

重新定向操作符 作用
< 重新定向输入
> 重新定向输出
>> 追加输出
2> 重新定向错误
&> 重新定向错误和输出
>& 重新定向错误和输出
2>&1 重新定向错误到标准输出
1>&2 重新定向标准输出到错误
>| 重新定向输出的时候覆盖 noclobber
<>filename 如果是一个设备文件,就把这个文件作为标准输入和标准输出

实例 8.87

1 $ tr '[A-Z]' '[a-z]' < myfile


Redirect input

2 $ ls > lsfile Redirect output


$ cat lsfile
dir1
dir2
file1
file2
file3

3 $ date >> lsfile Redirect and append output


$ cat lsfile
dir1
dir2
file1
file2
file3
Sun Sept 17 12:57:22 PDT 1999

4 $ cc prog.c 2> errfile Redirect error

5 $ find . –name \*.c –print > foundit 2> /dev/null


Redirect output to foundit and errors to /dev/null,
respectively.

6 $ find . –name \*.c –print >& foundit


Redirect both output and errors to foundit.

7 $ find . –name \*.c –print > foundit 2>&1


Redirect output to foundit and send errors to where output
is going; i.e. foundit

PDF created with pdfFactory Pro trial version www.pdffactory.com


268 第8章

8 $ echo "File needs an argument" 1>&2


Send standard output to error

说明
1 把 Linux tr 命令的标准输入定向到文件 myfile。所有大写字母都被转换为小写字母。
2 把 ls 命令的输出重新定向到文件 lsfile。
3 把 date 命令的输出重新定向追加到文件 lsfile 中。
4 编译 C 程序的源文件 prog.c。如果编译失败,错误信息被重新定向到文件 errfile。
5 find 命令在当前工作目录下搜索以.c 结尾的所有文件,并把结果打印到文件 foundit 中,错误信
息打印到/dev/null。
6 find 命令在当前工作目录下搜索以.c 结尾的所有文件,并把结果打印到文件 foundit 中,错误信
息也打印到 foundit。
7 同 6。
8 echo 命令把信息发送到标准错误,该信息标准错误与标准输出合并在一起。

exec 命令和重新定向。exec 命令可以替换当前程序而不需要启动一个新的进程。exec


可以改变标准输入和输出而不需要启动一个新的子进程,见表 8.28。如果文件用 exec 打开,
read 命令就会把文件指针每次向下一行直到文件的末尾。如果要重新从文件开始阅读则必须
把文件关闭再重新打开。但是,如果使用 Linux 的 cat 或者 sort 工具,操作系统在每一个命
令结束后都会将文件关闭一次。
表 8.28 exec 命令

exec 命令 作用
exec ls 在 Shell 内执行 ls,当命令 ls 结束后,还是在开始的那个 Shell 中而不需要返回
exec < filea 打开文件来 filea 读取标准输入
exec >filex 打开文件 filex 向标准输出写
exec 3<datfile 打开 datfile 作为文件说明符读取输入
sort <&3 datfile 被分类
exec 4>newfile 打开文件 newfile 作为文件描述符写输出
ls >&4 ls 的输出被重新定向到 newfile
exec 5<&4 创建 fd4 的拷贝 fd5
exec 3<&- 关闭 fd3

实例 8.88

1 $ exec date
Thu Oct 14 10:07:34 PDT 1999
<Login prompt appears if you are in your login shell >

2 $ exec > temp


$ ls
$ pwd
$ echo Hello
3 $ exec > /dev/tty
4 $ echo Hello
Hello

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 269

说明
1 exec 在当前 Shell 中执行 date 命令(无需调用子 Shell)。因为 date 命令是在当前 Shell 内执行的,
因此当 date 命令退出后,Shell 就终止了。如果 bash 从 tcshell 中启动,bash 将退出并出现 tcshell
提示符。如果你在登录 Shell 中,那么将退出登录。如果在交互窗口中运行,那么这个窗口将退
出。
2 exec 命令把当前 Shell 的标准输出打开到 temp 文件。因此 ls、pwd 和 echo 命令的输出将不再显
示在屏幕上,而是输出到文件 temp 中。见图 8.3。
3 exec 再次把标准输出打开到终端。从第四行起,输出将重新显示在屏幕上。
4 标准输出被显示在屏幕上(/dev/tty)。

/bin/sh /bin/sh

0 标准输入 键盘 0 标准输入 键盘
1 标准输出 temp 1 标准输出 屏幕(/dev/tty)
2 标准错误 屏幕(/dev/tty) 2 标准错误 屏幕(/dev/tty)

图 8.3 exec 命令和文件提示符

实例 8.89

1 > bash
2 $ cat doit
pwd
echo hello
date
3 $ exec < doit
/home/homebound/ellie/shell
hello
Thu Oct 14 10:07:34 PDT 1999
4 >

说明
1 在 tcshell 提示符下启动 bash(在这种模式下,当 exec 命令退出后,用户不会退出登录) 。
2 显示一个叫作 doit 文件的内容。
3 exec 命令把标准输入打开到一个称为 doit 的文件。于是从这个文件读取输入代替了从键盘读取
输入。文件 doit 中的命令将在当前 Shell 中就地执行。当最后一个命令执行完以后,Shell 就退
出了。
4 当 exec 命令执行完,bash 就退出了,接着出现 tcsh 提示符。如果你在登录 Shell 中,那么将退出
登录。如果在交互窗口中运行,那么这个窗口将退出。

实例 8.90

1 $ exec 3> filex


2 $ who >& 3

PDF created with pdfFactory Pro trial version www.pdffactory.com


270 第8章

3 $ date >& 3
4 $ exec 3>&-
5 $ exec 3<filex
6 $ cat <&3
ellie tty1 Jul 21 09:50
ellie ttyp1 Jul 21 11:16 (:0.0)
ellie ttyp0 Jul 21 16:49 (:0.0)
Wed Jul 21 17:15:18 PDT 1999
7 $ exec 3<&-
8 $ date >& 3
date: write error: Bad file descriptor

说明
1 文件描述符 3 被分配给 filex,并打开作为输出的重新定向。见图 8.4(a) 。
2 who 命令的输出被发送给文件描述符 3,也就是 filex。
3 date 命令的输出被发送给文件描述符 3。由于 filex 已经打开,所以新的输出就被追加到文件中。
4 关闭文件描述符 3。
5 exec 把 fd(文件描述符)3 打开作为输入。输入被重新定向到文件 filex。见图 8.4(b)

6 cat 命令从 fd3 中读取输入,赋值给 filex。
7 exec 命令关闭 fd3(通常,一旦文件被查找过了,操作系统就会将其关闭) 。
8 当尝试把 date 命令的输出发送给 fd3 的时候,bash 就会报告错误,因为在这以前 fd3 已经被
关闭了。

标准输入 标准输入
标准输出 标准输出
标准错误 标准错误
filex filex
a. 打开并读取文件 filex b. 打开并写入文件 filex

图 8.4 exec 和文件描述符

8.3.10 管道
管道就是把管道符号左边命令的输出发送给管道符号右边的命令作为输入。一个管道线
可以由不止一个管道组成。
下面这个例子就是计算登录人数的,把 tmp 文件中的命令的输出保存起来,使用 wc –l
命令统计 tmp 文件有多少行,然后再删除这个文件(即得到了登录人数)。

实例 8.91

1 $ who > tmp


2 $ wc –1 tmp
4 tmp
3 $ rm tmp

Using a pipe saves disk space and time.

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 271

4 $ who | wc -1
4
5 $ du .. | sort –n | sed –n '$p'
1980 ..
6 $ ( du / | sort –n | sed –n '$p' ) 2> /dev/null
1057747 /

说明
1 who 命令的输出被重新定向到文件 tmp。
2 wc –l 命令显示 tmp 文件的行数。
3 删除 tmp 文件。
4 使用管道工具,你可以把这三个步骤合并为一个。把 who 命令的输出放到匿名内核缓冲区中,
wc –l 命令将从这个缓冲区中读入并把输出打印到屏幕上。参考图 8.5。
5 du 命令的输出的是从当前上层目录开始,每一个目录所占用的磁盘块的个数,通过管道这个输
出被作为 sort 命令的输入,分类的结果被作为 sed 命令的输入,sed 打印它接收到的最后一行到
屏幕上。
6 如果因为权限的原因无法访问一些目录,du 命令将把错误信息发送 stderr,也就是屏幕。当你
把整行的命令都 放在括号中 ,所有的输 出都显示在 屏幕 上,而所有的错 误信息都将 送到
/dev/null。

终端显示器

标准输入 标准输入
标准输出 标准输出
标准错误 标准错误

图 8.5 管道

终端显示器

标准输入 标准输入 标准输入


标准输出 标准输出 标准输出
标准错误 标准错误 标准错误

图 8.6 多重管道

8.3.11 here 文档和重新定向输入


here 文档是一种特殊形式的引用。它为需要输出的程序,例如:mail、sort 和 cat 接收在
线文本,直到遇到用户定义的结束符号为止。它通常用在 Shell 脚本中创建菜单。需要获得
输入的命令后面有<<符号,以及用户定义的结束符号,最后是换行符。下一行就将是被发送

PDF created with pdfFactory Pro trial version www.pdffactory.com


272 第8章

给命令的文本。当用户定义的结束符单独出现在行的最左边时,输入就结束了。结束符用来
代替 Control-D 来结束程序的输入。
在命令行上,结束符前有<<、制表符或者只有制表符。下面的例子用来说明 here 文档
的在命令行的用法,但实际上它更广泛地应用在脚本中。

实例 8.92

1 $ cat << FINISH # FINISH is a user-defined


2 > Hello there $LOGNAME terminator
3 > The time is $(date +%T).
> I cat't wait to see you!!!
4 $ FINISH # terminator matches first

5 Hello there ellie # FINISH on line 1.


The time is 19:42:12.
I cat’t wait to see you!!!
6 $

说明
1 Linux cat 程序等待用户的输入,直到用户定义的结束符 FINISH 单独出现在一行的最左边。
2 次要提示符出现,下面的文本都是 cat 的输入,变量替换开始替换 here 文档。
3 命令替换 $(date+%T)与 here 文档一同使用。
4 用户定义的 FINISH 出现,输入结束。在 FINISH 前和后都不能有空格。
5 显示 cat 的输出。
6 出现 Shell 提示符。

实例 8.93

1 $ cat <<- DONE


> Hello there
> What's up?
>Bye now The time is 'date'.
2 > DONE
Hello there
What's up?
Bye now The time is Sun Feb 819:48:23 PST 1999.
$

说明
1 cat 等待用户的输入,直到用户定义的结束符 DONE 单独出现在一行的最左边。由于使用了<<-
符号,因此在结束符前面可以有一个或者几个制表符。这种情况在命令行下会有问题,但是在脚
本中没有任何问题。
2 结束符 DONE 被匹配,它前面有一个制表符。输入结束,输出被打印到屏幕上。

8.3.12 Shell 调用选项


当我们启动 bash 命令行的时候,有一些选项可以帮助我们控制它的行为。这有两种选
项:单字符选项和多字符选项。单字符选项由一个减号和一个字符组成,多字符选项由两个
减号和多个字符组成,多字符选项必须出现在单字符选项以前。交互式 Shell 通常在启动时

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 273

使用-i(交互模式)、-s(从标准输入读)和-m(允许作业控制)选项。见表 8.29。

表 8.29 bash 2.x shell 调用选项

选项 含义
-c string 从 string 中读取命令 string 后面的参数作为位置参量,从$0 启动
-D 被双引号括起来并以$开头的字符串列表打印到标准输出。如果本地术语集不是
C 或者 POSIX,再加上-n 选项,那么这样字符串将被作为语言翻译的目标,没有
任何命令会被执行
-i 启动交互模式,忽略 TERM、QUIT 和 INTERRUPT
-s 从标准输入中取得输入,允许设置位置参量
-r 启动受限制的 Shell
-- 选项结束信号,在这个符号以后禁止处理其他选项。这个符号后面的参数都被认
为是文件名或者参数
--dump –strings 同-D
--help 显示内建命令的用法
--login 把 bash 作为登录 Shell
--noediting 在交互模式下不使用 Readline 库
--noprofile 启动以后 Shell 不读取初始化文件,如/etc/profile、~/.bash_profile、~/.bash_login
以及~/.profile
--norc 默认选项,在交互模式下,bash 不会读取~/.bashrc 文件。如果运行的 Shell 类似
sh,则该选项将默认打开
--posix 改变 bash 的行为符合 POSIX1003.2 标准,否则 bash 不会自动符合 POSIX 标准
--quiet 启动过程中不显示任何信息,默认选项
--rcfile file 若 bash 是交互式的,则用 file 替代初始化文件~/.bashrc
--restricted 启动受限制的 Shell
--verbose 打开 verbose,同-v
--version 显示该 bash Shell 的版本信息并退出

8.3.13 set 命令和选项


set 命令可以用来打开和关闭 Shell 选项,也可以处理命令行参数。要想打开选项,就在
选项前加-。若要关闭选项,就在选项前加+。参考表 8.30。

实例 8.94

1 $ set -f
2 $ echo *
*
3 $ echo ??
??

4 $ set +f

说明
1 打开 f 选项,禁止文件名扩展。

PDF created with pdfFactory Pro trial version www.pdffactory.com


274 第8章

2 星号没有被扩展。
3 问号没有被扩展。
4 关闭 f 选项,允许文件名扩展。

表 8.30 内置命令 set 的选项

选项名称 快捷开关 作用
allexport -a 从设置开始标记所有新的和修改过的用于输出的变量
braceexpand -B 允许括号扩展,默认选项
emacs 在进行命令行编辑时,使用内建的 emacs 编辑器,默认选项
errexit -e 如果一个命令返回一个非 0 退出状态值(失败),就退出。
在读取初始化文件的时候这个选项没有被设置
histexpand -H 在做历史替换的时候允许使用!和!!
。默认选项
History 允许命令行历史,默认选项
ignoreeof 禁止 Control-D 的方式退出 Shell,必须输入 exit。就像设置
Shell 变量,IGNOREEOF=0
keyword -k 为命令把关键字参数放在环境中
interactive_comments 在交互模式下,#用来表示注释
monitor -m 允许作业控制
noclobber -C 保护文件在使用重新动向的时候不被覆盖
noexec -n 在脚本状态下读取命令但是不执行,主要是为了检查语法结
构。在交互模式下不起作用
noglob -d 禁止路径名扩展,即关闭通配符
notify -b 在后台作业结束以后通知用户
nounset -u 在扩展一个没有设置的变量时显示错误信息
onecmd -t 在读取并执行一个命令后退出
phisical -P 如果被设置,则在使用 pwd 和 cd 命令时不使用符号连接的
路径,而是遵循物理路径
Posix 改变 Shell 行为以符合 POSIX 要求
privileged -P 一旦被设置,Shell 就不再读取.profile 文件和 ENV 文件,
Shell
函数也不继承任何环境
Posix 改变 Shell 行为以符合 POSIX1003.2 标准
verbose -v 为调试打开 verbose 模式
Vi 在命令行编辑的时候使用内置的 vi 编辑器
xtrace -x 打开调试回响模式

8.3.14 shopt 命令和选项


shopt 命令(bash 2.x)也可以用来打开和关闭选项。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 275

表 8.31 shopt 命令选项

选项 含义
cdable_vars 如果内置命令 cd 的参数不是一个路径,那么就假设是个变量。变量的值是一
个路径
cdspell 更正 cd 命令参数中路径的拼写错误,这些错误包括错字符、多字符和少字符,
如果发现错,将自动更正并打印完整路径,执行命令,该参数只在交互模式
下使用
checkhash 在执行一个命令以前首先检查哈希表,如果这个表不存在就在正常路径下搜
索命令
checkwinsize 在运行每条命令后检查窗口的尺寸,如果必要可以更新 LINES 和 COLUME 的

cmdlist 尝试在同一行中保存多行命令,这使得重新编辑这些多行命令变得简单
dotglob 在文件名扩展中也包含那些以“.”开头的文件
execfail 在交互模式和非交互模式下,即使 exec 无法执行一个文件也不退出 shell
Expand_aliases 允许别名扩展,默认选项
extglob 允许扩展模式匹配特征(从 Korn Shell 的文件名扩展特性中获得的那些正则
表达式规则)
histappend 在 Shell 退出的时候把历史追加到一个文件中,这个文件名保存在变量
HISTFILE 中
histreedit 如果使用 readline,用户可以重新编辑失败的历史命令替换
histverify 当该选项被设置时,命令的历史替换结果不是立刻被传递给 Shell 去检验而是
先装入大 readline 编辑器的缓冲区中,允许进一步编辑
hostcomplete 当该选项被设置后,Shell 就会在出现@的时候自动完成主机名,默认选项。
huponexit 当退出交互模式的时候,Shell 会向所有作业发送 SIGUP 信号
Interactive_comment 默认允许在交互模式下使用#开头的注释
lithist 如果这个选项打开,cmdhistt 选项也打开,就尽量采用嵌入新行的方式代替分
号保存多行的历史命令
mailwarn 如果 back 通过检查发现邮件已被阅读过了就显示“The mail in mailfile has
been read”
nocaseglob 如果设置,Shell 就按照大小写敏感的方式进行文件名扩展
nullglob 如果设置,在文件名扩展没有找到匹配向的时候,使用空字符串匹配
promptvars 提示符也可以进行变量扩展。默认选项
restricted_shell 启动 Shell 的限制模式
shift_verbose 如果设置当位置参量个数溢出时打印错误信息
sourcepath 如果设置,内 source 就使用 PATH 变量中保存的路径寻找作为参数的文件。
默认选项
source “.”的同义词

8.3.15 Shell 内建命令


Shell 有很多的内建的命令,因为这些命令是内建的,因此 Shell 不需要在磁盘上为它们
定位,执行速度更快。help 选项可以帮助你取得这些命令的联机帮助。内建命令列在了表
8.32 中。

PDF created with pdfFactory Pro trial version www.pdffactory.com


276 第8章

表 8.32 内建命令

命令 作用

: 没有什么作用的命令,返回退出状态值 0
.file 点(dot)命令读取并 file 中的命令
Break[n] 参考“循环命令”
. 在当前进程中执行程序,类似 source
alias 为命令建立别名并打印别名清单
bg 把作业放到后台
显示当前键盘与功能之间的绑定关系,或是键盘与 readliue 函数或
bind
宏的绑定关系
break 在循环嵌套中结束最内层的循环
builtin[sh-builtin[args]] 运行一个内部命令,在出现一个函数与内部命令重名的时候用到
cd [arg] 在没有参数时将当前工作目录转至上一级目录,若有参数则转至相
应的目录
command command [arg] 运行一个命令,即使存在与这个命令同名的函数。即根据函数路径
定位
continue[n] 参考“循环命令”
declare[var] 显示所有变量或者声明变量及其属性
dirs 显示当前目录堆栈的内容
disown 从作业列表中删除一个活动的作业
echo[args] 显示一个参数,用换行符结尾
enable 允许或者禁止一个内建命令
eval[args] 读取参数到 Shell 并执行该命令
exec command 在 Shell 中就地执行
exit[n] 退出 Shell,退出状态值为 n
export[var] 允许变量 var 对于子进程可见
fc 编辑历史命令时候的历史修改命令
fg 把后台作业放到前台处理
getopts 分析和处理命令行参数
hash 控制内部哈希表以便完成快速查找
help[command] 显示内建命令的联机帮助
hisroty 以行号方式显示历史清单
jobs 列出后台作业清单
kill[-signal process] 中止进程

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 277

续表
命令 作用

getopts 在脚本中分析命令行并检测合法选项
let 计算数学表达式的值并把这个值赋给变量
local 在函数中限制变量的作用域只能在函数内部
logout 退出登录
popd 从目录堆栈中删除一项
pushd 在目录堆栈中增加一项
pwd 显示当前工作目录
read[var] 从标准输入读取一行保存到变量 var 中
readonly[var] 使得变量 var 只读
return[n] 从函数中推出时返回退出状态值 n
set 设置选项和位置参量
shift[n] 把位置参量向左移动 n 次
stop pid 挂起进程号为 PID 的进程
suspend 如果当前 Shell 不是登录 Shell 就停止执行这个 Shell
test 检验文件类型并给条件表达式赋值
times 打印用户和系统从这个 Shell 中启动的进程的个数
trap[args][n] 当 Shell 收到信号 n(0、1、2 或者 15)就执行 arg
type[command] 打印命令类型
typest 与 declare 相同,设置变量及属性
ultimit 显示和设置进程资源限制
umask[octal digits] 设置用户文件创建模式掩码
unalias 清空别名
unset[name] 清空变量或者函数
wait[pid#n] 等待 PID 号码为 n 的后台进程,并报告终止状态

Bourne Shell 练习

练习 1——开始

1.哪一个进程负责在屏幕上显示登录提示符?
2.哪个进程给变量 HOME、LOGNAME 和 PATH 赋值?
3.怎样知道你使用的是什么 Shell?
4.什么命令允许你更改登录 Shell?

PDF created with pdfFactory Pro trial version www.pdffactory.com


278 第8章

5.在哪里设置你的登录 Shell?
6.解释文件/etc/profile 和文件~/.bash_profile 之间的区别,哪个文件首先执行?
7.按照下列要求编辑.bash_profile 文件:
a.欢迎用户
b.增加用户的主目录到 path 中
c.用 stty 设置删除到 backspace 键
d.输入:source.bash_profile,
source 命令的作用是什么?
8.什么是 BASH_ENV 文件?什么时候这个文件是可执行的?
9.什么是默认主提示符?
a.把提示符修改为包括用户主目录和当天的时间
b.什么是默认的次要提示符?它的作用是什么?
10.解释下列每一项设置的含义:
a.set –o ignoreeof
b.set –o noclobber
c.set –o emacs
d.set –o vi
11.哪个文件保存前面的这些设置?它们为什么保存在这个文件中?
12.shopt –p 的作用是什么?为什么用 shopt 代替 set?
13.什么是内建命令?如何判断一个命令是内建命令还是可执行的外部命令?buildin
命令的作用是什么?enable?
14.什么情况下会导致 Shell 返回退出状态值 127?

练习 2——作业控制

1.程序与进程之间的区别是什么?什么是作业?
2.你使用的 Shell 的 PID 是什么?
3.如何停止作业?
4.哪个命令可以把后台的作业放到前台来?
5.如何显示全部正在运行的作业?如何显示左右已经被停止的作业?
6.kill 命令的作用是什么?
7.jobs –l 显示的是什么?kill –l 显示的是什么?

练习 3——命令自动完成、历史和别名

1.什么是文件名自动完成?
2.存储命令行命令的历史的文件叫作什么名字?
3.HISTSIZE 变量控制什么?HISTFILESIZE 变量控制什么?
4.Bang 做什么,bang 是什么含义?
5.如何再次执行最后一个以字母“v”开头的命令?
6.如何执行第 125 个命令?如何把历史命令清单按逆顺序打印出来?

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互使用 bash Shell 279

7.在使用 vi 编辑器的时候你如何设置交互式编辑?在哪个初始化文件中你可以设置这
个?
8.什么是 fc 命令?
9.Readline 库的作用是什么?从哪个初始化文件读入结构?
10.什么是键绑定?你如何找出哪个键是被绑定的?
11.什么是通用参数?
12.为如下的命令建立别名:
a.clear
b.fc –s
c.ls –color=tty
d.kill -l

练习 4——Shell 元字符

1.在提示符下输入:
touch ab abc a1 a2 a3 all al2 ba ba.1 ba.2 filex filey Abc ABC Abc2 abc
2.写出并测试可以完成如下功能的命令:
a.列出所有以字母 a 开头的文件
b.列出所有以至少一个数字结尾的文件
c.列出所有不以字母 a 或者 A 开头的文件
d.列出所有以一个句号后面跟一个数字结尾的文件
e.列出所有刚好包含两个 alpha 的文件
f.列出所有只包含三个大写字母的文件
g.列出所有以 11 或者 12 结尾的文件
h.列出所有以 x 或者 y 结尾的文件
i.列出所有以一个数字或者一个大写字母或者一个小写字母结尾的文件
j.列出所有包含字母 b 或者 B 的文件
k.删除所有以 a 或者 A 开头的两个字母的文件

练习 5——重新定向

1.跟终端捆绑在一起的三个文件的名字是什么?
2.什么是文件描述符?
3.你将使用什么命令来完成如下工作:
a.重新定向 ls 命令的输出到文件 lsfile
b.重新定向 data 命令的输出并追加到文件 lsfile
c.重新定向命令 who 的输出到文件 lsfile,将发生什么事情?
4.若只输入 cp 命令而不给任何参数,将发生什么事情?
5.如何把如上所述这些命令的错误信息保存在一个文件中?
6.使用 find 命令从上级目录开始找出所有的文件,然后把输出结果保存到文件 found,
并把输出的错误信息保存到文件 found.errs。

PDF created with pdfFactory Pro trial version www.pdffactory.com


280 第8章

7.取得三个文件的输出并把这些输出重新定向到文件 gottemall。
8.通过对 ps 命令和 wc 命令使用管道找出当前系统正在运行的进程有多少?

练习 6——变量

1.什么是位置参量?在命令行输入:
a.如何显示所有位置参量的清单?
b.哪个位置参量被赋值为 birds?
c.如何打印位置参量的个数?
d.如何从 Shell 内存中删除所有位置参量?
2.什么是环境变量?用什么命令可以显示它们的清单?建立一个称为 CITY 的变量,
并把它赋值为你的家乡。如何输出它?
3.什么是本地变量?设置一个本地变量,把它赋值为你的名字,最后清空它。
4.declare-i 的作用是什么?
5.$$变量将显示什么?$! 呢?

PDF created with pdfFactory Pro trial version www.pdffactory.com


第9章

bash Shell 编程
9.1 介 绍
当命令不是从命令行开始执行而是从一个文件开始,这个文件就叫作 Shell 脚本,这种
模式叫作非交互模式。当 bash 开始非交互模式的时候,它就开始寻找环境变量,BASH_ENV
(ENV)和启动文件(通常是.bashrc)并给它们赋值。在读取环境变量文件后,bash 开始执
行脚本。 1

9.1.1 建立 Shell 脚本的步骤


Shell 脚本通常在编辑器中编写,由命令和注释组成。注释通常以#开头,用于说明程序
将要做什么事情。
第一行。在脚本左上角的第一行用来说明程序执行脚本中的哪一行。这行通常被称为
shbang 行,写作:
#!/bin/bash
#!被称为魔术数字(magic number),用于供内核确认哪个程序将翻译并执行这个脚本。
该行必须在你的脚本的第一行。bash 也可以由参数来调整程序的行为,参考“bash 选项” 。
注释。注释是以#开头的,可以是单独的一行也可以在脚本命令以后和脚本命令共占一
行。它们用来说明脚本做什么,在没有说明的情况下,脚本有时是很理解的。虽然注释很重
要,但是在多数情况下它们是无用的。注释不仅仅是为了给别人看也是为了提醒自己,也许
两天后你就无法清楚地记得你现在想做什么了。
可执行语句和 bash Shell 光标。bash 脚本由 Linux 命令、bash 命令、程序结构和注释
组成。
使脚本可执行。当你创建一个文件后,它并没有执行的权限。你需要权限去执行它,通
过 chmod 可以将其权限改变为可执行的。

实例 9.1
1 $ chmod +x myscript
2 $ ls –1F myscript

1.以交互方式启动 bash,如果给出-norc 或者--norc 参数,BASH_ENV 和 ENV 文件都不会被读取。

PDF created with pdfFactory Pro trial version www.pdffactory.com


282 第9章

-rwxr-xr-x 1 ellie 0 Jul 13:00 myscript*

说明
1 用户、组和其他人都可以用 chmod 命令改变文件权限。
2 ls 命令显示这个文件对于所有的用户都是可以执行的,末尾的星号表示这是个可执行程序。

脚本会话。下面的例子中,用户在编辑器中创建一个文件。当该文件保存后,其执行权
限就被打开,接着执行脚本。如果程序出现错误,则 Shell 会立刻响应。

实例 9.2

(脚本)
1 #!/bin/bash
2 # This is the first Bash shell program of the day.
# Scriptname: greetings
# Written by: Barbara bashful
3 echo "Hello $LOGNAME, it's nice talking to you."
4 echo "Your present working directory is 'pwd'."
echo "You are working on a machine called 'uname -n'."
echo "Here is a list of your files."
5 ls # list files in the present working directory
6 echo "Bye for now $LOGNAME. The time is 'date +%T'!"

(命令行)
$ greeting # Don't forget to turn turn on x permission!
bash: ./greetings: Permission denied.
$ chmod +x greetings
$ greetings or ./greetings
3 Hello barbara, it's nice talking to you.
4 Your present working directory is /home/lion/barbara/prog
You are working on a machine called lion.
Here is a list of your files.
5 Afile cplus letter prac
Answerbook cprog library prac1
bourne joke notes per15
6 Bye of now barbara. The time is 18:05:07!

说明
1 脚本的第一行 #!/bin/bash 向 bernel 确认脚本的翻译执行程序是 bash。
2 注释不可执行,它们单独占据一行或者追加在命令后面。
3 在变量替换以后,echo 命令在屏幕上显示引号内的文字。
4 在命令替换以后,echo 命令在屏幕上显示引号内的文字。
5 执行 ls 命令,注释被 shell 忽略。
6 echo 命令显示双引号内的字符串。双引号中的变量和命令被替换,但是在这里引号不是必要的。

9.2 读取用户输入
9.2.1 变量(复习)
在上一节中我们讨论了变量的声明和复位。变量可以是本地变量供当前的 Shell 使用,

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 283

也可以是环境变量。除非 Shell 脚本还需要启动其他脚本,否则变量都用作本地变量在当前


的脚本中使用,见第 8 节“变量”。
从变量中提取值,再在变量前加美元符号。可以把变量用双引号引用,美元符号会被
Shell 解释为变量值提取。在单引号中,变量值无法提取。

实例 9.3

1 name="John Doe" or declare name="John Doe" (local variable)


2 export NAME="John Doe" (global variable)
3 echo "$name" "$NAME" (extract the value)

9.2.2 read 命令
read 命令是用于从终端或者文件中读取输入的内建命令,见表 9.1。read 命令读取整行
输入,每行末尾的换行符被翻译为 null(空字符串) 。如果没有指定名称,读取的行就被赋
值给一个特定的变量 REPLY。你也可以使用 read 命令,使得程序停下来等待用户输入回车。
要了解如何有效地使用 read 命令读取文件,请参考有关循环命令的章节。-r 选项忽略反斜杠
——换行符,斜杠作为行的一部分;read 命令共有四个选项,-a、-e、-p 以及-r。2
表 9.1 read 命令

格式 含义
read answer 从标准输入读取输入并赋值给变量 answer
read first last 从标准输入读取输入到第一个空格或者回车,将输入的第一个单
词放入变量 first 中,并将该行其他的输入放在变量 last 中
read 从标准输入读取一行赋值给内建变量 REPLY
read –a arrayname 把单词清单读入一个叫作 arrayname 的数组里 a
read –e 在命令行状态下打开命令行编辑。如果编辑器是 vi,那么在提示
符下就可以直接使用 vi 命令了 a
read –p prompt 打印提示,等待输入,并将输入储存在 REPLY 中 a
read –r line 允许输入包含反斜杠 a
a. 在 bash 2.0 版本以前没有这些功能。

实例 9.4

(脚本)
#!/bin/bash
# Scriptname: nosy
echo –e "Are you happy? \c"
1 read answer
echo "$answer is the right response."
echo –e "What is your full name? \c"
2 read first middle last

2.选项-a、-e 以及-p 只在 bash 2.x 的版本中有效。

PDF created with pdfFactory Pro trial version www.pdffactory.com


284 第9章

echo "Hello $first"

echo –n "Where do you work? "


3 read
4 echo I guess $REPLY keeps you busy!
------------------------------------------------------b
5 read –p "Enter your job title: "
6 echo "I thought you might be an $REPLY."

7 echo –n "Who are your best friends? "


8 read –a friends
9 echo "Say hi to ${friends[2]}."

---------------------------------------------------------------
(输出)
$ nosy
Are you happy? Yes
1 Yes is the right response.
2 What is your full name? Jon Jake Jones
Hello Jon
3 Where do you work? the Chico Nut Factory
4 I guess the Chico Nut Factory keeps you busy!
5 Enter your job title: Accountant
6 I thought you might be an Accountant.
7,8 Who are your best friends? Melvin Tim Ernesto
9 Say hi to Ernesto.

说明
1 read 命令从用户那里得到一行输入,并把它赋值给变量 answer。
2 read 命令从用户那里得到一行输入,并把第一个单词赋值给变量 first,第二个单词赋值给 middle,
剩余部分赋值给变量 last。
3 read 命令从标准输入得到一行,并把它赋值给 REPLY 变量。
4 打印变量 REPLY 的值。
5 通过-p 选项,read 命令产生一个提示“Enter your job title:”
,并把输入的行保存在变量 REPLY 中。
6 显示变量 REPLY 中所包含的字符串。
7 要求用户输入。
8 通过-a 选项,read 命令把输入作为一个单词数组。该数组被称为 friends,元素被读入变量 Melvin、
Tim 和 Ernesto。
9 打印数组 friends 的第三个元素,Ernesto,数组下标从 0 开始。

b. 此线以下的行在 bash 2.0 以前的版本中不能正确运行。

实例 9.5

(脚本)
#!/bin/bash
# Scriptname: printer_check
# Script to clear a hung up printer
1 if [ $LOGNAME != root ]
then
echo "Must have root privileges to run this program"
exit 1
fi
2 cat << EOF
Warning: All jobs in the printer queue will be removed.
Please turn off the printer now. Press return when you

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 285

are ready to continue. Otherwise press Control C.


EOF
3 read JUNK # Wait until the user turns off the printer
echo
4 /etc/rc.d/init.d/lpd stop # Stop the printer
5 echo –e "\nPlease turn the printer on now."
6 echo "Press return to continue"
7 read JUNK # Stall until the user turns the printer
# back on
edho # A blank line is printed
8 /ect/rc.d/init.d/lpd start # Start the printer

说明
1 检查是否是根用户,否则打印错误信息并退出。
2 建立一个“here”文档并在屏幕上打印错误信息。
3 read 命令等待用户的输入。当用户按下回车,变量 JUNK 接受用户的输入,该变量没有实际用处。
这里 read 命令用来等待用户关闭打印机,返回并输入回车。
4 lpd 停止打印守护程序(daemon)

5 显示“现在请把打印机打开” 。
6 用户被提示在准备好以后按下回车键。
7 不论用户输入什么,只要按下回车,程序就恢复执行。
8 lpd 启动打印守护程序。

9.3 数 学 计 算
9.3.1 整数(declare 命令和 let 命令)
declare 命令。用 declare –i 命令可以将变量声明为整数。如果将字符串赋值给整数变量,
bash 将把 0 赋值给该变量。数学计算可以发生在整数变量之间。如果你想把一个浮点数赋值
给整数变量,bash 就报告语法错误。整数变量可以接受不同进制的数,比如二进制、十进制
以及十六进制等等。

实例 9.6
1 $ declare –i num

2 $ num=hello
$ echo $num
0

3 $ num=5 + 5
bash: +: command not found

4 $ num=5+5
$ echo $num
10

5 $ num=4*6
$ echo $num
24

PDF created with pdfFactory Pro trial version www.pdffactory.com


286 第9章

6 $ unm="4 * 6"
$ echo $num
24

7 $ num=6.5
bash: num: 6.5: sytax error in expression (remainder of
expression is ".5")

说明
1 用 declare –i 命令创建一个整数变量 num。
2 给整数变量 num 赋值一个字符串 hello,导致这个变量的值为 0。
3 除非使用 let 命令,否则空格必须被引用或者删除。
4 删除空格,进行数学计算。
5 进行乘法计算,结果赋值给 num。
6 引用空格以便进行乘法。
7 向整数变量赋值一个浮点数,导致出现语法错误。

整数变量清单。declare –i 命令后面不加任何参数,将显示所有的整数变量和它们的值。
$ declare –i
declare –ir EUID="15" # effective user id
declare –ir PPID="235" # parent process id
declare –ir UID="15" # user id

表达和使用不同的进制。数字可以表达为二进制数、十进制数和十六进制数等等,范围
从 2 进制~36 进制。

格式
variable=base#number-in-that-base

实例 9.7
n=2#101 Base is 2; number 101 is in base 2

实例 9.8

(The command Line)


1 $ declare –i x=017
$ echo $x
15
2 $ x=2#101
$ echo $x
5
3 $ x=8#17
$ echo $x
15
4 $ x=16#b
$ echo $x
11

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 287

说明
1 declare 函数用于给整数变量 x 赋八进制值 017,八进制数必须以 0 开头。017 的十进制值为 15,
打印该十进制值。
2 把二进制数 101 赋值给变量 x,二进制数必须以 2#开头。打印二进制数 101 的十进制数 5。
3 把八进制数 17 赋值给变量 x,八进制数必须以 8#开头,打印 x 的十进制数为 15。
4 把十六进制数 b 赋值给变量 x,十六进制数必须以 16#开头,打印 x 的十进制数 11。

let 命令。let 命令是 bash 的内建命令,用来做数学计算和数字表示法检测。要想了解你


的 bash 版本支持哪些 let 操作,输入命令:
help let
表 9.4 是 let 命令操作清单。

实例 9.9

1 $ i=5 or let i=5

2 $ let i=i+1
$ echo $i
6

3 $ let "i = i + 2"


$ echo $i
8

4 $ let "i+=1"
$ echo $i
9

5 $ i=3

6 $ (( i+=4))
$ echo $i
7

7 $ (( i=i-2 ))
$ echo $i
5

说明
1 将 5 赋值给变量 i。
2 let 命令使得变量 i 增加 1。当进行数学计算时,变量替换不需要$。
3 如果参数包含空格就需要引用。
4 快捷操作符号+=表示把变量值增加 1。
5 变量 i 被赋值为 3。
6 双括号用来替代 let 命令,先给变量 i 增加 4,然后再赋值给变量 i。
7 先在 i 中减少 2,然后再赋值给 i,也可以写为 i-=5。

9.3.2 浮点数学计算

bash 支持整数的数学计算,如果需要进行更加复杂的计算,bc、awk 和 nawk 实用程序

PDF created with pdfFactory Pro trial version www.pdffactory.com


288 第9章

将十分有用。

实例 9.10

(The Command Line)


1 $ n='echo "scale=3; 13 / 2" | bc'
$ echo $n
6.500

2 product='gawk –v x=2.45 –v y=3.123 'BEGIN{printf "%.2f\n",x*y}''


$ echo $product
7.65

说明
1 echo 的结果通过管道传递给 bc 程序。scale 被设置为 3,小数点右侧的有效数字将被打印。计算
13 除以 2,引号后面管道被关闭。然后将命令替换以后的计算结果赋给变量 n。
2 gawk 从命令行参数取得值,x=2.45、y=3.123。数字相乘以后,printf 函数格式化并打印结果精确
到小数点后面 2 位。输出被赋给变量 product。

9.4 位置参量与命令行参数
9.4.1 位置参量
信息可以通过命令行传递给脚本。在脚本名称后面以空格分隔的单词称为参数。
命令行参数在脚本中可以按照其位置提供参考作用。例如:$1 表示第一个参数,$2 表
示第二个参数,依此类推,到$9 以后,就用括号把数字括起来防止混淆。例如第十个参数,
就表示为${10}。变量$#用来判断参数的个数,而$*用来显示所有的参数。用 set 可以设置或
者重置位置参量。当使用 set 命令时,所有位置参量的值都被清空。见表 9.2。
表 9.2 位置参量

位置参量 含义
$0 引用脚本名称
$# 获取位置参量的个数
$* 列位置参量清单
$@ 同上
“$*” 扩展为一个参数
“$@” 扩展为彼此分隔的参数
$1 … ${10} 引用单个位置参量

实例 9.11
(The Script)
#!/bin/bash
# Scriptname: greetings2
echo "This script is called $0."
1 echo "$0 $1 and $2"

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 289

echo "The number of positional parameters is $#"


------------------------------------------------------------------
(The Command Line)
$ chmod +x greetings2
2 $ greetings2
This script is called greetings2.
greetings and
The number of positional parameters is
3 $ greetings2 Tommy
This script is called greetings2.
greetings Tommy and
The number of positional parameters is 1

4 $ greetings2 Tommy Kimberly


This script is called greetings2.
greetings Tommy and Kimberly
The number of positiona1 parameters is 2

说明
1 在脚本 grettings2 中,位置参量$0 表示脚本名称。$1 表示第一个命令行参数,$2 表示第二个命
令行参数。
2 脚本 grettings2 没有任何参数传递,输出说明脚本的名称叫作 grettings2,因为没有参数,所以变
量$1 和 $2 为空,什么也不打印。
3 这次,有一个参数 Tommy 被传递给脚本,Tommy 被赋值给位置参量 1。
4 输入两个参数 Tommy 和 Kimberly。Tommy 被赋值给$1,Kimberly 被赋值给$2.

9.4.2 set 命令和位置参量


set 命令可以用来重置位置参量 3。一旦重置,旧的参考变量清单就丢失了。恢复位置参
量用命令 set --,$0 永远是脚本名。

实例 9.12
(The Script)
#!/bin/bash
# Scriptname: args
# Script to test command line arguments
1 echo The name of this script is $0.
2 echo The arguments are $*.
3 echo The first argument is $1.
4 echo The second argument is $2.
5 echo The number of arguments is $#.
6 oldargs=$*
7 set Jake Nicky Scott # reset the positional parameters
8 echo All the opsitional parameters are $*.
9 echo The number of postional parameters is $#.
10 echo "Good-bye for now, $1."
11 set $(date) # reset the positional parameters
12 echo The date is $2 $3, $6.
13 echo "The value of \$oldargs is $oldargs."

3.没有参数的 set 命令将显示该 Shell 的所有变量设置,带有选项的 set 命令可以打开或者关闭例如-x 或-v 那样的 Shell 控制
选项。

PDF created with pdfFactory Pro trial version www.pdffactory.com


290 第9章

14 set $oldargs
15 echo $1 $2 $3
(The Output)
$ args a b c d
1 The name of this script is args.
2 The arguments are a b c d.
3 The first argument is a.
4 The second argument is b.
5 The number of arguments is 4.
8 All the positional parameters are Jake Nicky Scott.
9 The number of positional parameters is 3.
10 Good-bye for now, Jake.
12 The date is Mar 25, 2000
13 The value of $o1dargs is a b c d.

说明
1 脚本名储存在变量$0 中。
2 $*表示所有位置参量。
3 $1 表示第一个位置参量。
4 $2 表示第二个位置参量。
5 $#表示表示位置参量的个数。
6 所有的位置参量都保存在变量 oldargs 中。
7 set 命令允许你重置位置参量,清除旧的参量列表,现在,$1 是 Jack,$2 是 Nicky,$3 是 Scotte。
8 $*表示所有变量,Jack、Nicky 和 Scotte。
9 $#表示表示位置参量的个数是 3。
10 $1 是 Jack。
11 命令替换以后,位置参量成为 date 命令的输出。
12 显示新的$2、$3 和$6。
13 打印变量 oldargs。
14 用 set 变量从 oldargs 中建立位置参量。
15 显示前三个位置参量。

实例 9.13

(脚本)
#!/bin/bash
# Scriptname: checker
# Script to demonstrate the use of special variable
# modifiers and arguments
1 name=${1:?"requires an argument" }
echo Hello $name

(命令行)
2 $ checker
checker: 1: requires an argument
3 $ checker Sue
Hello Sue

说明
1 特殊变量编辑符?检查$1 中是否有值,如果没有就退出脚本,打印信息。
2 若不带参数运行,$1 将不被赋值,显示错误信息。
3 给程序 checker 一个命令行参数,Sue,$1 被赋值为$1。继续运行程序。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 291

$*与$@的分别。只有在用双引号引用时二者才有区别。双引号中的$*使得变量变为一
个字符串;而双引号中的$@,相当于其中的每一个变量都被双引号引用,每一个单词都被
看作分开的字符串。

实例 9.14
1 $ set 'apple pie' pears peaches
2 $ for i in $*
> do
> echo $i
> done
apple
pie
pears
peaches

3 $ set 'apple pie' pears peaches


4 $ for i in "$*"
> do
> echo $i
> done
apple pie pears peaches

5 $ set 'apple pie' pears peaches


6 $ for i in $@
> do
> echo $i
> done
apple
pie
pears
peaches
7 $ set 'apple pie' pears peaches
8 $ for i in "$@" # At last!!
> do
> echo $i
> done
apple pie
pears
peaches

说明
1 设置位置参量。
2 当$*展开的时候,apple pie 两侧的引号被去掉,apple 和 pie 就成为两个单词。for 循环按照顺序
把每一个单词赋值给 i,并打印 i 的值。每一次循环,左边的单词就被替换,且下一个单词就被
赋值给变量 i。
3 设置位置参量。
4 用双引号引用$*,整个变量变成一个大的字符串。apple pie pears peach,整个被赋值给变量 i,所
以循环只有一次。
5 设置位置参量。
6 不用引号,$*和$@的作用是一样的(见第 2 条解释)。
7 设置位置参量。
8 用双引号引用$@,每一个位置参量作为单独被引用的字符串。列出 apple pie、pear 和 peaches,
这是希望得到的结果。

PDF created with pdfFactory Pro trial version www.pdffactory.com


292 第9章

9.5 条件结构和流控制
9.5.1 退出状态
条件命令允许你基于是或否,真或假来处理一些任务。if 是最简单的判断形式;if/else
允许你执行两路判断;if/elif/else 允许你执行多路判断。
bash 允许两种类型的条件控制:命令的成功或者失败,表达式的真或者假。在两种情况下,
都需要使用退出状态。退出状态值为 0 表示成功或者为真,否则为失败或者为假。?状态变量
保存了一个与退出状态值相互对应的数值。让我们通过看下面的实例 9.15,回顾一下退出状态
值的工作原理。
实例 9.15

(At the Command Line)

1 $ name=Tom
2 $ grep "$name" /etc/passwd
Tom:8ZKX2F:5102:40:Tom Savage:/home/tom:/bin/sh
3 $ echo $?
0 Success!
4 $ name=Fred
5 $ grep "$name” /etc/passwd
$ echo $?
1 Failure

说明
1 给变量 name 赋值为字符串 Tom。
2 grep 命令在 passwd 文件中搜索 Tom。
3 ?状态变量包含最后一个命令的退出状态。在这个例子中是 grep 的退出状态。如果找到了 tom
该值就是 0,否则就非 0。
4 变量 name 被赋值为字符串 Fred。
5 grep 命令在 passwd 文件中搜索 Fred,若无法找到,?状态变量的值是 1,表示搜索失败。

9.5.2 内建 test 命令
通常用内建的 test 命令给表达式赋值,这个命令也用来连接括号。可以使用 test 命令,
或用一系列的括号代替 test 命令。只有 test 命令或者使用方括号时,表达式不能赋值。因为
空格在字符串中用来分隔单词,所以包括空格的单词需要使用引号,见实例 9.16。
在 bash2.x 版本中,[[]]可以用来给表达式赋值(内建混合的 test 命令)。包含空格的字
符串在整体使用时必须被引号引用。在简单 test 命令中,逻辑符号&&(逻辑和)和||(逻辑
或)可以替代 –a 或者 –o 选项(见实例 9.17)。
虽然 test 命令可以给数学表达式赋值,但最好还是用 let 给数学表达式赋值,因为它有
丰富的类 C 语言的操作符。let 命令可以利用双括号加以简化(见实例 9.18) 。
无论使用 test 命令、混合命令还是 let 命令,表达式的结果都将检验,退出状态值是 0
则表示成功,否则表示失败(见表 8.14) 。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 293

下面的例子用来说明如何使用内建命令 test 判断退出状态值和 test 命令的其他形式,一


组[]、复合命令[[]]、let 命令和(())。

实例 9.16
The test Command
(At the command Line)

1 $ name=Tom

4 $ test $name != Tom


5 $ echo $?
1 Failure
6 $ [ $name = Tom ]
# Brackets replace the test command
7 $ echo $?
0
8 $ [ $name = [Tt]??]
$ echo $?
1
9 $ x=5
$ y=20
10 $ [ $x –gt $y ]
$ echo $?
1
11 $ [ $x –le sy ]
$ echo $?
0

说明

1 给变量 name 赋值为字符串 Tom。


2 grep 命令在 passwd 文件中搜索 Tom。
3 ?状态变量包含最后一个命令的退出状态。在这个例子中是 grep 的退出状态,如果找到了 Tom,
这个值就是 0,否则这个值就非 0。
4 test 命令用来给字符串、数字表达式赋值,或者判断文件。像所有命令一样返回退出状态值。如
果是 0 则表达式为真,否则为假。等号两边必须有空格,这里判断 name 的值是不是 Tom。
5 判断失败退出状态值为 1。
6 括号是 test 命令的变形。 第一个括号后面必须有空格。
判断表达式看看变量 name 的值是不是 Tom。
在判断字符串的时候 bash 允许使用一个或者两个等号。
7 退出状态值为 0,说明 $name 等于 Tom。
8 test 命令不允许使用通配符。?被看作是问号而不是通配符。判断失败,退出状态值为 1,表示
第 8 行出现错误。
9 x 和 y 被预先给定为数字。
10 Test 用数字关系运算操作判断表达式,这里判断如果$x 大于$y 就返回 0,表示真,否则返回非 0
数(见表 9.3) 。
11 判断$x 是否小于$y,返回 0 表示为真,返回 1 表示为假。

实例 9.17

The compound test command (bash 2.x)


$ name=Tom; friend=Joseph
1 $ [[ $name == [Tt]om ]] Wildcards allowed
$ echo $?

PDF created with pdfFactory Pro trial version www.pdffactory.com


294 第9章

0
2 $ [[ $name == [Tt]om && $friend == "Jose" ]]
$ echo $?
1
3 $ shopt –s extglob Turns on extended pattern matching
4 $ name=Tommy
5 $ [[ $name == [Tt]o+(m)y ]]
$ echo $?
0

说明
1 如果使用复合命令 test,Shell 元字符可以用在字符串判断中。这个表达式判断中变量 name 是不
是等于 Tom、tom 或者 tommy。如果表达式是真,退出状态值就是 0。
2 逻辑运算符号&&(逻辑和)和 ||(逻辑或)都可以参与复合判断。如果使用&&,两个表达式
就必须同时为真,如果第一表达式就为假,判断就停止了;使用|| 时如果有一个表达式为真,整
个表达式就为真,因此如果第一个表达式为真,判断就不继续了。注意,“Jose”是被引用的。
如果不是被引用就测试变量 friend 是否包含模式 Jose。
这个例子因为第二个表达式为假所以为假。
3 内建命令 shopt 打开了扩展模式匹配。
4 把 Tommy 赋值给变量。
5 这个判断中要判断表达式与使用新的元字符的是否模式匹配。这个表达式判断变量 name 是否匹
配以字符 t 或者 T 开头,紧跟一个 o,一个或者多个 m,然后是一个 y。

实例 9.18

The let Command (bash 2.x)


(At the Commmand Line)
1 $ x=2
$ y=3

2 (( x > 2 ))
echo $?
1

3 (( x < 2 ))
echo $?
0

4 (( x == 2 && y == 3 ))
echo $?
0

5 (( x > 2 || y < 3 ))
echo $?
1

说明

1 x 和 y 赋值为数字。
2 双引号代替 Let 为数字表达式赋值。如果 x 大于 y 退出状态值就是 0,因为条件为假,所以退出
状态值为 1。注意变量赋值的时候,如果使用(() ),美元符号就不是必须的。
3 双引号为数字表达式赋值,如果 x 小于 2,退出状态值就是 0,否则为 1。
4 复合表达式赋值。表达式判断如下:如果 x 等于 2 同时 y 等于 3,退出状态值就为 0,否则为 1。
5 复合表达式赋值。表达式判断如下:如果 x 大于 2 或者 y 小于 3,退出状态值就为 0,否则为 1。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 295

表 9.3 test 命令操作符

判断操作符 判断是否为真
字符串判断
[string1=string2] string1 等于 string2
[string1==string2] string1 等于 string2
[string1!=string2] string1 不等于 string2
[string] string 不空
[ -z string] string 长度是 0
[ -n string] string 长度非 0
[ -l string] string 长度
例子 test -n $ vord 或[-u $ word ]
test tom=sue 或[tom=sue ]
逻辑判断
[string1 –a string1] string1 和 string2 都是真
[string1 –o string2] string1 或 string2 是真
[!string1] string1 不匹配
a
逻辑判断(复合判断)
[[pattern1 && pattern2]] pattern1 和 pattern2 都是真
[[pattern1 || pattern2]] pattern1 或 pattern2 是真
[[!pattern]] pattern 不匹配
整数判断
[int1 –eq int2] int1 等于 int2
[int1 –ne int2] int1 不等于 int2
[int1 –gt int2] int1 大于 int2
[int1 –ge int2] int1 大于等于 int2
[int1 –lt int2] int1 小于 int2
[int1 –le int2] int1 小于等于 int2
文件判断中的二进制操作
[file1 –nt file2] file1 比 file2 新
[file1 –ot file2] file1 比 file2 旧
[file1 –ef file2] file1 与 file2 有相同的设备或者 I 结点数
a. 在逻辑判断中,pattern 可以包含元字符;在字符串判断中,pattern2 必须包含在引号中。

表 9.4 let 命令操作符

操作符 含义
-+ 一元减和加
!~ 逻辑否
*、/、% 乘、除、余数
+- 加、减
在 bash2.x 版本以前没有的符号:
<<、>> 位逻辑左移位、右移位

PDF created with pdfFactory Pro trial version www.pdffactory.com


296 第9章

续表
操作符 含义
<= >= <> 比较运算符
= =、!= 等、不等
& 位逻辑与
^ 位逻辑非
| 位逻辑或
&& 逻辑和
|| 逻辑或
=、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|= 快捷方式

9.5.3 if 命令
最简单的判断形式就是 if。if 结构后面的命令执行并返回退出状态值,退出状态值通常
由程序的作者决定。如果退出状态值是 0, 命令成功就执行关键字 then 后面的语句。在 C Shell
中,if 后面的表达式必须跟 C 语言一样是一个布而型的表达式。但是在其他的 Shell 中,可
以是一组命令。如果命令退出状态值是 0 就执行关键字 then 后面的语句块,直到遇到 fi。fi
终止 if 块。如果退出状态值非 0,则意味着出现了错误,关键字 then 后面的语句被忽略,执
行 fi 后面的语句。
了解被判断命令的退出状态值是十分重要的。退出状态值是了解 grep 是否在文件中搜
索到模式所必须依赖的方法。如果 grep 成功就返回 0,否则返回非 0。

格式
if command
then
command
command
fi
-----------------------------------

(Using test for numbers and strings -- old format)

if test expression
then
command
fi
or

if [ string/numeric expression ] then


command
fi
-------------------------------------

(Using test for striags -- new formst)

if [[ string expression ]] then


command
fi

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 297

(Using let for numbers -- new format

if (( numeric expression ))

----------------------------------

实例 9.19
1 if grep "$name" /etc/passwd > /dev/null 2>&1
2 then
echo Found $name!
3 fi

说明
1 grep 在 /etc/passwd 文件中搜索参数 name,将标准输出和标准错误重新定向到/dev/null。
2 如果退出状态值是 0,程序就执行 then 后面的语句直到 fi 关键字 then 与 fi 之间的语句以缩进的
格式书写,是为了增强程序的可读性,为调试提供方便。
3 fi 终止了 then 后面的语句块。

实例 9.20
1 echo "Are you o.k. (y/n) ?"
read answer
2 if [ "$answer" = y -o "$answer" = y ]
then
echo "Glad to hear it."
3 fi

4 if [ $answer = y -o "$answer" = y ]
[: too many arguments

----------------------------------------------
5 if [[ $answer == [yy]* || $answer == Maybe ]]a
then
echo "Glad to hear it."
fi

6 shopt -s extglob

7 answer="not really"

8 if [[ $answer = [Nn]o?( way|t really) ]]


then
echo "So sorry."
fi

说明
1 问用户问题并要求回答,read 命令等待用户的回答。
2 方括号替代 test 命令用在判断表达式中。如果表达式为真,就返回 0,否则就返回非 0。如果变
量 answer 的值是 Y 或者 y,就执行 then 后面的命令。$answer 被双引号引用作为一个字符串。
否则判断命令就会失败。

PDF created with pdfFactory Pro trial version www.pdffactory.com


298 第9章

3 fi 终止第 2 行的 if。
4 第二行如果多于一个单词出现在=前面,test 命令就失败了。例如用户输入“yes, you batcha”变
量 answer 就被赋值了三个单词,导致 test 失败,除非用双引号把$answer 引用起来。结果的错
误信息在这里显示。
5 复合命令操作符[[]]允许 Shell 元字符出现在字符串表达式中。除非用旧版本的 test 而且变量多
于一个单词,否则不需要引号引用。
6 内建的 shopt 被赋值为 extglob,允许宽展参量扩充。见表 8.16。
7 把变量 answer 赋值为“not really”。
8 这里使用的是扩展匹配模式。表达式的含义是:如果变量 answer 匹配字符串以 no 或者 NO 开
头,紧跟着是 0 个或者一个括号内的表达式,这个表达式就为真。这个表达式可以是 no、No、
no way、No way、not really 或 Not really。

exit 命令和变量?。exit 命令用来结束脚本返回命令行。如果你希望在某种条件下退出脚


本,可以使用该命令。exit 命令的参数是一个在 0~255 之间的数字。如果参数是 0,则表示
顺利退出,而非 0 参数表示出现了一些问题。给 exit 命令的参数保存在?变量中。

实例 9.21

(脚本)
$ cat bigfiles
# Name: bigfiles
# Purpose: Use the find command to find any files in the root
# partition that have not been modified within the past n (any
# number within 30 days) days and are larger than 20 blocks
# (512 byte blocks)

1 if (( $# != 2 ))a #[ $# -ne 2 ]
then
echo "Usage: $O mdays size " l>&2
exit 1
2 fi
3 if (( $1 < 0 || $1 > 30 ))b # [ $1 -lt 0 -o S1 -gt 30 ]
then

echo "mdays is out of range"


exit 2
4 fi
5 if (( $2 <= 20 )) # $2 -le 20 ]
then
echo "size is out of range"
exit 3
6 fi

7 find / -xdev -mtime $1 -size =$2

(命令行)
$ bigfiles
Usage: bigfiles mdays size

$ echo $?
1

$ bigfiles 400 80
mdays is out of range

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 299

$ echo $?
2

$ bigfiles 25 2
size is out of range

$ echo $?
3

$ bigfiles 2 25
(Output of find prints here)

说明
1 这个语句的含义是:如果参数的个数不是 2,就打印错误信息到标准错误,退出脚本,退出参量
为 1。Let 命令和 test 命令都可以用来做数学表达式的判断。
2 fi 标志 then 后面的语句块的结束。
3 这个语句的含义是:如果第一个参量小于 0 或者大于 30 就打印错误信息然后退出脚本,退出参
量为 2。见表 9.4。
4 fi 结束 if 语句。
5 这个语句的含义是:如果命令行第二个参量的值小于等于 20(以比特为一块)就打印错误信息,
退出脚本,退出状态参数为 3。
6 fi 结束 if 语句。
7 find 命令从根开始搜索。-xdev 用来防止 find 搜索其他分区。选项-mtine 有一个数字参数,这个
数字是从文件修改到现在的天数-size 选项后面是一个参数,表示引 512 比特为一个块,这个文件
使用了多少这样的块。
a. 不能在 bash 2.x 以前版本中运行,在旧版本中该语句需改写为 if let $(($# !=2))。
b. 同上,在旧版本语句可改写为 if let $(($1<0 ||$1>30))。

检验空变量。当检验的变量值为空时,使用双引号或者 test 命令会失败。

实例 9.22
(The Script)

1 if [ "$name" = "" ]
# Alternative to [ ! "$name" ] or [ -l "$name" ]
then
echo The name variable is null
fi
(From System showmount program, which displays all remotely mounted
systems)
remotes=$(/usr/sbin/showmount )
2 if [ "X${remotes}" l= "X" ]
then
/usr/sbin/wall ${remotes}
...
3 fi

说明
1 如果 name 变量是空,判断就为真,双引号表示空。
2 showmount 命令显示所有从远程机器挂上来的客户端。这个命令可能列出一个、更多或者没有。
变量 remotes 包含值或者为空。判断的时候,字母 X 在变量 remotes 前面。如果 remotes 的值为
空,表示没有客户远程登录,X 等于 x,导致程序再次从第三行开始执行,如果变量包含一个值,

PDF created with pdfFactory Pro trial version www.pdffactory.com


300 第9章

例如主机名 pluto,表达式将变为 if Xpluto !=X,并执行 wall 命令。(所有的远程机器上的用户都


将得到一条信息),在表达式中使用 X 的目的是保证即使 remotes 为空、操作符!=两边也仍然有
占位符。
3 fi 结束 if。

嵌套 if 命令。在嵌套情况下,fi 命令总是与最近的 if 配对使用。使用缩进格式容易比较


清晰地看出哪一个 if 与哪一个 fi 相互配对使用。

格式
if command
then
command(s)
else
command(s)
fi

实例 9.23

(The Script)
#!/bin/bash
#Scriptname: grepit
1 if grep "$name" /etc/passwd >& /dev/null; then
echo Found $name!
3 else
4 echo "Can't find $name."
exit 1
5 fi

说明
1 grep 程序在 NIS 的 passwd 文件中搜索其参数 name。因为用户不需要输出,标准输出和标准错误
就被重新定向到/dev/null,这是 Linux 的一个漏斗(bucket)。
2 如果 ypmatch 命令的退出状态值是 0,程序就跳转到 then 后面的语句,执行这些命令直到 else。
3 如果 ypmatch 命令没有在 passwd 文件中找到$name,也就是退出状态值非 0,就执行 else 后面的
语句。
4 如果在 passwd 数据库中没有找到$name,就执行 echo 语句,程序退出,退出状态值为 1,表示
失败。
5 fi 终止 if。

实例 9.24
(The Script)
#!/bin/bash
# Scriptname: idcheck
# purpose:check user id to see if user is root.
# Only root has a uid of 0.
# Format for id output:uid=9496(ellie) gid=40 groups=40
# root's uid=0

1 id='id | gawk -F'[=(]' '{print $2}'' # get user id


echo your user id is: $id
2 if (( id == 0 ))a # [ $id -eq 0 ] (See cd file: idcheck2)

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 301

then
3 echo "you are superuser."
4 else
echo "you are not superuser."
5 fi

(The Command Line)


6 $ idcheck
Your user id is: 9496
You are not superuser.
7 $ su
Password:
8 # idcheck
your user id is: 0
you are superuser

说明
1 id 命令通过管道传递给 gawk 命令。gawk 命令使用等号和半个括号作为域分隔符,从输出中提
取用户 id,并把输出赋值给 id。
2、3、4 如果 id 的值等于 0,就执行第三行,如果不等于 0 就执行 else 后面的语句。
5 fi 标志 if 的结束。
6 uid 为 9496 的当前用户执行脚本 idcheck。
7 用 su 命令切换用户为 root。
8 #提示符表示新的用户是超级用户 root,root 的 uid 是 0。

a. 在 bash 2.x 以前版本中不能运行。

9.5.4 if/elif/else 命令
if/elif/else 命令允许多路判断过程。如果 if 后面的判断失败,命令就判断 elif 后面的表
达式。如果表达式为真就执行它的 then 后面的语句,如果 elif 后面的表达式为假,就检验下
一个 elif。如果没有一个表达式为真,就执行 else 后面的语句。else 块被称为默认。

格式
if command
then
command(s)
elif command
then
commands(s)
elif command
then
command(s)
else
command(s)
fi

实例 9.25

(The Script)
#!/bin/bash
# Scriptname: tellme

PDF created with pdfFactory Pro trial version www.pdffactory.com


302 第9章

# Using the old-style test command


1 echo -n "How old are you? "
read age
2 if [ $age -lt 0 -o $age -gt 120 ]
then
echo "Welcome to our planet! "
exit 1
fi
3 if [ $age -ge 0 -a $age -le 12 ]
then
echo " A child is a garden of verses"
elif [ $age –ge 12 –a $age –le 19 ])
then
echo "Rebel without a cause"
elif [ $age -ge 20 -a $age -le 29 ]
then
echo " You got the world by the tail!!"
elif [ $age -ge 30 -a $age -le 39 ]
then
echo "Thirty something..."
4 else
echo "Soryy I asked"
5 fi

(The Output)
$ tellme
How old are you? 200
Welcome to our planet!

$ tellme
How old are you? 13
Rebel without a cause

$ tellme
How old are you? 55
Sorry I asked

----------------------------------------------------
# Using the new (( )) compound let command
#!/bin/bash
# Scriptname: tellme2

1 echo –n "How old are you? "


read age
2 if (( age < 0 || age > 120 ))
then
echo "Welcome to our planet! "
exit 1
fi
3 if ((age >= 0 && age <= 12))
then
echo "A child is a garden of verses"
elif ((age >= 13 && age <= 19 ))
then
echo "Rebel without a cause"
elif (( age >= 19 && age <= 29 ))
then
echo "You got the world by the tail!!"

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 303

elif (( age >= 30 && age <= 39 ))


then
echo "Thirty something..."
4 else
echo "Sorry I asked"
5 fi

说明
1 用户要求输入,输入将被赋值给变量 age。
2 test 命令检验一个数学表达式。如果 age 小于 0 或者大于 120,echo 命令就执行,程序终止,退
出状态值为 1。进入交互状态。
3 test 命令检验一个数学表达式。如果 age 大于等于 0 或者小于 12,test 命令就返回退出状态值 0,
表示真,执行 then 后面的语句。否则就跳转到 elif,如果失败就跳转到下一个 elif。
4 else 结构是默认部分。如果上面的判断全部为假,就执行 else 后面的语句。
5 fi 终止 if 语句。

9.5.5 文件检验
在写脚本的时候经常的出现这样的情况——需要一个带有特定权限、特定类型或者特
定属性的特定文件。你会发现,文件检验是写依赖性(dependable)脚本中非常必要的部
分。见表 9.5。

表 9.5 文件检验操作符

检验操作符 检验是否为真
文件检验
-b filename 特定块文件
-c filename 特定字符文件
-d filename 目录存在
-e filename 文件存在
-f filename 非目录普通文件存在
-G filename 文件存在并属于一个有效的 GID
-g filename 设置 Set-group-ID
-k filename 粘滞位设置
-L filename 文件是一个符号链接
-p filename 文件是一个管道
-O filename 文件存在并属于一个有效的 UID
-r filename 文件可读
-S filename 文件是一个套接字
-s filename 文件尺寸非 0
-t fd fd(文件描述符)已经在终端上打开
-u filename 设置 Set-user-ID
-w filename 文件可写
-x filename 文件可执行

PDF created with pdfFactory Pro trial version www.pdffactory.com


304 第9章

实例 9.26
(The Script)
#!/bin/bash
# Using the old style test command
# filename: perm_check
file=./testing

1 if [ -d $file ]
then
echo "$file is a directory"
2 elif [ -f $file]
then
3 if [ -r $file –a –w $file _a _x $file]
then # nested if command
echo "you have read,write,and execute\
permission on $file."
4 fi
5 else
echo "$file is neither a file nor a directory."
6 fi
--------------------------------------------------------------a
Using the new compound operator for test (( ))

#!/bin/bash
# filename: perm_check2
file=./testing

1 if [[ -d $file ]]
then
echo "$file is a directory"
2 elif [[ -f $file ]]
then
3 if [[ -r $file $$ -w $file && -x $file ]]
then # nested if command
echo "you have read,write,and execute \
permission on $file."
4 fi
5 else
echo "$file is neither a file nor a directory."
6 fi

说明
1 如果文件 testing 是一个目录就打印“testing is a directory.”。
2 如果文件 testing 不是一个目录,elseif 就判断是否是一个文本文件,如果是就执行 then……。
3 判断文件 testing 是否可读并可写,执行 then……。
4 fi 终止内部 if。
5 如果第一行和第二行的 if 都不是真就执行 else 后面的命令块。
6 这个 fi 与第一个 if 配合使用。

a. 新的带有复合操作符的判断不能运行在 bash 2.x 以前版中。

9.5.6 null 命令

null 命令用冒号表示,是一个内建的什么都不做的命令,返回状态值为 0。如果在 if 命

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 305

令后面没有内容,同时又要避免产生错误信息,就需要在 then 后面写 null 语句。通常 null


命令作为 loop 命令的参数来建立一个无限循环。

实例 9.27

(The Script)
#!/bin/bash
# filename: name_grep
1 name=Tom
2 if grep "$name" databasefile >& /dev/null
then
3 :
4 else
echo "$1 not found in databasefile"
exit 1
fi

说明
1 字符串 Tom 被赋值给变量 name。
2 用 if 命令检验 grep 的退出状态。如果在数据库中找到 Tom,null 命令,也就是冒号被执行(也
是什么也不做) 。标准输出和标准错误都被重新定向到 /dev/null。
3 冒号就是 null 命令,除了返回一个 0 以外什么也不做。
4 如果 Tom 没有找到就打印错误信息并退出。如果 grep 失败,else 后面的命令就被执行。

实例 9.28

(The Command line)


1 $ DATAFILE=
2 $ : ${DATAFILE:=$HOME/db/datafile}
$ echo $DATAFILE
/home/jody/ellie/db/datafile
3 $ : ${DATAFILE:=$HOME/junk}
$ echo $DATAFILE
/home/jody/ellie/db/datafile

说明
1 变量 DATAFILE 被赋值为 null。
2 冒号是一个“什么也不做”命令。修改符号(:=)返回一个可以赋值给变量或者用做判断的值。
在这个例子中,表达式把一个参数传递给“什么也不做”命令。Shell 进行命令替换,也就是如
果 DATAFILE 没有值,就把路径赋值给它。变量 DATAFILE 被设置为一个固定值。
3 因为变量已经被设置,所以不能被修改符右边的值重新设置。

实例 9.29

(The Script)
#!/bin/bash
1 # Script name: wholenum
# purpose:The expr command tests that the user enters an
# integer

PDF created with pdfFactory Pro trial version www.pdffactory.com


306 第9章

#
echo "Enter a number."
reab number
2 if expr "$number" + 0 >& /dev/null
then
3 :
else
4 echo "You did not enter an integer value."
exit 1
5 fi

说明
1 要求用户输入一个整数,将该数字赋值给变量 number。
2 用 expr 命令给表达式赋值。如果执行了加法且 expr 命令返回一个成功退出的状态。则所有的输
出都将重新定向到位存储/dev/null 中。
3 如果 expr 成功,它返回退出状态值 0,冒号命令什么也不做。
4 如果 expr 失败,它返回非 0 的退出状态值,执行 echo 命令然后退出程序。
5 fi 终止 if 命令。

9.5.7 case 命令
case 命令是一个多路分支判断语句,可以用来替换 if/elif 结构。case 命令会尝试用变量
匹配 value1、value2……直到匹配找。一旦一个值匹配了 case 变量,就执行这个值后面的语
句直到两个分号为止。然后就从 esac(就是 case 的反向拼写)后面开始执行。
如果 case 变量没有被匹配,程序就执行*)后面的语句,直到遇到;或者 esac 为至。*)
的作用跟在 if/elif 中的 else 的作用是一样的。case 值中允许出现 Shell 通配符和竖线(管道)
作为 OR 操作符。

格式

case variable in
value1)
command(s)
;;
value2)
command(s)
;;
*)
command(s)
;;
esac

实例 9.30

(The Script)
#!/bin/bash
# Scriptname: xcolors
1 echo –n "Choose a foreground color for your xterm window: "
2 read color
2 case "$color" in
3 [Bb]l??)

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 307

4 xterm –fg blue –fn terminal &


5 ;;
6 [Gg]ree*)
xterm –fg darkgreen –fn terminal &
;;
7 red | orange) # The vertical bar means "or"
xterm –fg "$color" –fn terminal &
;;
8 *)
xterm –fn terminal
;;
9 esac
10 echo "Out of case command"

说明
1 要求用户输入,输入赋值给变量 color。
2 case 命令给表达式$color 赋值。
3 如果变量 color 以 B 或者 b 开头,然后是 l 和任意两个字符,这个表达式就匹配第一个值,该值
以右半括号结束。通配符是 Shell 用在文件名表达式中的元字符。xterm 命令设置前台的颜色为蓝
色。
4 如果第三行的值匹配表达式就执行语句。
5 这个命令块的最后需要双分号。当执行到分号就控制分支到第 0 行。如果分号在本行内,脚本就
比较容易除错。
6 如果表达式以 G 或者 g 开头,然后是 ree 和 0 个或者多个任意字符,就执行 xterm 命令。双分号
结束这个命令块,并控制分支到第 10 行。
7 竖线用做 OR 操作符。如果 case 表达式匹配 red 或者 orange,就执行 xterm。
8 这是默认值。如果表达式没有匹配上面任何值,就执行*)后面的命令。
9 esac 终止 case 命令。
10 在某个 case 值被匹配后,就从这里开始继续执行。

用 here 文档和 case 命令建立菜单。here 文档和 case 命令通常一起使用。here 文档用


来建立在屏幕上显示出来的菜单选项。用户被要求在其中选择,case 命令用来检验用户的选
择并执行相应的命令。

实例 9.31

(From the .bash_profile File)


echo "Select a terminal type: "
1 cat <<- ENDIT
1) linux
2) xterm
3) sun
2 ENDIT
3 read choice
4 case "$choice"in
5 1) TERM=linux
export TERM
;;
2) TERM=xterm
export TERM
;;
6 3) TERM=sun
export TERM
;;

PDF created with pdfFactory Pro trial version www.pdffactory.com


308 第9章

7 esac
8 echo "TERM is $TERM."

(The Output)
$ . .bash_profile
Select a terminal type:
1) linux
2) xterm
3) sun
2 <-- User input
TERM is xterm.

说明

1 如果把这段脚本放在.bash_profile 文件中,在登录时,你将有机会从菜单中选择恰当的终端类型。
here 文档用来建立菜单的选项。
2 用户定义的 ENDIT 终止符表示 here 文档的末尾。
3 read 命令把用户输入保存到变量 TERM 中。
4 case 命令把变量 TERM 与 1),2),*)后面的值比较。
5 第一个检验的值为 1,如果匹配,终端设置为 Linux,TERM 变量被输出以便自 Shell 可以继承它。
6 默认值不是必须的,TERM 变量通常在/etc/profile 中预先设置。如果选项是 3,终端类型被设置
为 sun。
7 esac 终止 case 命令。
8 case 结束以后,执行这一行。

9.6 循 环 命 令
循环命令就是反复执行一个命令或者一组命令,直到完成事前设置好的次数或者达到某
种条件。bash Shell 有三种循环:for 循环、while 循环和 until 循环。

9.6.1 for 命令
for 循环命令用于根据项目清单确定的次数执行命令。例如,你可以根据文件或者用户
名清单执行相同的命令。for 命令后面紧跟着用户自定义变量——关键字 in,然后是一个单
词清单。第一次执行循环,单词列表中的第一个单词被赋值给变量。一旦单词被赋值给赋值
变量,就进入循环体,执行关键字 do 和 done 之间的命令。下一次的循环,第二个单词被赋
值给变量,如此继续。循环体,由 do 开始到 done 结束。当清单中的所有单词都轮换过一次
以后,循环结束,程序控制继续 done 后面的语句。

格式

for variable in word_list


do
command(s)
done

实例 9.32

(The Script)

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 309

#!/bin/bash
# Scriptname: forloop
1 for pal in Tom Dick Harry Joe
2 do
3 echo "Hi $pal"
4 done
5 echo "Out of loop"

(The Output)
$ forloop
Hi Tom
Hi Dick
Hi Harry
Hi Joe
Out of loop

说明
1 这个 for 循环从名字列表开始直到结束每个单词都循环一次。所有的单词都轮换过一次以后,单
词列表就变为空。循环在 done 处结束并从这里开始继续执行。 第一次循环变量 pal 被赋值为 Tom。
第二次循环变量 pal 被赋值为 Dick。第三次循环变量 pal 被赋值为 Harry。最后次循环变量 pal 被
赋值为 Joe。
2 关键字 do 出现在单词列表后面,如果用在同一行,它们之间就需要用分号分隔。如:
For pal in Tom Dick Harry Joe; do
3 这是一个循环体。在 Tom 被赋值给变量 pal 以后,循环体的命令就开始执行。
4 关键字 done 结束循环。一旦单词列表中的最后一个单词被赋值给变量并从列表中清除以后,循
环退出,并从第二行开始执行。
5 循环退出以后,在这里继续控制。

实例 9.33

(The Command Line)


1 $ cat mylist
tom
patty
ann
jake

(The Script)
#!/bin/bash
# Scriptname: mailer
2 for person in $(cat mylist)
do
3 mail $person < letter
echo $person was sent a letter.
4 done
5 echo "The letter has been sent."

说明
1 显示 myfile 文件的内容。
2 命令替换以后, 文件 myfile 的内容变为一个单词列表。
在第一次循环中,tom 被赋值给变量 person,
然后逐次轮换为 patty 等其他单词替代。

PDF created with pdfFactory Pro trial version www.pdffactory.com


310 第9章

3 在循环体内给每一个用户发送一份 letter 文件拷贝。


4 done 结束循环。
5 当给所有用户发送了 mail 后,循环结束并退出,执行这一行。

实例 9.34

(The Script)
#!/bin/bash
# Scriptname: backup
# purpose:
# Create backup files and store them in a backup directory
#
1 dir=/home/jody/ellie/backupscripts
2 for file in memo[1-5]
do
3 if [ -f $file ]
then
cp $file $dir/$file.bak
echo "$file is backed up in $dir"
fi
4 done

(The Output)
memo1 is backed up in /home/jody/ellie/backupscripts
memo2 is backed up in /home/jody/ellie/backupscripts
memo3 is backed up in /home/jody/ellie/backupscripts
memo4 is backed up in /home/jody/ellie/backupscripts
memo5 is backed up in /home/jody/ellie/backupscripts

说明
1 变量 dir 被赋值为保存备份脚本的目录路径。
2 单词列表里包含当前目录中所有 memo 开头,并以 1~5 之间的数字结尾的文件名。在循环中,
每个单词都会被赋值给变量 file 一次。
3 进入循环体以后,文件名被检验是否存在,是否是一个真正的文件,如果是就把她拷贝到
/home/jody/ellie/backupscripts 下,并在现在的名字后面追加.bak 为扩展名。
4 关键字 done 结束循环。

单词列表中的$@和$*变量。在不使用双引号的时候是一样的。$*的值是一个字符串,
而$@的值是一组分开的单词。

实例 9.35
(The Script)
#!/bin/bash
# Scriptname: greet
1 for name in $* # same as for name in $@
2 do
echo Hi $name
3 done
(The Command Line)
$ greet Dee Bert Lizzy Tommy
Hi Dee
Hi Bert
Hi Lizzy

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 311

Hi Tommy

说明
1 $*和$@把单词列表扩展到位置参量。在这个例子中,参数从命令行传递给脚本:Dee、Bert、Lizzy
和 Tommy。每一个列表中的名字都会按照顺序赋值给循环中的变量 name。
2 执行循环体命令直到单词列表为空。
3 关键字 done 结束循环体。

实例 9.36

(The Script)
#!/bin/bash
# Scriptname:permx
1 for file # Empty wordlist
do

2 if [ -f $file –a ! –x $file ] or if [[ -f $file && ! –x $file ]]a


then

3 chmod +x $file
echo $file now has execute permission
fi
done
(The Command Line)
4 $ permx *
addon now has execute permission
checkon now has execute permission
doit now has execute permission

说明
1 如果 for 循环没有单词列表,它就自动循环位置参量。这跟 for file in $*是一样的。
2 从命令行取得文件名。Shell 通配符表示当前目录下所有的文件名。如果一个文件是文本文件且
没有执行属性,就执行第三行的命令。
3 给处理的每一个文件加上执行属性。
4 在命令行,星号意味着 Shell 通配符表示当前目录下的所以文件。这些文件名将作为参数传递给
脚本 permx。

a. 只在 bash 2.x 版本中有效。

9.6.2 while 命令
while 命令判断它后面的命令,如果退出状态值是 0,就执行循环体内的命令,
直到 done,
控制返回循环体的顶部,while 命令将再次检验命令的退出状态,直到退出状态值为非 0,
程序继续执行 done 后面的语句。

格式

while command
do
command(s)
done

PDF created with pdfFactory Pro trial version www.pdffactory.com


312 第9章

实例 9.37

(The Script)
#!/bin/bash
# Scriptname: num
1 num=0 # Initialize num
2 while (( $num < 10 ))a # or while [ num –lt 10 ]
do
echo –n "$num "
3 let num+=1 # Increment num
done
4 echo –e "\nAfter loop exits, continue running here"

(The Output)
0 1 2 3 4 5 6 7 8 9
4 After 1oop exits, continue running here

说明
1 这是初始步骤,变量 num 被赋值为 0。
2 while 命令后面跟一个 let 命令,let 命令对数学表达式求值,如果条件为真就返回退出状态值 0,
也就是说,如果 num 的值小于 10 就进入循环体。
3 在循环体内部 num 的值增加 1,如果 num 的值不改变,循环将一直执行下去,直到这个进程被
杀掉。
4 在循环退出以后,echo 命令打印换行符和字符串。

a. bash 2.x 版本使用这种形式。

实例 9.38
(The Script)
#!/bin/bash
# Scriptname: quiz
1 echo "Who was the 2nd U.S. president to be impeached?"
read answer
2 while [[ "$answer" != "Bill Clinton" ]]
3 do
echo "Wrong try again!"
4 read answer
5 done
6 echo you got it!

(The Output)
$ quiz
Who was the 2nd U.S. president to be impeached? Ronald Reagon
Wrong try again!
Who was the 2nd U.S. president to be impeached? I give up
Wrong try again!
Who was the 2nd U.S. president to be impeached? Bill clinton
You got it!

说明
1 echo 命令提示用户:“who was the 2nd U.S. president to be impeached ?”,read 命令等待用户输入,
并把输入保存在变量 answer 中。
2 进入 while 循环,使用 test 命令,
也就是括号,检验表达式。 如果变量 answer 没有恰好是 Bill Clinton,

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 313

就进入循环体执行在 do 和 done 之间的命令。


3 关键字 do 是循环体的开始。
4 用户被要求在次输入。
5 done 关键字表示循环体的结束。控制返回循环体的顶部,再次检测表达式。如果用户输入的不
是 Bill Clinton,循环就继续。直到用户输入 Bill Clinton,就退出循环,程序控制跳转到第 6 行。
6 当循环体结束结束以后,从这里开始继续执行。

实例 9.39

(The Script)
$ cat sayit
#!/bin/bash
# Scriptname: sayit
echo Type q to quit.
go=start
1 while [ -n "$go" ] # Make sure to double quote the variable
do
2 echo –n I love you.
3 read word
4 if [[ $word == [Qq] ]]
then # [ "$word" = q –o "$word" = Q ] Old style
echo "I'll always love you!"
go=
fi
done
(The Output)
$ sayit
Type q to quit
I love you. When user presses the enter key, the program
continues
I love you.
I love you.
I love you.
I love you.q
I'll always love you!
$

说明
1 执行 while 后面的命令并检验退出状态。test 命令的-n 表示检验一个非空字符串。因为如果 go 有
一个初始化的值,检验就成功,返回退出状态值 0。如果 go 没有被双引号引用,变量值就为空,
test 命令就显示信息:
go : test : argument expected
2 进入循环体,打印字符串 I love you 到屏幕。
3 read 命令等待用户的输入。
4 检验表达式。如果用户输入 Q 或者 q,就显示“I’ll always love you !”,并把变量 go 赋值为空。
当再次进入 while 循环,因为变量为空所以检验失败,循环结束。控制跳转到关键字 done 后面。
在这个例子中,脚本因为没有更多的行需要执行而结束。

9.6.3 until 命令
until 命令的用法跟 while 命令的用法类似,只是在 until 后面的语句为假的时候执行循环
体,例如一个命令的退出状态值返回为非 0。当到达 done 关键字以后就自动返回循环体的

PDF created with pdfFactory Pro trial version www.pdffactory.com


314 第9章

顶部,until 命令再次检验命令的退出状态值。循环一直继续,直到 until 后面的命令的退出


状态值为 0。当退出状态值为 0 时,循环退出,程序从 done 后面继续。

格式

until command
do
command(s)
done

实例 9.40

#!bin/bash
1 until who | grep linda
2 do
sleep 5
3 done
talk linda@dragonwings

说明
1 until 命令检验管道最后一个命令 grep 的退出状态值。who 命令列出登录的所有用户名并通过管
道把输出传递给 grep。当找到用户 linda 的时候,grep 就返回 0。
2 如果用户 linda 没有登录,就进入循环体休眠 5 秒钟。
3 当 linda 登录,且 grep 的退出状态值为 0,控制就将跳转到 done 以后,并执行后面的语句。

实例 9.41

(The Script)
$ cat hour
#!/bin/bash
# Scriptname:hour
1 let hour=0
2 until (( hour > 24 ))a # or [ $hour –gt 24 ]
do
3 case "$hour" in
[0-9]|1[0-1])echo "Good morning!"
;;
12) echo "Lunch time."

;;
1[3-7])echo "Siesta time."
;;
*) echo "Good night."
;;
esac
4 let hour+=1 # Don't forget to increment the hour
5 done

(The Output)
$ hour
Good morning!
Good morning!
...
Lunch time.
Siesta time.

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 315

...
Good night.
...

说明
1 变量 hour 初始化为 0。
2 let 命令检验数学表达式,看 hour 是大于还是等于 24。如果 hour 不大于或者等于 24,就进入循
环体,如果 until 后面的命令返回非 0 退出状态值,就进入 until 循环体。循环一直继续,直到循
环 until 后面的条件为真。
3 case 命令对于变量 hour 求值并检验 case 后面的语句是否与变量匹配。
4 在控制跳转到循环体顶部以前,let 命令使变量 hour 增加 1。
5 done 标志着循环的结束。

a.bash 2.x 使用这种形式。

9.6.4 select 命令和菜单


here 文档是建立菜单的简单方法,但是 bash 介绍了另外一种循环机制,叫作 select 循
环,主要的作用就是建立菜单。一个数字化的菜单显示在标准错误上,PS3 用来提示用户输
入。默认的 PS3 是#?。在 PS3 提示显示以后,Shell 就等待用户输入。输入的是该菜单中的
数字,若输入被保存在指定变量 REPLY 中,则变量 REPLY 中的数字与括号右边选项清单中
的字符串有着对应关系。
case 命令与 select 命令连用,允许用户在菜单中选择,基于选择执行相应的命令。LINES
和 COLUMNS 变量可以用来决定在终端显示的菜单的层。 (在 bash2.x 版本中,这两个变量
是内建的,但是在早期版本中却不是,如果它们没有被预先定义,则可以在.bash_profile 文
件中定义和输出它们)输出显示在标准错误上,每一个选项前都有数字和右括号,PS3 提示
显示在菜单的底部。因为 select 命令是一个循环命令,因此一定要记得用 break 命令退出循
环或者 exit 命令退出脚本。

格式
select var in wordlist
do
command(s)
done

实例 9.42
(The Script)
#!/bin/bash
# Script name: runit
1 ps3="Select a program to execute: "
2 select program in 'ls –F' pwd date
3 do
4 $program
5 done
(The Command Line)
Select a program to execute:2
1) ls -F
2) pwd

PDF created with pdfFactory Pro trial version www.pdffactory.com


316 第9章

3) date
/home/ellie
Select a program to execute: 1
1) ls -F
2) pwd
3) date
12abcrty abc12 doit* progs/ xyz
Select a program to execute: 3
1) ls -F
2) pwd
3) date
Sun Mar 12 13:28:25 PST 2000

说明
1 PS3 所包含的字符串将显示在 select 循环所建立的菜单底部。这个提示符默认是$#,并通过标准
错误输出到屏幕。
2 select 循环由一个变量和三个显示菜单上的单词列表组成,它们是 ls –F、pwd 和 date。这个单词
列表中的单词都是 Linux 中的命令,它们也可以是其他的命令。如果单词中有空格就需要用引号
引用。
3 do 关键字表示 select 循环体的开始。
4 当用户选择菜单中的选项的时候,这个选项的号码就与这个选项号码括号右边的单词值对应起
来。例如,若选择 2,则与 2 关联的 pwd 被赋值给变量 program。$program 执行该命令。
5 关键字 done 表示 select 循环体语句的结束。流控制就返回到循环体的顶部。这个循环将一直执
行到用户输入 C 为止。

实例 9.43

(The Script)
#!/bin/bash
# Scriptname name: goodboys
1 PS3="Please choose one of the three boys : "
2 select choice in tom dan guy
3 do
4 case $choice in
tom)
echo Tom is a cool dude!
5 break;; # break out of the select loop
6 dan | guy )
echo Dan and Guy are both wonderful.
break;
*)
7 echo "$REPLY is not one of your choices" 1>&2
echo "Try again."
;;
8 esac
9 done
(The Command Line)

$ goodboys
1) tom
2) dan
3) guy
Please choose one of the three boys : 2
Dan and Guy are both wonderful.

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 317

$ goodboys
1) tom
2) dan
3) guy
Please choose one of the three boys : 4
4 is not one of your choices
Try again.
Piease choose one of the three boys : 1
Tom is a cool dude!
$

说明
1 select 循环在第二行命令建立的菜单上面打印 PS3 提示符。
2 进入 select 循环体,它按照数字顺序显示列表中的单词。
3 循环体从这里开始。
4 当一个值被跳过,下一个值成为列表中的第一个值时,choice 变量被赋值为列表中的第一个值。
5 break 命令控制循环跳到第 9 行。
6 如果 dan 或 guy 被选择,就执行紧跟着的 echo 后面的命令,然后跳到第 9 行。
7 内建变量 REPLY 包含当前列表项目的数字。1、2 或者 3。
8 标志 case 命令的结束。
9 done 表示 select 命令的结束。

实例 9.44

(The Script)
#!/bin/bash
# Script name: ttype
# Purpose: set the terminal type
# Author: Andy Admin
1 COLUMNS=60
2 LINES=1
3 PS3="Please enter the terminal type: "
4 select choice in wyse50 vt200 xterm sun
do
5 case $REPLY in
1)
6 export TERM=$choice
echo "TERM=$choice"
break;; # break out of the select loop
2 | 3 )
export TERM=$choice

echo "TERM=$choice"
break;;
4)
export TERM=$choice
echo "TERM=$choice
break;;
*)
7 echo –e "$REPLY is not a valid choice. Try again\n" 1>&2
8 REPLY= # Causes the menu to be redisplayed
;;
esac
9 done

PDF created with pdfFactory Pro trial version www.pdffactory.com


318 第9章

(The Command Line)


$ ttype
1) wyse50 2) vt200 3) xterm 4) sun
Please enter the terminal type : 4
TERM=sun

$ ttype
1) wyse50 2) vt200 3) xterm 4) sun
Please enter the terminal type : 3
TERM=xtem

$ ttype
1) wyse50 2) vt200 3) xterm 4) sun
Please enter the terminal type : 7
7 is not a valid choice. Try again.
1) wyse50 2) vt200 3) xterm 4) sun
Please enter the terminal type :2
TERM=vt200

说明
1 变量 COLUMNS 表示在终端上显示的用 select 命令建立的菜单的列宽度。默认值是 80。
2 变量 LINES 控制菜单在终端上的垂直显示。默认显示 24 行。当把这个变量的值改为 1 时,整个
菜单将显示在第 1 行上,而不是像上一个例子中的那样垂直显示。
3 在菜单选项的下面显示 ps3 提示符。
4 select 循环将打印一个有 4 个选项的菜单:wyse50、vt200、vt100 和 sun。变量 choice 将根据用户
的反应和保存在 REPLY 变量中的值不同而不同。 例如 REPLY 中是 1,wyse50 就被赋值给 choice。
REPLY 中是 2,vt200 就赋值给 choice;REPLY 中是 3,xterm 就赋值给 choice;如果 REPLY 中
是 4,sun 就被赋值给 choice。
5 REPLY 变量保存用户选择。
6 终端类型被赋值、输出和打印。
7 如果用户没有输入 1 和 4 之间的值,他将被提示再次输入,注意,菜单中将出现 PS3 提示符。
8 REPLY 如果为空,就再次显示菜单。
9 select 循环结束。

9.6.5 looping 命令
在某些情况下,需要中断循环并回到循环的顶部,或者需要一种方法结束当前的循环,
bash 提供了一个命令来处理这些情况。
shift 命令。shift 命令用来把参量列表位移指定次数。没有参数的 shift 把参数变量表向
左位移一位。一旦位移发生,被位移出列表的参数就被永远删除了。通常在 while 循环中,
shift 用来读取列表中的参量。

格式
shift [n]

实例 9.45

(Without a Loop)
(The Script)

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 319

#!/bin/bash
# Scriptname: shifter
1 set joe mary tom sam
2 shift
3 echo $*
4 set $(date)
5 echo $*
6 shift 5
7 echo $*
8 shift 2

(The Output)
3 mary tom sam
5 Thu Mar 16 10:00:12 PST 2000
7 2000
8 shift: shift count must be <= $#

说明
1 set 命令设置位置参量。$1 是 joe,$2 是 mary,$3 是 tom,$4 是 sam,而$*表示所有的参量。
2 shift 命令将参量向左位移,joe 被删除。
3 位移以后打印参量。
4 set 命令重新设置位置参量为 Linux date 命令的输出。
5 打印新的参量列表。
6 向左位移 5 次。
7 打印新的参量列表。
8 尝试位移多于参量总数的次数,Shell 打印错误信息到标准错误。$#表示参量的总个数。

实例 9.46
(With a Loop)
(The Script)
#!/bin/bash
# Name: doit
# Purpose: shift through command line arguments
# Usage: doit [args]
1 while (( $# > 0 ))a # or [ $#-gt 0 ]
do
2 echo $*
3 shift
4 done

(The Command Line)


$ doit a b c d e
a b c d e
b c d e
c d e
d e
e

说明

1 用 let 命令判断数学表达式。如果位置参量的个数大于 0,就进入循环体。位置参量来自于命令


行参数,一共有 5 个。
2 打印所有的位置参量。

PDF created with pdfFactory Pro trial version www.pdffactory.com


320 第9章

3 位置参量列表向左位移一次。
4 循环体在这里结束。控制返回到循环的顶部。每次进入循环体,shift 命令都使参量列表中的值减
少 1。在第一次位移以后,$#的值为 4。当$#的值为 0,循环就结束。

a. 不能在 Bash 2.x 以前版本中运行。

实例 9.47
(The Script)
#!/bin/bash
# Scriptname: dater
# purpose: set positional parameters with the set command
# and shift through the parameters.

1 set $(date)
2 while (( $# > 0 )) # or [ $# -gt o ] Old style
do
3 echo $1
4 shift
done

(The output)
$ dater
Wed
Mar
15
19:25:00
PST
2000

说明
1 set 命令把 date 命令的输出赋值给位置参量$1~$6。
2 while 命令判断位置参量的个数是否大于 0,如果为真就进入循环体。
3 echo 显示第一个参量的值。
4 shift 命令把参量列表向左位移一次。每次循环都位移一次直到参量列表为空。这个时候,$#为 0,
循环中断。

break 命令。内建 break 命令用来从循环中强行退出,但是不退出程序。执行 break 以


后,控制从关键字 done 开始执行。break 命令可以从内层循环中退出,所以如果你使用循环
嵌套的话,break 可以用一个数字作为参数,允许你指定 break 强行退出的循环的层数。如
果你有三层嵌套循环,最外面的循环数是 1,中间层的循环是 2,最里面的循环数为 3。在
退出无限循环的时候,break 非常有用。

格式
break[n]

实例 9.48
#!bin/bash
# Scriptname: loopbreak
1 while true; do
2 echo Are you ready to move on\?
read answer
3 if [[ "$answer" == [Yy] ]]

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 321

then
4 break
5 else
....commands...
fi
6 done
7 print "Here we are"

说明
1 true 命令是 Linux 中一个退出状态值永远为 0 的命令。它经常被用来启动一个无限循环。只要使
用分号分隔这些命令,就可以把 do 语句和 while 命令放在同一行。
2 要求用户输入,输入被保存在变量 answer 中。
3 如果$answer 的值是 Y 或者 y,就跳转到第 4 行。
4 执行 break 命令,循环退出,控制跳转到第 7 行,打印 Here we are is。除非用户回答 Y 或者 y,
否则程序就继续要求用户输入,永远一直下去。
5 如果第三行判断失败,就执行 else 命令后面的语句。循环体在关键字 done 处结束。控制跳转到
第 1 行的 while。
6 循环体结束。
7 执行 break 命令以后从此处开始执行。

continue 命令。如果某些条件为真,continue 命令控制跳转到循环的顶部。所有 continue


命令后面的语句都将被忽略。如果嵌套循环,continue 命令就跳转到最内的循环的顶部。如
果一个数字作为它的参数,控制就可以在指定的任何层的循环的顶部重新开始执行。如果你
有三层循环嵌套,最外面的循环号是 3,中间层的循环号是 2,最内的循环号是 1。4
格式
Continue [n]

实例 9.49
(The mailing List)
$ cat mail_list
ernie
john
richard
melanie
greg
robin

(the Script)
#!/bin/bash
# Scriptname: mailem
# Purpose: To send a list
1 for name in $(cat mail_list)
do
2 if [[ $name == richard ]] ;then
3 continue
else
4 mail $name < memo
fi
5 done

4.如果 continue 命令后面的数字大于循环嵌套的层数,就会退出循环。

PDF created with pdfFactory Pro trial version www.pdffactory.com


322 第9章

说明
1 命令替换以后$或者‘cat mail_list’,for 循环将按照单词重复文件 mail_list 中的名字清单。
2 如果名字匹配 richard,就执行 continue 命令,控制返回循环的顶部,对表达式求值的位置。因为
richard 已经被位移出列表,下一个用户 melanie 将被赋值给变量 name,旧的风格,if [ “$name” =
richard]; then。
3 continue 命令控制返回到循环顶部,忽略其他命令。
4 除了 richard 以外,所有的用户名都被打印,并被 mail 一份 memo 文件的副本。
5 循环体结束。

嵌套循环和循环控制。当你使用嵌套循环的时候,continue 命令和 break 命令都被赋予


一个整数作为参数,控制流到第几层循环。

实例 9.50

(The Script)
#!/bin/bash
# Scriptname: months
1 for month in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
do
2 for weed in 1 2 3 4
do
echo –n "Processing the month of $month. O.K.?"
read ans
3 if [ "$ans" = n –o –z "$ans" ]
then
4 continue 2
else
echo –n "Process week $weed of $month? "
read ans
if [ "$ans" = n –o –z "$ans" ]
then
5 continue
else
echo "Now processing week $week of $month."
sleep 1
# Commands go here
echo "Done processing..."
fi
fi
6 done
7 done

(The Output)
$ months
processing the month of Jan. O.K.?
processing the month of Feb. O.K.? y
Process Week 1 of Feb? Y
Now processing week 1 of Feb.
Done Processing...
Processing the month of Feb. O.K.? y
Process Week 2 of Feb? Y
Now processing week 2 of Feb.
Done Processing...
processing the month of Feb. O.K.? n
processing the month of Mar. O.K.? n

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 323

processing the month of Apr. O.K.? n


processing the month of May. O.K.? n

说明
1 启动外层循环,第一次循环时 month 被赋值为 Jan。
2 内层循环启动。这层循环第一次变量 week 被赋值为 1。在内层循环单词列表被完全重复一遍以
后,才返回外层循环。
3 如果用户输入 n 或者回车,就执行第 4 行。
4 continue 命令带参数 2,控制流从外层第二层循环的顶部开始执行,若 continue 命令没有参数就
从最内层的循环顶部开始执行。
5 控制流返回到最内层的 for 循环。
6 done 终止最内层的循环。
7 done 终止最外层循环。

9.6.6 I/O 重新定向和子 Shell


文件中的输入可以通过管道重新定向给一个循环,输出也可以通过管道重新定向给一个
文件。Shell 启动一个自 Shell 来处理 I/O 重新定向和管道。在循环终止的时候,循环内部定
义的任何变量对于脚本的其他部分来说都是不可见的。
重新定向循环输出到一个文件。bash 循环的输出不仅可以定向给屏幕,也可以定向给一
个文件。见实例 9.51。

实例 9.51

(The Command Line)


1 $ cat memo
abc
def
ghi

(The Script)
#!/bin/bash
# program name: numberit
# put line numbers on all lines of memo
2 if let $(( $# < 1 ))
then
3 echo "Usage: $0 filename " >&2
exit 1
fi
4 count=1 # Initialize count
5 cat $1 | while read line
# Input is coming from file provided at command line
do
6 let $((count == 1)) && echo "Processing file $1..." > /dev/tty
7 echo –e "$count\t$line"
8 let count+=1
9 done > tmp$$ # Output is going to a temporary file
10 mv tmp$$ $1

(The Command Line)


11 $ numberit memo

PDF created with pdfFactory Pro trial version www.pdffactory.com


324 第9章

Processing file memo...

12 $ cat memo
1 abc
2 def
3 ghi

说明
1 显示文件 memo 的内容。
2 在运行脚本的时候如果用户没有提供命令行参数,参数个数将小于 1,并打印错误信息。
3 如果参数个数少于 1,用法信息被发送到 stderr。
4 count 变量的值为 1。
5 Linux 的 cat 命令显示名字保存在 $1 中的文件的内容。输出被重新定向给 while 循环。在第一次
循环时,read 命令读取文件的第 1 行,第 2 次循环读取第二行,依次类推。若读取成功就返回 0
否则返回 1。
6 如果 count 的值是 1,就执行 echo 命令,将输出发送到/dev/tty,即屏幕。
7 echo 命令在文件的行的后面打印 count 的值。
8 count 增加 1。
9 整个循环的输出,文件$1 中的每一行除了第一行,都重新定向到文件 tmp$$,第一行被重新定向
到终端,/dev/tty。a
10 tmp 文件被重新命名为$1 中的文件名。
11 执行程序,将被处理的文件名叫作 memo。
12 脚本执行结束以后显示文件 memo 的内容,每一行前面都被加了行号在前面。

a.$$表示当前 shell 的 PID(进程 ID 号)


、通过在文件名末尾追加这样的数字,保持文件名的惟一性。

通过管道重新定向循环的结果到一个 Linux 命令。输出可以通过管理给另一个命令或重


新定向到一个文件。
实例 9.52

(The Script)
#!/bin/bash
1 for i in 7 9 2 3 4 5
2 do
echo $i
3 done | sort -n

(The Output)
2
3
4
5
7
9

说明
1 for 循环重复一组没有分类的数字。
2 在循环体内打印数字,输出被重新定向给 Linux sort 命令,做数字分类。
3 关键字 done 创建管道,在子 Shell 中运行循环。

在后台运行循环。循环可以在后台运行,程序可以不等待循环的结束而连续运行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 325

实例 9.53
(The Script)
#!/bin/bash
1 for person in bob jim joe sam
do
2 mail $person < memo
3 done &

说明
1 for 循环重复列表中的每一个名字:bob、jim、joe 和 sam。每一个名字都按照顺序赋值给变量 person。
2 在循环体内向每一个用户发送一份包含文件 memo 副本的邮件。
3 关键字 done 后面的&使得循环在后台运行。在循环运行的同时,程序继续运行。

9.6.7 IFS 和循环


Shell 的内部域分隔符可以是空格、制表符和换行符。它可以作为命令的分隔符用在例
如 read、set 或 for 等命令中。如果在列表中使用不同的分隔符,用户可以自己定义这个符号。
在修改以前把 IFS 原始符号的值保存在另外一个变量中是一个好主意,这样做可以在需要时
恢复成默认值。

实例 9.54

(The Script)
$ cat runit2
#/bin/bash
# Script is called runit.
# IFS is the internal field separator and defaults to
# spaces, tabs, and newlines.
# In this script it is changed to a colon.
1 names=Tom:Dick:Harry:John
2 oldifs="$IFS" # save the original value of IFS
3 IFS=":"
4 for persons in $names
do
5 echo Hi $persons
done
6 IFS="$oldifs" # reset the IFS to old value
7 set Jill Jane Jolene # set positional parameters
8 for girl in $*
do
9 echo Howdy $girl
done

(The Output)
$ runit2
5 Hi Tom
Hi Dick
Hi Harry
Hi John
9 Howdy Jill
Howdy Jane
Howdy Jolene

PDF created with pdfFactory Pro trial version www.pdffactory.com


326 第9章

说明
1 变量 names 被赋值为 Tom:Dick:Harry:John。每个单词之间依靠冒号分隔。
2 IFS 的原始值,空格赋值给另外一个变量 oldifs。因为 IFS 的值是空格因此需要引号引用。
3 把 IFS 赋值为冒号。现在冒号可以作为分隔符了。
4 变量替换以后,for 循环重复使用冒号作为内部域分隔符每一个名字。
5 显示列表中的每一个名字。
6 还原 IFS 为原来的值。
7 设置位置参量$1 为 Jill,$2 为 Jane,$3 为 Jolene。
8 $*表示所有的位置参量。for 循环依次把这些名字赋值给变量 girl。
9 显示参量中的每一个名字。

9.7 函 数
函数是在 ATT 的 UNIX System VR2 版本开始引入到 Bourne Shell 中的,并在 Bourne
Again Shell 中得到强化。函数就是一个命令或者一组命令的名字。函数可以使程序模块化并
提高效率,可以就在当前的 Shell 环境中执行。换而言之,在执行像 ls 这样的可执行程序时
并不产生自进程。你甚至可以把函数保存在文件中,而在准备使用时候再把它们装入脚本。
下面是我们在使用函数时需要注意的重要规则。
1.Shell 可以决定是执行一个别名、函数、内建命令,还是一个基于磁盘的可执行文件。
它总是先执行别名,然后是函数、内建命令,最后才可执行程序。
2.函数在使用前必须定义。
3.函数在当前环境下运行。它跟调用它的脚本分享变量,并通过位置参量传递参数。
通过 local 函数可以在函数内部建立本地变量。
4.如果你在函数中使用 exit 命令,则可以退出了整个脚本。如果函数退出,就返回到
脚本中调用该函数的地方。
5.函数中的 return 命令返回函数中最后一个命令的退出状态值或者给定的参数值。
6.使用内建命令 export –f 可以把函数输出给子 Shell。
7.使用内建命令 declare –f 可以显示定义的函数清单。如果只显示函数的名字,可以
用命令 declare –F。5
8.像变量一样,函数内部陷阱是全局的。它们可以被脚本和脚本激活的函数共享。如
果一个陷阱被定义为函数,它就可以被脚本共享。但是它可能产生意想不到的效果。
9.如果函数保存在其他的文件中,就必须通过 source 或者 dot 命令把它们装入当前脚本。
10.函数可以递归。也就是说,函数可以调用自己,而且调用的次数没有限制。

格式
function function_name { commands ; commands; }

实例 9.55
function dir { echo "Directories: ";ls –1|awk '/^d/ {print $NF}'; }

5.只在 bash 2.x 版本中有效。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 327

说明
关键字 function 后面跟着的函数名称是 dir(有的时候函数名称后面有一对空的括号,它们并不
是必须的)。当输入 dir 以后,就执行花括号内的命令。这个函数的作用是只显示当前工作目录下的
子目录。第一个花括号两边的空格是必须的。

复位函数。使用命令 unset 从内存中删除函数

格式
unset –f function_name

输出函数。函数可以输出给子 Shell。

格式
export –f function_name

9.7.1 函数参数和返回值
因为函数可以在当前 Shell 内执行,变量对于函数和 Shell 来说都是可见的。所以在函数
内对于环境变量的任何修改都将影响 Shell。
内建的 local 函数。要创建只在函数内部使用,且当函数退出时就消失的变量,可以用
内建的函数 local 实现。
参数。通过位置参量可以向函数传递参数。位置参量对于函数来说是专用的。也就是说,
参数将不影响在函数外使用的任何位置参量。见实例 9.56。
内建的 return 函数。return 函数可以用来退出函数并返回到脚本中调用该函数的地方 (记
住,如果使用 exit 你将不仅退出函数,而且还将退出整个脚本) 。如果你没有为 return 命令
指定参数,返回的函数值就是最后一行脚本的退出状态值。如果赋值给 return 命令,则这个
值将保存在变量?中,这个值可以是 0~256 之间的一个整数。因为 return 命令限制只能返
回一个 0~256 之间的整数,因此你可以使用命令替换来捕捉函数的输出。把整个函数放在
括号内,前面是一个$,就是$(function_name)或者就像传统的捕捉 Linux 命令的输出一样,
通过引用把输出赋值给一个变量。

实例 9.56

(Passing Arguments)
(The Script)
#!/bin/bash
# Scriptname: checker
# Purpose: Demonstrate function and arguments

1 function Usage { echo "error: $*" 2>&1; exit 1; }

2 if (( $# != 2 ))
then
3 Usage "$0: requires two arguments"
fi
4 if [[ ! ( -r $1 && -w $1 ) ]]

PDF created with pdfFactory Pro trial version www.pdffactory.com


328 第9章

then
5 Usage "$1: not readable and writeable"
fi
6 echo The arguments are: $*
< Program continues here >

(Output)
$ checker
error: checker: requires two arguments

$ checker filel file2


error: filel: not readable and writeable

$ checker filex file2


The arguments are filex file2

说明
1 定义一个叫作 Usage 的函数。它用来把错误发送到标准错误(屏幕)去。它的参数是函数被调用
时的任何字符串。参数保存在特殊变量$*中,包含所有的位置参量。在函数内部,位置参量是本
地的,不影响在函数外部使用的位置参量。
2 如果从命令行传递给脚本的参数少于 2 个,程序就跳转到第 3 行。
3 当函数 usage 被调用的时候,“$0:require two arguments”就被传递给函数并保存在变量$*中。接
着,echo 语句把信息发送给标准错误。当退出状态为 1 时表示出现错误,脚本就退出。a
4,5 如果从命令行进入程序的第一个参数不是一个既可以读又可以写的文件的文件名,usage 就被调
用,其参数为“$1:not readable and writeable”。
6 从命令行进入脚本的参数保存在$*中,这对函数内的$*并没有影响。
a.按照旧版本的判断形式,该表达式应写为:
if [ : \ ( -r $1 -a -w $1 \)]。

实例 9.57

(Using the return Command)


(The Script)
$ cat do_increment
#!/bin/bash
# Scriptname: do_increment
1 increment () {
2 local sum; # sum is known only in this fumction
3 let "sum=$1 + 1"
4 return $sum # Return the value of sum to the script.
}
5 echo -n "The sum is "
6 increment 5 # Call function increment; pass 5 as a
# parameter. 5 becomes $1 for the increment
# function.
7 echo $? # The return value is stored in $?
8 echo $sun # The variable "sum" is not known here

(The Output)
$ do_increment
4,6 The sum is 6
7

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 329

说明
1 定义一个名为 increment 的函数。
2 内建函数 local 把变量 sum 定义为函数内专用变量。它在函数外部是不存在的,一旦函数退出,
它就被删除。
3 当函数被调用时,第一个函数$1 的值加 1,并被保存在变量 sum 中。
4 带有参数的内建命令 return 使得函数返回脚本中调用它的地方。它把参数保存在变量?中。
5 字符串被响应到屏幕。
6 调用函数 increment,参数是 5。
7 当函数返回时,退出状态保存在变量?中。如果 return 没有指定参数,退出状态就指的是函数中
最后一个命令的退出状态值。return 的参数可以是一个在 0 和 256 之间的整数。
8 sum 是在函数 increment 中定义的,因此它只在函数内部有效,在函数外部是不可见的,因此什
么也不打印。

实例 9.58

(Using Command Substitution)


(The Script)
$ cat do_square
#!/bin/bash
# Scriptname: do_square
1 function square {
local sq # sq is local to the fumction
let "sq=$1 * $1"
echo "Number to be squared is $1."
2 echo "The result is $sq "
}
3 echo "Give me a number to square. "
read number
4 value_returned=$(square $number) # Connand substitution
5 echo "$value_returned"

(The Output)
$ do_square
3 Give me a number to square.
10
5 Number to be squared is 10.
The result is 100

说明
1 定义函数 square。它的用处是在被调用时计算第一个参数的平方。
2 打印平方数的结果。
3 要求用户输入,程序从这一行开始执行。
4 调用函数 square,参数是用户输入的数字。因为函数包含在括号中并且其前面有$,所以运行命
令替换。函数和 echo 语句的输出都被赋值给变量 value_returned。
5 打印命令替换的返回值。

9.7.2 函数和 source(或者 dot)命令


保存函数。函数通常被定义在.profile 中,所以当你登录时,它们就被定义了。函数可以
被输出并保存在文件中。当你需要它们的时候,就可以使用 suorce 命令或者 dot 命令和文件

PDF created with pdfFactory Pro trial version www.pdffactory.com


330 第9章

名来激活在这些文件中定义的函数。

实例 9.59

1 $ cat myfunctions
2 function go() {
cd SHOME/bin/prog
PS1=''pwd' > '
ls
}
3 function greetings{} { echo "Hi $1! Welcome to my world." ; }

4 $ source myfunctions
5 $ greetings george
Hi george! Welcome to my world.

说明
1 显示文件 myfunctions,其中包含两个函数。
2 第一个函数叫作 go,它的作用是设置主提示符为当前目录。
3 第二个函数叫作 greetings,它的向名字作为参数的用户表示祝贺。
4 source 命令或者 dot 命令把包含函数的文件装入 Shell 内存。
现在两个函数在当前 Shell 中都可用了。
5 调用并执行 greeting 函数。

实例 9.60

(The .dbfunctions file shown below contains functions to be used by the main
program. See cd for complete script.)
1 $ cat dbfunctions
2 function addon () {# Function defined in file .dbfunctions
3 while true
4 do
echo "Adding information "
echo "Type the full name of employee "
read name
echo "Type address for employee "
read address
echo "Type start date for employee (4/10/88 ) :"
read startdate
echo $name:$address:$startdate
echo –n "Is this correct? "
read ans
case "$ans" in
[Yy]*)
echo "Adding info..."
echo $name:$address:$startdate>>datafile
sort –u datafile –o datafile
echo –n "Do you want to go back to the main \
menu? "
read ans
if [[ $ans == [Yy] ]]
then
4 return # return to calling program
else
5 continue # go to the top of the loop

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 331

fi
;;
*)
echo "Do you want to try again? "
read answer
case "$answer"in
[Yy]*) continue;;
*) exit;;
esac
;;
esac
done
6 } # End of function definition
------------------------------------------------------
(The Command Line)
7 $ more mainprog
#!/bin/bash
# Scriptname: mainprog
# This is the main script that will call the function, addon
datafile=$HOME/bourne/datafile
8 source dbfunctions # The file is loaded into memory
if [ ! –e $datafile ]
then
echo "$(basename $datafile) does not exist" >&2
exit 1
fi
9 eche "Select one: "
cat << EOF
[1] Add info
[2] Delete info
[3] Update info
[4] Exit
EOF
read choice
case $choice in
10 1) addon # Calling the addon function
;;
2) delete # Calling the delete function
;;
3) update
;;
4)
echo Bye
exit 0
;;
*) echo Bad choice
exit 2
;;
esac
echo Returned from function call
echo The name is $name
# Variable set in the function are known in this shell.
done

说明
1 显示文件.dbfunctions。
2 定义 addon 函数,这个函数负责向 datafile 中增加信息。

PDF created with pdfFactory Pro trial version www.pdffactory.com


332 第9章

3 进入一个 while 循环,除非循环体中包含 break 或者 continue 之类的语句,否则它将永远循环下


去。
4 return 使得程序回到函数被调用的地方。
5 控制回到循环的顶部。
6 右花括号结束了函数定义。
7 这是主脚本,函数 addon 加在脚本中。
8 source 命令把文件.dbfunctions 加入到程序内存中。现在,函数 addon 定义在这个脚本中,并且可
以被调用了。这跟在该脚本中定义函数是一样的。
9 利用 here 文档显示菜单,要求用户选择。
10 调用 addon 函数。

9.8 陷 阱 信 号
当你在程序运行时,按下 Control-C 或者 Control-/,一旦该信号到达程序就立刻终止运
行。但是在很多的时候,你可能并不希望在信号到达的时候,程序就立刻停止运行。而是它
能希望忽略这个信号而一直运行,或者在程序退出以前,做一些清除操作。trap 命令允许你
控制你的程序在收到信号以后的行为。
信号的定义是由一个进程发送给另外一个进程的,或者在特定的键按下以后由操作系统
发送给进程的,又或者在异常情况下发生时,由数字组成的非同步的消息。 6trap 命令告诉
Shell 根据收到的信号而以不同的方式终止当前的进程。如果 trap 命令后面跟着一个用引号
引用的命令,则在接收到指定的信号数字后,就执行这个命令。Shell 总共读取两次命令字
符串,一次是在设置 trap 的时候,一次是在信号到达的时候。如果命令字符串被双引号引用,
在第一次 trap 设置时就执行变量和命令替换。 如果是用的单引号引用, 那么等到信号到达 trap
开始执行的时候,才运行变量和命令替换。

格式
trap 'command; command' signal-number
trap 'command; command' signal-name

实例 9.61
trap 'rm tmp*; exit 1' 0 1 2 15
trap 'rm tmp*; exit 1' EXIT HUP INT TERM

说明
当 1(挂起)
,2(中断)或者 15(软件终止)任何一个信号到达就删除所有 tmp 文件并退出。

如果在一个脚本运行过程中,系统接到一个中断,trap 命令给出多种处理中断的方法。
你可以以常规动作处理信号(默认)、忽略信号或者建立一个处理函数,在信号到达时调用
这个函数。
信号的名字例如 HUP 和 INT 通常有一个前缀 SIG,例如 SIGHUP、SIGINT 等等。7bash
允许你在信号上使用象征性名称,例如没有前缀或者用数字作为信号的名称。一个叫作 EXIT

6.Bolsky,Morris I 和 Korn,David G.《The New KarnShell Command Auel P10 grawwing》2 版,PrenticeHall 1995 P327。
7.SiGKill,数字 9,通常称为“必杀” 。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 333

的或者数字 0 的伪信号,将在 Shell 退出时,导制一个陷阱的执行。


表 9.6 信号数字与信号(k:u-l)

1)SIGHUP 9)SIGKILL 18)SIGCONT 26)SIGVTALRM


2)SIGINT 10)SIGUSR1 19)SIGSTOP 27)SIGPROF
3)SIGQUIT 11)SIGEGV 20)SIGSTP 28)SIGWINCH
4)SIGILL 12)SIGUSR2 21)SIGTTIN 29)SIGIO
5)SIGTRAP 13)SIGPIPE 22)SIGTTOU 30)SIGPWR
6)SIGABRT 14)SIGALRM 23)SIGURG
7)SIGBUS 15)SIGTERM 24)SIGXCPU
8)SIGFPE 17)SIGCHLD 25)SIGXFSZ

信号复位。在 trap 命令后面跟一个信号或者数字,可以把信号复位为默认动作。一旦调


用了函数,函数设置的陷阱可以被调用这个函数的 Shell 识别。同时,在函数外设置的陷阱
也可以被函数识别。

实例 9.62
trap 2 or trap INT

说明
为信号 2,SIGINT 设置默认动作。该动作是当终止键(Control-c)按下就杀死当前进程。

忽略信号。如果 trap 命令后面跟一对空引号,列表中的信号就会被进程所忽略。

实例 9.63
trap " " 1 2 or trap "" HUP INT

说明
信号 1(SIGHUP)和信号 2(SIGINT)将被 shell 进程忽略。

陷阱列表。通过输入 trap 命令,可以显示陷阱列表和分配给陷阱的命令清单。

实例 9.64

(At the command line)


1 $ trap 'echo "Caught ya!; exit"' 2
2 $ trap
trap -- 'echo "Caught ya!; exit 1"' SIGINT
3 $ trap -

说明
1 trap 命令设置收到信号 2(Cotrol-c)时的动作是退出。
2 没有参数的 trap 命令显示陷阱清单。
3 如果参数是一个横线,则所有的信号都被恢复为原始值,而不论在 Shell 启动时它们是什么。

PDF created with pdfFactory Pro trial version www.pdffactory.com


334 第9章

实例 9.65

(The Script)
#!/bin/bash
# Scriptname: trapping
# Script to illustrate the trap command and signals
# Can use the signal numbers or bash abbreviations seen
# below. Cannot use SIGINT, SIGQUIT, etc.
1 trap 'echo "Control-C will not terminate $0."' INT
2 trap 'echo "Control-\ will not terminate $0."' QUIT
3 trap 'echo "Control-Z will not stop $0."' TSTP
4 echo "Enter any string after the prompt.
When you are ready to exit, type \"stop\"."
5 while true
do
6 echo –n "Go ahead...> "
7 read
8 if [[ $REPLY == [Ss]top ]]
then
9 break
fi
10 done

(The Output)
$ trapping
4 Enter any string after the prompt.
When you are ready to exit, type "stop".
6 GO ahead...> this is it^C
1 Control-c will not terminate trapping.
6 GO ahead...> this is it again^z
3 Control-z will not terminate trapping.
6 GO ahead...> this is never it /^\
2 Control-\ will not terminate trapping.
6 GO ahead...> stop
$

说明
1 首先 trap 捕捉到 INT 信号,Control-c。如果在程序运行的时候,按下 Control-C,引号中的命令
就会被执行,这个时候打印“Control-C will not terminate trapping”并等待用户输入而不终止。
2 当用户执行 Control -\,也就是 QUIT 信号的时候,执行第二个 trap 命令,程序将显示 Control-\will
not terminate trapping 并继续执行。默认信号 SIGQUIT 将杀死当前进程并产生一个 core 文件。
3 当用户执行 Control -Z,也就是 TSTP 信号的时候, 执行第三个 ttap 命令,程序将显示 Control-Zwill
not terminate trapping 并继续执行。通常这个信号导致程序在后台被挂起。
4 提示用户输入。
5 进入 while 循环。
6 打印字符串 Go ahead …>并等待用户输入。
7 read 命令把用户输入赋值给内建变量 REPLY。
8 如果变量 REPLY 的值匹配 Stop 或者 stop。break 命令就退出循环终止程序。除了用 kill 命令杀
死这个进程,输入 Stop 或者 stop 是惟一终止程序的方法。
9 break 命令导致退出循环体。
10 关键字 done 标志循环结束。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 335

复位信号。trap 命令后面以信号名字或者号码作为参数,可以复位信号为默认动作。

实例 9.66
trap 2

说明
复位信号 2,也就是 SIGINT,或者 Contorl-C,它默认用来杀死进程。

实例 9.67
trap 'trap 2' 2

说明
设置信号 2(SIGINT)在信号到达时的默认动作执行引号内的命令。用户必须按两次 Control-C
才能终止程序。第一个陷阱捕捉信号,第二个陷阱复位默认动作为杀死进程。

函数中的陷阱。如果你使用陷阱处理函数中的信号,一旦函数被激活,它将影响整个脚
本。陷阱对于脚本来说是全局的。在下面的例子中,陷阱被设置为忽略中断键^C。要终止
这个脚本的循环就只能使用 kill 命令。它证明了在函数中使用陷阱可能出现不可预料的情
况。

实例 9.68

(The Script)
#!/bin/bash
1 function trapper () {
echo "In trapper"
2 trap 'echo "Caught in a trap!"' INT
# Once set, this trap affects the entire script. Anytime
# ^C is entered, the script will ignore it.
}
3 while :
do
echo "In the main script"
4 trapper
5 echo "Still in main"
sleep 5
done

(The Output)
$ trapper
In the main script
In trapper
Still in main
^CCaught in a trap!
In the main script
In trapper
Still in main
^CCaught in a trap!
In the main script

PDF created with pdfFactory Pro trial version www.pdffactory.com


336 第9章

说明
1 定义 trapper 函数。所有在函数中设置的陷阱和变量对于脚本来说都是全局的。
2 trap 命令将忽略 INT、信号 2 及中断键^C。如果按下^C,就打印信息 Caught in a trap,脚本一直
无限执行下去。用 kill 命令可以杀死脚本。
3 主脚本启动一个无限循环。
4 调用 trapper 函数。
5 当函数返回后,从这里开始继续执行。

9.9 调 试
通过使用 bash 命令的-n 选项,可以在不执行脚本中的任何命令的情况下检查脚本语法。
如果脚本中有语法错误,Shell 就会报告有关的错误。如果没有错误,就什么也不显示。
在脚本调试中最常用的命令是 set 命令的-x 选项以及 bash –x 选项后面加脚本文件名。
参考表 9.7 的调试选项清单。这些选项允许跟踪脚本的执行。运行替换以后每一个脚本命令
都显示出来并执行。显示脚本的行的时候,行的前面会有一个(+)号。
关闭长选项,而使用-v 的选项(bash-v 脚本名),将按照输入时候的样子显示脚本的每
一行,并执行它们。
表 9.7 除错选项

命令 选项 含义
bash –x scriptname 显示选项 命令替换以后,执行以前显示脚本的每一行
bash –v scriptname 长选项 按照输入时候的样子在执行以前显示脚本的每一行
bash –n scriptname 不执行选项 解释但不执行命令
set –x 打开显示 跟踪脚本执行
set +x 关闭显示 关闭跟踪

实例 9.69

(The Script)
$ cat todebug
#!/bin/bash
# Scriptname: todebug

1 name="Joe Shmoe"
if [[ $name == "Joe Blow" ]]
then
printf "Hello $name\n"
fi

declare –i num=1
while (( num < 5 ))
do
let num+=1
done
printf "The total is %d\n", $num

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 337

(The Output)
2 bash –x todebug
+ name=Joe Shmoe
+ [[ Joe Shmoe == \J\o\e\ \B\l\o\w ]]
+ declare –i num=1
+ (( num < 5 ))
+ let num+=1
+ (( num < 5 ))
+ let num+=1
+ (( num < 5 ))
+ let num+=1
+ (( num < 5 ))
+ let num+=1
+ (( num < 5 ))
+ printf 'The total is %d\n,' 5
The total is 5

说明
1 该脚本称为 todebug。你可以打开-x 开关来观察脚本的运行。循环的每一次都显示出来,当变量
值发生改变时打印该变量值。
2 启动带有-x 选项的 bash,关闭显示,每一行的脚本都在前面加一个+显示在屏幕上面。行显示以
前变量替换已经完成。命令执行的结果显示在行的后面。

9.10 用 getopts 处理命令行选项


如果你的脚本需要一些命令行选项,位置参量并不总是最有效的。例如 ls 命令有一些命
令行选项和参数(每个选项前面需要一个前导破折号,但是参数不需要),选项可以通过许
多的方式传递给程序:例如 ls –l –i –F、ls –I –a –l –F 及 ls –ia –F 等等。如果脚本需要参数,
位置参量可能只用来处理参数,例如 ls –l –i –F。每一个破折号选项都相应地保存在$1、$2、
$3 中。但是,如果用户用一个破折号引导所有的选项会发生什么呢?-ilF 就赋值给$1。getopts
提供了一种像 ls 那样处理选项和参数的方法 8。getopts 允许脚本 runit 程序处理各种方式组
合的参数。

实例 9.70

(The Command Line )


1 $ runit –x –n 200 filex
2 $ runit –xn200 filex
3 $ runit –xy
4 $ runit –yx –n 30
5 $ runit –n250 –xy filey
( any other combination of these arguments )

说明
1 程序 runit 有四个参数:x 是一个选项,n 是一个需要后面有一些选项的选项,filex 是一个独立参

8.参考 UNIX 的 Man 手册第三部中的关于 C 库函数 getont 的部分。

PDF created with pdfFactory Pro trial version www.pdffactory.com


338 第9章

数。
2 程序 runit 组合一些选项 x、n 和 200。filex 也是一个参数。
3 用选项组合 x 和 y 启动 runit。
4 用选项组合 x 和 y 启动 runit;选项 n 单独传递,30 作为它的参数。
5 用选项 n 和数字参数组合启动 runit。选项 x 和 y 组合在一起,而 filex 是单独的。

在讨论 runit 程序的细节以前,我们首先看看带有 getopts 的行,看看它是怎么处理参数


的。

实例 9.71
(A Line from the Script Called "runit")
while getopts :xyn: name

说明
1 x、y 和 n 都是选项。这个例子中,第一个选项前面有冒号。这告诉 getopts 静默错误报告(silenterror
。如果在选项后面有冒号,表示选项需要一个用空格与它间隔开的参数。参数是一个没
reporting)
有破折号开头的单词,-n 选项就需要参数。
2 任何在命令行输入的选项都需要破折号。
3 没有破折号的选项告诉 getopts 已经到了选项清单的末尾。
4 每次调用 getopts 时,它都把找到的值赋值给变量 name(当然你也可以使用其他变量) ,如果给
定一个合法的参数,name 就赋值给问号。

getopts 脚本。下面的实例 9.72 说明 getopts 如何处理参数。

实例 9.72

(The Script)
$ cat opts1
#!/bin/bash
# program opts1
# Using getopte –- First try –-
1 while getopts xy options
do
2 case $options in
3 x) echo "you entered –x as an option";;
y) echo "you entered –y as an option";;
esac
done
(The Command Line)
4 $ optsl –x
you entered –x as an option
5 $ optsl -xy
you entered –x as an option
you entered –y as an option
6 $ optsl -y
you entered –y as an option
7 $ optsl -b
optsl: illegal option –- b
8 $ opts1 b

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 339

说明
1 getopts 作为 while 命令的条件。这个程序的合法选项清单列表在 getopts 命令后面。它们是 x 和 y。
每个选项都在循环体内分别进行判断,且都在去掉破折号以后被赋值给变量 options。当没有参
数可处理的时候,getopts 就返回一个非 0 退出状态值,导致 while 循环终止。
2 case 被用来判断每一个可能在变量 options 中找到的选项,x 或者 y。
3 如果 x 作为参数就显示 You entered x as option。
4 在命令行,脚本 opts1 被给定选项 x,getopts 将处理这个合法的选项。
5 在命令行,脚本 opts1 被给定选项 x 和 y,getopts 将处理这个合法的选项。
6 在命令行,脚本 opts1 被给定选项 y,getopts 将处理这个合法的选项。
7 在命令行,脚本 opts1 被给定选项 b,这是一个非法选项,getopts 将错误信息发送到标准错误。
8 没有破折号的选项不是选项,并会导致 getopts 停止处理参数。

实例 9.73
(The Script)
$ cat opts2
#!/bin/bash
# Program opts2
# Using getopts –- Second try –-
1 while getopts xy options 2> /dev/null
do
2 case $optiopts in
x) echo "you entered –x as an option";;
y) echo "you entered –t as an option";;
3 \?) echo "Only –x and –y are valid options" 1>&2;;
esac
done
(The Command Line)
$ opts2 –x
you entered –x as an option

$ opts2 -y
you entered –y as an option

$ opts2 xy

$ opts2 -xy
you entered –x as an option
you entered –y as an option
4 $ opts2 -g
Only –x and –y are valid options
5 $ opts2 -c
Only –x and -y are valid options

说明
1 如果 getopts 有任何错误信息就发送到/dev/null。
2 如果选项是一个错误选项,一个问号就被赋值给变量 options,case 命令可以用来判断问号,允许
你打印自己的错误信息到标准错误。
3 如果 options 被赋值为问号,执行 case 语句。因为?有反斜线保护,所以不能将其看作通配符进
行文件名替换。
4 g 不是一个合法的选项,?被赋值给变量 options,显示错误信息。
5 c 不是一个合法的选项,?被赋值给变量 options,显示错误信息。

PDF created with pdfFactory Pro trial version www.pdffactory.com


340 第9章

特殊的 getopts 变量。getopts 函数提供了两个用于跟踪参数的变量:OPTIND 和


OPTARG,OPTINO 被初始化为 1,并在 getopts 每处理完一次命令行参数以后增加 1。OPTARG
变量包含合法参数的值。见实例 9.74 和实例 9.75。

实例 9.74
(The Script)
$ cat opts3
#!/bin/bash
# Program opts3
# Using getopts –- Third try –-
1 while getopts dq: options
do
case $options in
2 d) echo "-d is a valid switch ";;
3 q) echo "The argument for –q is $OPTARG";;
\?) echo "Usage:opts3 –dq filename ... " 1>&2;;
esac
done

(The Command Line)


4 $ opts3 -d
-d is a valid switch

5 $ opts3 –q foo
The argument for –g is foo

6 $ opts3 -q
Usage:opts3 –dq filename ...

7 $ opts3 -e
Usage:opts3 –dq filename ...
8 $ opts3 e

说明
1 while 命令判断 getopts 的退出状态,如果 getopts 成功地处理了一个参数,它的回退出状态值为 0,
进入 while 循环体内。选项列表后面的冒号表示选项 q 需要参数,该参数将保存在特殊变量
OPTARG。
2 合法选项之一的 d,作为一个选项进入,没有破折号的保存在变量 options 中。
3 合法选项之一的 d 需要参数,在选项和参数之间必须有空格,如果 q 作为一个选项进入,后面跟
着一个参数,没有破折号的 q 被保存在变量 options 中,参数保存在变量 OPTARG 中,如果选项
后面没有参数,变量 options 中就储存一个?。
4 对 opts3 来说,d 是合法选项。
5 对 opts3 来说,带一个参数的正是合法选项。
6 q 选项不带参数是错误的。
7 e 选项非法,options 中保存。
8 如果选项前面没有破折号或者加号(+) ,getopts 就不把它作为选项处理返回非 0 退出状态值,
while 循环终止。

实例 9.75

$ cat opts4
#!/bin/bash

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 341

# Program opts4
# Using getopts -– Fourth try --
1 while getopts xyz: arguments 2>/dev/nu11
do
case $arguments in
2 x) echo "you entered –x as an option .";;
y) echo "you entered –y as an option." ;;
3 z) echo "you entered –z as an option."
echo "\$OPTARG is $OPTARG.";;
4 \?) echo "Usage opts4 [-xy] [-z argument]"
exit 1;;
esac
done
5 echo "The number of arguments passed was $(( $OPTIND – 1 ))"

(The Command Line)


$ opts4 –xyz foo
You entered –x as an option.
You entered –y as an option.
You entered –z as an option.
$OPTARG is foo.
The number of arguments passed was 2.
$ opts4 –x –y –z boo
You entered -x as an option.
You entered -y as an option.
You entered -z as an option.
$OPTARG is boo.
The number of arguments passed was 4.

$ opts4 -d
Usage: opts4 [-xy] [-z argument]

说明
1 while 命令判断 getopts 的退出状态值,如果 getopts 成功处理参数,就反回退出状态值 0,进入循
环体。z 后面的冒号告诉 getopts,z 选项后面必须有一个参数。如果选项有一个参数,该参数就
保存在内建变量 OPTARG 中。
2 若 x 作为给定选项,则将其保存在变量 arguments 中。
3 若 z 紧跟一个参数作为选项,则该参数保存在内建变量 OPTARG 中。
4 如果非法选项进入,就保存?到变量 arguments 中,并显示错误信息。
5 特殊 getopts 变量 OPTIND 保存下一个将被处理的选项的数。它是一个永远比实际命令行参数多
1 的数。

9.11 eval 命令与命令行解析


eval 命令可以对命令行求值,做 Shell 替换,并执行命令行,通常在普通命令行解析不
能满足要求时使用。

实例 9.76
1 $ set a b c d
2 $ echo The last argument is \$$#
3 The last argument is $4

PDF created with pdfFactory Pro trial version www.pdffactory.com


342 第9章

4 $ eval echo The last argument is \$$#


The last argument is d

5 $ set -x
$ eval echo The last argument is \$$#
+ eval echo the last argument is '$4'
++ echo the last argument is d
The last argument is d

说明
1 设置四个位置参量。
2 希望的结果是打印最后一个位置参量的值。\$ 打印一个美元符号。$#的值是 4,表示位置参量的
个数。在 Shell 对$#求值以后,就不再对$4 的值分析。
3 打印$4 而不是最后一个参数。
4 Shell 的变量替换以后,eval 命令进行变量替换并执行 echo 命令。
5 关闭语法分析显示。

实例 9.77

(From Shutdown Program)


1 eval ‘/usr/bin/id | /usr/bin/sed 's/[^a-z0-9=].*//'‘
2 if [ "${uid:=0}" –ne 0 ]
then
3 echo $0: Only root can run $0
exit 2
fi

说明
1 这是一个小技巧,把 id 程序的输出发送给 sed 从字符串中提取 uid。id 的输出是:
uid=9496(ellie)gid=40 groups=40
uid=0(root)gid=1(daemon)groups=1(deamon)
sed 正则表达式的意思是:从字符串的开始处开始查找,一个不是字母、数字和等号的符号,删
除这个符号和这个符号后面的所有字符。结果是删除了从第一个左括号开始的所有的字符。左边还
剩下:uid=9496 或者 uid=0。
例如,如果用户的 id 是 root,这个命令执行以后 uiod=0,这个命令建立一个本地变量 uid 并把
它赋值为 0。
2 用命令修改器判断变量 uid 值为 0。
3 如果 uid 不是 0 用 echo 命令显示脚本名称和信息。

9.12 bash 选 项
9.12.1 Shell 启动选项
当使用 bash 命令启动 Shell 的时候,可以通过选项控制 Shell 的行为。一共有两中类型
的选项:单字符选项和多字符选项。单字符选项由一个前导破折号和一个字母组成,多字符
选项由两个前导破折号和任意多个字符组成。多字符选项必须出现在单字符选项之前。交互
登录通常使用-i(交互)、-s(从标准输入读)和-m(允许作业控制)。参考表 9.8 所示。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 343

表 9.8 Bash 2.x 的交互选项

选项 含义
-c string 从 string 中读取命令,string 后面的参数都赋值给位置参量,从$0 开始
-D 打印一个用双引号引用的字符串的列表,前面有一个$到屏幕。当当前环境不是 C
或者 POSIX 时,这些字符串就是语言翻译的对象。-n 选项表示不执行任何命令
-i 交互模式,忽略 TERM、QUIT 及 INTERRUPT 信号
-s 从标准输入读取命令,允许设置位置参量
-r 启动一个受限制的 Shell
-- 选项终止的信号,停止进一步处理选项。任何--和-后面的参数都被看作文件名和参

--dump-string 同-D
--help 显示内建命令的帮助信息然后退出
--login 把 bash 作为登录 Shell
--noediting 启动时 bash 不读取启动文件
--noprofile 开始时,bash 不读取初始化文件/etc/profile、~/.bash_profile、~/.bash_login 或~/.profile
--norc 交互模式下,bash 不读取文件~/.bashrc。当运行 sh 的时候,在默认情况下这个选项
是打开的
--posix 改变 bash 的行为以符合 POSIX 标准,否则是不符合这个标准的
--quiet 在默认情况下启动不显示任何信息
--rcfile file 如果启动交互模式,就用这个文件替代~/.bashrc
--restricted 启动一个受限制的 Shell
--verbose 打开长选项(verbose)
,同-v
--version 显示版本信息然后退出

表 9.9 Shell 启动选项(2.x 以前的版本)

-c string 从 string 中读取命令,string 后面的参数都赋值给位置参量,从$0 开始


-D 打印一个用双引号引用的字符串的列表,前面有一个$到屏幕。当当前环境不是 C
或者 POSIX 的时候,这些字符串是语言翻译的对象。-n 选项表示不执行任何命令
-i 交互模式,忽略 TERM、QUIT 及 INTERRUPT 信号
-s 从标准输入读取命令,允许设置位置参量
-r 启动一个受限制的 Shell
- 选项终止的信号,停止进一步处理选项。任何--和-后面的参数都被看作文件名和参数
-login 把 bash 作为登录 Shell
-nobraceexpansion 关闭花括号扩展
-nolineediting 启动的时候 bash 不读取启动文件
-noprofile 开始时,bash 不读取初始化文件:/etc/profile,~/.bash_profile,~/.bash_login,或者
~/.profile
-posix 改变 bash 的行为以符合 POSIX 标准,否则是不符合这个标准的

PDF created with pdfFactory Pro trial version www.pdffactory.com


344 第9章

续表
-quiet 在默认情况下启动不显示任何信息
-rcfile file 如果启动交互模式,就用这个文件替代~/.bashrc
-verbose 打开长选项,同-v
-version 显示版本信息并退出

9.12.2 set 命令和选项


set 命令可以用来打开或者关闭 Shell 的选项,也可以用来处理命令行参数。打开一个选
项就在它前面加-,否则就加+。参考表 9.10。

实例 9.78

1 $ set -f

2 $ echo *
*

3 $ echo ??
??

4 $ set +f

说明
1 打开 f 选项,禁止文件名扩展。
2 星号不能扩展。
3 问号不能扩展。
4 f 选项关闭,文件名可以扩展。

表 9.10 内建 set 命令选项

选项名 快捷键 功能
allexport -a 标记从设置选项开始所有输出的新的和修改过的变量,直到
unset
*braceexpand -B 允许括号扩展,这是默认的
emacs 使用内建 emacs 编辑器作命令行编辑,这是默认选项
errexit -e 如果命令的退出状态值是非 0 的,在读取初始化文件的时候
就什么也不设置
*histexpand -H 在历史替换的时候允许!和!
!,这是默认选项
*history -k 允许命令行历史,这是默认选项
ignoreeof 禁止 EOF(Control-D)退出 Shell;必须输入 exit。与将 shell
变量设置为 IFNOREEOF=10 的效果是一样的
*keyword 将命令的参数放入环境
interactivecomments 在交互模式下,#表示注释
monitor -m 允许作业控制

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 345

续表
选项名 快捷键 功能
noclobber -C 在使用重新定向的时候防止文件被覆盖
noexec -n 读取命令但不执行,用在检查语法的时候,在交互模式下是
关闭的
noglob -d 禁止路径名扩展
notify -b 当后台作业结束的时候通知用户
nounset -u 在试图扩展一个没有被设置的变量时显示错误信息
*onecmd -t 读取并执行一个命令后退出
physical -P 如果设置了,则在使用 pwd 命令时,若没有符号连接作为参
数就显示物理地址
posix 改变默认动作以符合 POSIX 标准
privileged -p 如果设置,Shell 不读取.profile 文件和 ENV 文件,shell 函数
不从环境中继承变量。setuid 脚本自动设置
posix 改变默认动作以符合 POSIX 1003.2
verbose -v 为调试打开 verbose 模式
vi 使用内建 vi 编辑器作为命令行编辑器
xtrace -x 打开除错显示模式
* 星号表示该选项仅在 bash 2.x 版本中有效。

9.12.3 shopt 命令和选项


shopt(bash 2.x)命令可以用来打开和关闭 Shell 选项
表 9.11 shopt 命令选项

选项 含义
cdable_vars 如果内建变量 cd 后面不是一个目录,就假设这是一个包含目录名的变量
cdspell 更正 cd 命令中目录拼写错误。这些错误包括字母写错、少写字母及多写字
母。如果进行了更正,则打印更正后的目录。命令继续执行。这个只在交互
模式下有效
checkhash 在执行一个命令以前,bash 检验哈希表看看这个命令是否存在,如果不存在
就搜索标准路径
checkwinsize 在 echo 命令后检验 windows size,如果必要升级 LINES 和 VOLUMNS 的值
cmdhist bash 尝试在历史清单中保存一个多行命令的所有行,并允许简单地编辑多行
命令
dotglob bash 在文件扩展的结果中包括以“.”开头的文件
execfail 如果非交互模式下,Shell 无法执行作为 exec 参数的文件,也不会退出。则
exec 将失败,Shell 也不退出
expand_aliases 默认允许别名扩展
etxglob 允许扩展模式匹配的特性
histappend 当 Shell 退出时, 把历史列表追加到文件末尾,该文 件名保存在变 量
HISTFILE 中,而不是覆盖覆盖这个文件

PDF created with pdfFactory Pro trial version www.pdffactory.com


346 第9章

续表
选项 含义
histreedit 如果使用 readline,用户就可以重新编辑失败的历史替换
histverify 如果设置,就使用 readline,历史替换的结果不是立刻传递给 Shell 语法分析
器,而是装入内存中的 readline 编辑缓冲区,允许进一步修改
hostcomplete 默认情况下,如果设置,且使用 readline,则当出现@的时候,就默认尝试
自动完成主机名
huponexit 如果设置,在交互情况下 Shell 退出,将向所有的作业发送 SIGHUP 信号
interactive_comments 允许在交互模式下以#开头的单词和这个单词后面的内容被忽略
lithist 如果设置,cmdhist 选项被打开,多行命令被保存到历史中,使用换行符作
为分隔符而不是使用分号
mailwarn 如果被设置,且 Shell 正在检测一个已经被检测过的 mail 文件时,就显示
The mail in mailfile has been read
nocaseglob 如果设置,则在文件名扩展时对大小写敏感
nullglob 如果设置,bash 允许文件名模式匹配空字符串
promptvars 如果设置,提示符就显示为变量与参数扩展后的字符串,默认值为打开
restricted_shell 这个设置的是 Shell 按限制方式启动。这个值不能被改变。当启动文件执行
的时候它是不能被复位的,它允许启动文件自己判断是否启动限制 Shell
shift_verbose 如果设置,当换档次数超过位置参量个数以后,内建的 shift 命令就打印错
误信息
sourcepath 如果设置,内建 source 变量就使用 PATH 变量的值来查找包含参数文件的
目录,默认值为打开 i(点)的同义词
source ·(点)的同义词

9.13 Shell 内建命令


Shell 有一些内建的命令,因为它们是内部的,所以 Shell 不需要为这些命令在磁盘上定
位,从而可以更快捷地执行。help 特性可以在线显示所有内建命令的帮助。要详细了解内建
命令请参考表 9.12。

表 9.12 内建命令

命令 功能
: 什么也不做,返回退出状态值 0
.file 从文件中读并执行命令
break[n] 参考循环命令
. 执行当前进程中的程序
alias 列出已经存在的命令并为这些命令建立昵称
bg 把作业放入后台
bind* 显示当前函数与键的绑定,或者把键绑定到函数 readline
break 打破无限循环

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 347

续表
命令 功能
builtin [ sh-builtin[args]]* 在内部运行一个 Shell,并传递参数,返回退出状态值 0。如果函数跟内建
命令之间名字重复这就显得十分有用了
cd [arg] 改变当前目录
command command *[arg] 如果命令与函数有一样的命令,这个用来运行命令
continue[n] 参考循环命令
declare[var]* 显示所有变量或者声明变量
dirs 显示所有通过 pushd 记录的当前目录
disown 从作业表中删除一个活动作业
echo[args] 在终端上显示参数
enable* 打开或者关闭 Shell 的内建变量
eval[args] 读取参数作为输入并执行作为结果导致的命令
exec command 在 Shell 内执行命令
exit [n] 以退出状态值 n 退出 Shell
export[var] 让变量 var 对于子 Shell 是可见的
fc 用来编辑历史的历史修正命令
fg 把后台作业放到前台来
getopts 取得并处理命令行选项
hash 控制内部哈希表快速查找命令
help[command]* 显示内建命令的帮助信息
history 按行号显示历史列表
jobs 显示后台作业列表
kill [-signal process] 发送信号给 pid 号或者作业号
getopts 在脚本中取得和处理合法选项
let 用于对数学表达式求值并把结果赋值给一个变量
local 在函数中用于限制变量发挥作用的域
logout 退出 Shell 登录
popd 删除目录堆栈中的项
pushed 向目录堆栈中增加项
pwd 打印当前目录
read[var] 从标准输入中读取行并赋值给变量
readonly[var] 使变量只读
reture[n] 从函数中返回
set 设置选项和位置参量,见表 9.2
shift[n] 向左移动位置参量第 n 次
stop pid 挂起 pid 为 n 的进程

PDF created with pdfFactory Pro trial version www.pdffactory.com


348 第9章

续表
命令 功能
suspend 停止执行当前 Shell
test 检查当前文件类型并对条件表达式求值
times 打印累积从这个 Shell 衍生出来的系统和用户进程的个数
trap[arg][n] 系统接受到信号 n(0、1、2 或 5)
,就执行 arg
type [command] 打印命令类型
typeset 设置变量并赋予属性
ulimit 显示并设置资源限制
umask[octal digits] 设置 file mask
unalias 复位别名
unset[name] 复位函数或者变量的值
wait[pid#n] 等待 pid 为 n 的后台进程并报告终端状态

BASH SHELL 练习

练习 1——第一个脚本

1.写一个名为 greetme 的脚本来完成如下功能:


a.包含一段说明你的名字、脚本名以及这个脚本作用的注释
b.问候用户
c.打印日期与时间
d.打印这个月的日历
e.打印你的机器名
f.打印操作系统的版本和名字
g.打印上级目录中所有文件的名字
h.打印 root 运行的所有进程
i.打印变量 TERM、PATH 和 HOME 的值
j.打印磁盘容量(du)
k.利用 id 命令打印你的组的 ID
l.打印“Please couldn’t you loan me $50.00?”
m.告诉用户“Godbye”和当前小时
2.确保你的脚本是可以执行的
chmod +x greetme

3.你的脚本的第一行是什么?这一行对于你的作用是什么?
练习 2——命令行参数
1.写一个名为 rename 的脚本,包含两个参数,第一个参数是原始文件的名字,第二个

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 349

参数是新文件的名字。
如果发现用户提供的参数少于两个,就在屏幕上显示使用方法并退出脚本。下面是这个
脚本工作的例子:
$ rename
Usage: rename oldfilename newfilename
$

$ rename file1 file2


file1 has been renamed file2
Here is a listing of the directory:
a file2
b file.bak

2.这个 find 命令将找出根分区中所有大于 100K,并在一个星期以内被修改过的文件。


find / -xdev –mtime –7 –size +200 -print

3.写一个叫作 bigfile 的脚本,这个脚本包含两个参数:一个是 mtime,另外一个是尺


寸。如果用户没有提供这两个参数就向标准错误输出打印信息。
4.如果你有时间可以写一个叫作 vib 的脚本,让它为 vi 备份文件。备份文件的名字就
是在原始文件名字的后面追加后缀.bak。
练习 3——获取用户输入
1.写一个脚本 nosy 完成如下的功能:
a.询问用户的全名——姓氏、名字和中间名(英文名字习惯)
b.用用户名的第一个字向用户表示欢迎
c.询问用户的出生时间,计算他或者她的年龄
d.询问用户的登录名字,打印他或者她的 ID
e.告诉用户他或者她的主目录是什么
f.显示他或者她正在运行的进程
g.告诉用户今天是星期几和当前时间,输出的格式应该是这样的:
“The day of the week is Tuesday and the current time is 04:07:38 PM.”

2.建立一个叫作 datafile 的文本文件(如果这个文件没有被提供给你的话)


,每一行由
用冒号分隔的域组成,这些域是:
a.名字的第一个和最后一个字
b.电话号码
c.地址
d.生日
e.薪水
3.建立一个叫作 lookup 的脚本来完成如下的工作:
a.建立一段说明用来说明你的名字,脚本的名字、日期和你写这个脚本的目的。写
这个脚本的目的是按照存储的顺序显示文本文件的内容
b.根据名字的最后一个字把记录分类

PDF created with pdfFactory Pro trial version www.pdffactory.com


350 第9章

c.向用户显示 datafile 中的内容


d.告诉用户文件中有多少记
4.尝试用-x 和-y 选项调试你的脚本。如何使用这些命令?它们之间的区别在哪里?
练习 4——条件语句

1.写一个叫作 checking 的脚本来完成如下的功能:


a.取得命令行参数——用户的登录名
b.测试是否存在命令行参数
c.测试用户名是否在/etc/passwd 文件中存在,如果存在就打印:
“Found<user>in the /etc/passwd file.”

否则显示:
“No such user on our system.”

2.在 lookup 脚本中询问用户是否想向 datafile 中增加条目,如果答案是肯定的就:


a.提示用户输入新的名字、电话、地址、生日和薪水。每一项目都被保存在不同的
变量中。你需要用冒号把这些信息分隔开并追加到文件 datafile 后
b.按照名字的最后一个字把文件内容分类。告诉用户内容已经被成功添加并按照行
号显示给用户看
练习 5——条件语句和文件测试
1.重写脚本 checking。在检验用户名是否存在于文件/etc/passwd 文件以后,检验用户
是否已经登录。如果是就打印所有正在运行的进程,否则就打印:
“<user>is not logged on.”

2.用 let 命令建立一系列等级。通过一个测验给用户一个数字分数。脚本将测试这个分


数是否在合法范围内,如果不在合法的范围内,脚本将退出,否则就显示用户的字母等级,
例如 You received an A , Excellent!。字母等级定义如下:
A(90-100) B(80-89) C(70-79) D(60-69) F(少于 60)
3.lookup 脚本的运行需要依赖 datafile 文件。在 lookup 脚本中测试文件 datafile 是否存
在,是否可读,是否可写。在 lookup 脚本中增加一个如下的菜单:
[1]Add entry.
[2]Delete entry.
[3]View entry.
[4]Exit.

你已经写了 Add entry 部分的脚本。在这部分脚本中应该增加一些代码,检验用户提供


的名字是否已经存在于文件中,如果已经存在了就提示用户,如果不存在就添加这个名字。
现在写代码实现 Delete entry、View entry 和 Exit 功能。
Delete entry 部分代码应该首先检验是否存在用户希望删除的行,如果不存在就提示用
户。如果存在就删除这行,并提示用户该行已经被删除。退出的时候注意设置正确的退出状
态。

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 351

你还记得如何通过命令行检测退出状态吗?
练习 6——Case 语句
1.在 BSD 和 System 5 中的用法是不一样的。Linux 的使用方法给 BSD 类似。在 system5
中显示全部进程的命令是:
ps –ef
在 Linux 中是:
ps –aux
写一个程序调用 systype 检测系统的类型。可以检测的类型包括:

AIX
LINUX
HP-UX
SCO
OSF1
ULTRIX
SunOS(Solaris/SunOS)
OS

其中 HP-UNIX、SCO 和 IRIX 都是 ATT 类型的系统,其他的是 BSD 类型的系统。


你正在使用的 UNIX 系统的版本将打印到标准输出。系统名字可以用命令 uname –s 得
到或者在文件/etc/motd 中找到。
2.写一个叫作 Timegreet 的脚本实现如下的功能:
a.在脚本的最前部分提供一个说明部分,包括作者的名字,日期和写这个脚本的目

b.把如下的脚本翻译为使用 case 命令,而不是使用 if/elif 的形式:
#!bin/bash
# Comment section
you=$LOGNAME
hour=$( date +%H )
echo "The time is: $( date +%T )"
if (( hour > 0 && hour < 12 ))
then
echo "Good morning, $you!"
elif (( hour == 12 ))
then
echo "Lunch time!"
elif (( hour > 12 && hour < 16 ))
then
echo "Good afternoon, $you!"
else
echo "Good night, $you, Sweet dreams."
fi

PDF created with pdfFactory Pro trial version www.pdffactory.com


352 第9章

练习 7——循环
从下面选择一个:
1.写一个叫作 mchecker 的脚本检测是否有新的邮件到了,并向屏幕写一条信息,是否
有新的邮件到了。
a.程序首先取得该用户邮件假脱机文件的尺寸。脚本将每 30 秒钟运行一次循环。每
次运行都将比较本次该文件尺寸与上次文件尺寸之间的变化,如果发现尺寸变大
就向用户发送信息“username,you have new mail”
文件的尺寸可以通过 ls –s、wc –c 或者 find 命令的输出得到。
2.写一个脚本实现如下的功能:
a.在脚本的最前部分提供一个说明部分,包括作者的名字,日期和写这个脚本的目

b.用 select 循环建立一个菜单
c.产生类似如下的输出:
1)steak and potatos
2)fish and chips
3)soup and salad
Please make a selection.1
Stick to your ribs
Watch your cholesterol
Enjoy your meal

1)steak and potatos


2)fish and chips
3)soup and salad
Please make a selection.2
British are coming!
Enjoy your meal

1)steak and potatos


2)fish and chips
3)soup and salad
Please make a selection.2
Health foods…
Enjoy your meal
3.写一个叫作 dusage 的程序,每次向邮件清单中的用户发送一个邮件。这些用户的电
子邮件地址将保存在一个叫作 potential_hogs 的文件中。这些邮件地址中有一个是 admin。
a.检验 potential_hogs 文件是否存在,是否可读
b.使用循环给用户发送邮件,只有使用 500 个以上块的人才被发送邮件。用户 admin
将被忽略。邮件内容被保存在 dusage 脚本的 here documnt 中

PDF created with pdfFactory Pro trial version www.pdffactory.com


bash Shell 编程 353

c.保存一个收到邮件的人的清单。建立一个日志文件来完策划能够这个工作。在所
有邮件发送完毕以后,打印被发送邮件人的个数和他们的清单
练习 8——函数

1.重写最后一个程序 systype,它的作用是返回系统名称。使用这个函数决定在 checking


程序中的 ps 命令应该使用什么样的选项。
2.ATT UNIX 中显示所有进程的命令是:
ps –ef

3.在 Linux/BSD 中显示所有进程的命令是:


ps –aux or ps aux

4.建立一个 cleanup 函数删除所有临时文件并退出脚本。在程序运行的时候,如果发出


中断或者挂起的信号,trap 命令就调用函数 cleanup。
5.使用 here document 在 lookup 脚本中增加新的菜单项目:
[1] Add entry
[2] Delete entry
[3] Change entry
[4] View entry
[5] Exit

写一个函数来处理菜单中的每一个项目。在用户选择了一个合法项目以后函数结束,再
次要求用户选择菜单中的项目。如果用户选择了非法的选项,就打印:
“invalid entry , try again.”

然后再次显示菜单。
6.在 lookup 脚本中的 View entry 项目下建立子菜单,向用户询问,他或者她是否愿意
查看特定的信息:
a.phone
b.address
c.birthday
d.salary

7.在程序运行中如果发出中断或者挂起的信号,trap 命令就执行清除操作。

PDF created with pdfFactory Pro trial version www.pdffactory.com


第 10 章

交互式 TC Shell
10.1 简 介
所谓交互式的 Shell 就是把标准输入、输出和标准错误与一个终端连接起来。当你交互
使用 TC Shell 的时候,需要在 TC Shell 提示符下输入命令并等待反应。TC1 Shell 是在登录
时启动的命令翻译器。它是以前伯克利(Berkeley)UNIX 版本的加强版本。同时还增加了
命令行编辑、拼写检查及可编程的自动 completion(包括文件名、命令和变量)等等。
TC Shell 的主要发布站点是ftp.astron.com、ftp.gw.com及ftp.primat.wisc.edu2。虽然在大多
数 Linux 发行版本中都有 tcsh,但实际上它可以移植到大多数操作系统中,包括 Solaris、
Windows NT、HP-UX、QNX 等等。
本章的重点是如何交互地使用 tcsh 和初始化工作环境,下一章将着重于学习用 tcsh 编
写脚本所需要的编程结构,那样就不需要在命令行输入命令了,而是将命令存入文件并执行
该文件就可以了。

10.1.1 tcsh 的版本


若想知道当前 tcsh 的版本信息,则需要在命令行中输入如下命令:
which tcsh

下面的命令将告诉你 tcsh 安装在哪一个目录下,并打印版本信息:


/directory_path/tcsh –c 'echo &version'

实例 10.1
1 which tcsh
/bin/tcsh
2 /bin/tcsh –c 'echo $version'
tcsh 6.07.09 (Astron) 1998-07-07 (i386-intel-linux) options
8b, nls,d1,al,rh,color

1.tcsh 中的 t 可以追溯到为 DEC 的 PDP-10 计算机使用的 TENEX 和 TOP-10s 操作系统。这两个系统为监视器提供了某种形式


的命令自动完成功能。tcsh 的作者很欣赏这种特性,因而在把该特性强化后加入到 tcsh 中,并给 cshel 前加了 t。
2.自学可以参考www.tac.nyc.ny.us/mirrors/tcsh-book。

PDF created with pdfFactory Pro trial version www.pdffactory.com


356 第 10 章

10.1.2 启动
在 TC Shell 启动以前需要处理一系列进程,参考图 10.1。

启动

图 10.1 启动 TC Shell

在系统引导以后,第一个需要运行的是 init,PID 号码是 1。它产生一个 getty 进程,这


些进程负责打开一个终端端口,以提供一块用于得到标准输入、显示标准输出和标准错误,
以及打印提示符的地方。在用户输入其用户名以后,就执行/bin/login,login 程序提示输入
密码、加密并验证密码、建立初始化环境,以及初始化 Shell 和/bin/tcsh。TC Shell 就在/etc
目录下寻找叫作/etc/csh.chrc 和/etc/csh.login(如果存在的话)的系统初始化文件。接着,在
用户目录下寻找文件 ~/.tchrc,这是另外一个用来初始化用户环境的文件。如果这个文件不
存在,tcsh 就寻找另外一个功能相同的文件~/.cshrc,在执行这个文件中的命令以后,就执行
叫作.history 的历史文件的内容,然后运行~/.login 文件,最后执行的是~/.cshdirs 文件,上面
提到的每一个文件都将在“环境”一节中详细介绍。3
每当新的 tcsh 启动时,/etc/csh.cshrc 和文件~/.tcshrc 就将被执行一次,而.login 文件只在
用户登录时执行一次,它包含初始化用户环境所需要的变量和命令。在从启动文件中执行完
命令后,提示符就显示在屏幕上了,(默认是>),tcsh 等待输入命令。参见图 10.2。
当退出登录时,如果变量 autologout 已被设置了,用户可以通过键入 Control-D 或者等
待自动退出登录。在退出以前,Shell 会寻找一个叫作/etc/csh.logout 文件和用户目录下的
~/.logout 文件,无论找到哪个文件,其命令都将被执行。.logout 中的命令通常被用来清除临
时文件、追加数据到日志文件中、以及向用户告别等等。

3.这些文件的执行顺序可以在编译 tcsh 时改变。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 357

图 10.2 如果以上的任何一个初始化文件存在,它们将按照这样的顺序被访问

10.2 TC Shell 环境

10.2.1 初始化文件
在 tcsh 启动以后,首先执行系统启动文件/etc/csh.cshrc 和用户目录下的两个文件(.tcshrc
和.login)。这些文件使用户能够初始化其环境。

实例 10.2

# /etc/csh.cshrc

# System wide environment and startup programs for csh users

1 if ($?PATH) then
2 setenv PATH "${PATH}:/usr/XllR6/bin"
else
3 setenv PATH "/bin:/usr/bin:/usr/local/bin:/usr/XllR6/bin"
endif

4 if ($?prompt) then
5 [ "$SHELL" = /bin/tcsh ]
6 if ($status == 0) then
7 set prompt='[%n@%m %c]$ '
8 else
9 set prompt=\['id –nu'@'hostname –s'\]\$\
10 endif
endif
11 limit coredumpsize 1000000

12 [ 'id –gn' = 'id –un' –a 'id –u' –gt 14]


13 if $status then

PDF created with pdfFactory Pro trial version www.pdffactory.com


358 第 10 章

14 umask 022
else
15 umask 022
endif

16 setenv HOSTNAME '/bin/hostname'


17 set history= 1000

18 test –d /etc/profile.d
19 if ($atatus == 0) then
20 set nonomatch
21 foreach i ( /etc/profile.d/*.csh )
22 test -f $i
if ($status == 0) then
23 source $i
endif
end
24 unset nonomatch
endif

说明
1 $?PATH 用来测试变量 PATH 是否已经被赋值,如果返回 1 则表示真。
2 如果 PATH 变量已经被赋值,就追加“/usr/X11R6/bin”到它的后面,这是一个包含 X Windows
文件目录。
3 如果 PATH 变量没有被赋值,就设置这个变量的值为“/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin”

4 这行用来检测提示符是否被设置。
5 如果将表达式放在方括号中,表达式就被测试,如果表达式的值是真,那么退出状态值就是 0,
否则就返回一个非 0 值。如果变量 SHELL 的值是“/bin/tcsh” ,退出状态值就是 0。
6 变量 status 包含最后一个命令执行以后的退出状态值。在这个例子中就是第 5 行的表达式测试的
结果。
7 如果最后一个命令执行的退出状态值是 0,那么就设置/bin/tcsh 的提示符,提示符被设置为用户
的名字后面紧跟着一个@、主机名和当前工作目录,所有这些都用方括号括起来。最后是一个美
元符号($)。
8 如果退出状态值是非 0,那么 else 分支跳转到第 9 行。
9 这行为标准的 csh 程序设置提示符。它将打印用户名、一个@符号和主机短名,具体来说主机短
名在第一个逗点处把主机名断开,最后是一个美元符号($) 。
10 endif 结束判断。
11 核心文件(通常在程序因为非法系统操作而崩溃时建立的)的尺寸被限制在 1000000 比特大小。
如果用 control-\终止一个正在运行的文件,就会产生一个核心文件。
12 如果组 ID 号和用户 ID 号相同,并且大于 14,下一行就被执行,否则就执行 else 后面的语句。
典型情况下,只有特殊用户的 ID 才小于 14,例如 root、daemon、adm 以及 lp 等。
13 如果测试到前一行返回的是非 0 退出状态,就执行第 14 行,否则执行第 15 行。
14 umask 用来设置文件创建掩码,也就是在创建时初始化文件和目录的默认权限。目录将是 755
(rwxr-xr-x) ,文件是 644(rw-r--r--)

15 设置 umask,创建目录时候的默认权限是 775,创建文件时的默认权限是 664。
16 环境变量 HOSTNAME 用/bin/hostname 的输出赋值。
17 变量 HISTORY 设置为 1000,当命令在命令行输入的时候就被储存到一个历史列表当中,当你设
置 histoery 的值是 1000 时,输入 history 命令后,将显示最近的 10000 个命令。
18 如果/etc/.profile.d 目录存在的话,就返回 0,否则返回非 0。
19 如果是 0,就表示目录存在,跳转到第 20 行。
20 设置 nonomatch 方式在出现特殊符号无法匹配的时候,shell 发送错误信息。
21 foreach 循环按照顺序把 /etc/profile/d 目录下面的所有以.csh 结尾的文件的都赋值给变量 i。
22 如果赋值给变量 i 的文件名是规则文件名,就继续到下一行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 359

23 如果返回状态值是 0,就在当前环境中运行这个文件。
24 关键字 end 表示循环体结束。

~/.tcshrc 文件。这个文件包含 tcsh 变量的设置,且在每次 tcsh 于 Shell 启动时都运行。


别名和历史都在这里设置。

实例 10.3

# (The .tcshrc File)


1 if ( $?prompt ) then
2 set prompt = "\! stardust > "
3 set history = 100
4 set savehist = 5
5 set noclobber
6 set rmstar

7 set cdpath = ( /home/jody/ellie/bin/usr/local/bin /usr/bin )


8 set ignoreeof
9 alias m more
alias status 'date;du –s'
alias cd 'cd \!*;set prompt = "\!<$cwd>"'
10 endif

说明
1 如果提示符已经设置($?提示符) ,就进入交互模式,也就说不运行脚本。只有交互式 shell 中才
设置脚本。
2 主提示符被设置为当前历史事件的数量,stardust 然后是>,这将改变默认的>提示符。
3 历史变量被设置为 100,这个被控制的数量将显示在屏幕上,如果输入为 history,则最后 100 个
输入命令就显示在屏幕上。
4 正常情况下,退出时,历史记录就被清除。savehist 变量允许你保存历史记录清单末尾的部分记
录。在这个例子中,最后 5 条记录将被保存到主目录下叫作.history 的文件中,这样当你再次登
录的时候,Shell 就会自动检测这个文件是否存在,如果存在,就把这个文件中的内容放在新的
历史列表的最顶端。
5 变量 noclobber 用来防止用户在使用重新定向的时候不小心删除文件。例如:sort myfile>myfile
将毁坏文件 myfile。若使用变量 noclobber,则在你准备重新定向到一个已经存在的文件的时候,
屏幕上就显示提示“file exist”。
6 如果 tcsh 的变量 rmstar 被设置,用户在删除任何文件以前都会得到提示,这有利于防止用户误删
除当前工作目录下的所有文件。
7 cdpath 变量的值是一组路径清单的元素。当你输入一个目录名来改变路径的时候,如果这个目录
名不直接在当前目录下,Shell 就会搜索 cdpath 寻找及定位这个目录的位置,并改变当前目录到
这个目录。
8 变量 ignoreeof 防止你因为输入 control-D 而退出。Linux 的一些使用程序从键盘取得输入,例如
mail,可以用 contril-D 来结束。在一个很慢的环境下,用户可能会尝试多次使用 control-D 来加
快程序的结束,第一次,mail 退出了;第二次,用户可能就退出登录了。设置了变量 ignoreeof
后,系统就保证只有在使用 logout 的时候,用户才退出。
9 设置 alias 为一个或者一组命令提供了快捷方式。如果你输入快捷方式,相应的命令就会执行。
在这个例子中,m 就是命令 more 的别名每次你输入 m,more 就会执行。status 用来打印磁盘使
用状况。cd 别名在用户每一次改变目录的时候改变提示符,新的提示符将包含当前历史事件的
数目,当前工作目录,并用<>括起来。
10 endif 表示从第一行开始的判断结束了。

PDF created with pdfFactory Pro trial version www.pdffactory.com


360 第 10 章

~/.login 文件。它只在用户第一次登录的时候运行,其中包含环境变量的设置和终端设
置。图形应用程序通常从这里启动。因为环境变量具有继承的能力,因此只需要设置一次,
终端环境也不必要每次都重新设置,这些设置都放在~/.login 文件中。

实例 10.4

# (The .login File)


1 stty -istrip
2 stty erase ^h
3 stty kill ^u
#
# If possible start the windows system.
# Give a user a chance to bail out
#
4 if ($TERM == "linux" ) then
5 echo "Starting X windows. Press control c \
to exit within the next 5 seconds "
sleep 5
6 startx
7 endif
8 set autologout =60

说明
1 stty 命令设置终端选项,如果使用-istrip 选项,就不会发生 7 个字节的不兼容的问题。
2 stty 设置 control-H,backspace 具有删除功能。
3 以#开头的行是注释。
4 如果当前终端窗口(tty)是一个控制台(Linux),就执行下一行,否则就跳转到 endif 以后。
5 这一行的内容将响应到屏幕,如果用户不按 control-C,将休眠 5 秒钟,然后启动 X 程序。
6 用 startx 命令启动 X 环境。
7 endif 结束判断结构。
8 变量 autologout 被设置为 60,意味着在登录 60 分钟以后,用户就自动退出了。

10.2.2 搜索路径
path 变量用于查找在命令行输入的命令。搜索从左到右,逗点表示当前目录。如果在
path 清单中的目录或当前目录中没有找到输入的命令,Shell 就在标准错误上显示信息
“Command not found”,建议在.login4 文件中设置路径。在 tcsh 中与在 bash 或者 Korn Shell
中搜索路径的设置是不一样的。在 tcsh 中,不同元素之间依靠空白分割,但是在其他的 Shell
中是用冒号分隔。
tcsh Shell 可以在内部更新变量 path 以保持与其他程序的兼容性,例如 bash、Bourne 以
及 Korn Shell 等。

实例 10.5

# Path is set in the ~/.tcshrc file.

1 set path = (/usr/bin /bin /usr/bsd /usr/local/bin .)

4.不要混淆了路径变量和.cshrc 文件中的 cdpath 变量。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 361

2 echo $path
/usr/bin /bin /usr/bsd /usr/local/bin .

# The environment variable PATH will display as a colon-separated list

3 echo $PATH
/usr/bin:/bin:/usr/bsd:/usr/local/bin:.

说明
1 为 tcsh 设置 path。它包含一个以空白分割目录清单,当一个命令从命令行输入,Shell 就从左到
右的搜索这些目录。
2 显示 path 变量的值。
3 环境变量 PATH 显示出来的清单,跟 path 变量显示的清单是一样的,只是用冒号分割,它被传
递给从当前 Shell 启动的程序(其他的 Shell 用冒号分割路径)

rehash 命令。Shell 建立了一个内部的哈希表,用来存储搜索路径中目录清单的内容(如


果搜索路径中包含逗点目录,也就是当前工作目录,当前目录的文件将不会保存在哈希表
内)。为了提高效率,Shell 利用哈希表查找定位在命令行输入的命令,而不是每一次都搜索
这些路径。如果一个新的命令被加入到已经存在的哈希表目录中,该哈希表就需要重新计算,
你可以用下面的命令完成这个工作:
rehash

当你改变路径或者启动其他 Shell 的时候,哈希表就会自动重新计算。


hashstat 命令。hashstat 命令用来统计在哈希表中搜索命令的效率,并把统计显示出来。
统计包含“命中”(hit)和“失败” (miss)两个项目。如果 Shell 在路径的末尾才找到这个
命令,就比在路径的开始找到命令效率低,结果导致大量的“失败”和少量的“命中” 。在
5
这种情况下你可以把命中率高的目录放在比较靠左的位置,以提高搜索的效率。 内建的
unhash 命令禁止内部哈希表的使用。如:
> hashstat
1024 hash buckets of 16 bits each

source 命令。source 命令是 Shell 的内建命令,也就是说它是 Shell 的一部分代码。它


用来执行文件中的一个或一组命令。通常执行一个命令时,Shell 就调用一个子进程来执行
这个命令,所以任何变化都不会影响被称为父进程原始的 Shell。source 命令使得命令在当前
的 Shell 中执行,所以在任何文件中设置的变量都将最终影响到当前 Shell 的环境。source 命
令通常被用来在.tcshrc 文件、.cshrc 文件和.login 文件被修改后,再次执行它们。例如,如果
登录以后路径改变了,就执行命令:
> source .login 或者 souce .tcshrc

10.2.3 Shell 提示符


TC Shell 有三个提示符,一个是主提示符(一个>符号),一个辅助提示符(一个问号,

5.在没有 vfork(2)的机器上,打印哈希存储区的号码和尺寸。

PDF created with pdfFactory Pro trial version www.pdffactory.com


362 第 10 章

在 while、oreach 或者 if 命令后面使用),还有一个是第三提示符,用于拼写检查时候使用。
在用户登录后,显示在终端上的提示符就是主提示符。它可以被重新设置。如果你在提示符
下写脚本就需要使用程序结构,例如循环或者判断结构,这个时候就需要辅助提示符帮助你
在下一行继续程序的结构。它会一直出现在每一个新行的前面,直到结构终止。如果拼写检
查打开了,第三提示符被用来确认自动的拼写检查。它包含字符串 CORRECT> corrected
command (y|n|e|a)?,该可以用特殊的格式化字符个性化提示符,参见表 10.1。

表 10.1 提示符

%/ 当前工作目录
%~ 当前工作目录,~表示用户的主目录,~user 表示 user 的主目录
%c[[0]n], 跟踪当前目录元素,如果 n(n 是数字)存在,就跟踪 n 个元素
%.[[0]n]
%C 类似%c,但是没有~替换
%h,%!,! 当前历史事件的数目
%M 完整的主机名
%m 主机名第一个逗点“.”以后的部分
%S(&s) 开始(结束)标准输出模式
%B(%b) 开始(结束)黑体字模式
%U(%u) 开始(结束)下划线模式
%t,%@ 12 小时格式显示当前时间
%T 24 小时格式显示当前时间
%p 12 小时格式显示精确到秒的时间
%P 同上,但是是 24 小时格式的
^c 在绑定键中解析 C
\c 在绑定键中解析 C
%% 一个%
%n 用户名
%d 显示文字格式的星期几
%D 显示今天的日期(不包含月和年)
%w 显示文字格式的月份
%W 显示数字格式的月份
%y 显示两位格式的年“yy”
%Y 显示四位格式的年“yyyy”
%l Shell 的 tty
%L 清空提示符以后的所有内容
%$ 展开$后面的 Shell 变量或者环境变量
%# >给普通用户使用
#给超级用户使用

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 363

续表
%{string%} 包含一个换码序列的字符串,只在改变终端属性时候使用,并不移动指针的位
置。这一项不能作为提示符的最后一项
%? 在提示符前显示命令执行的返回码
%R 在辅助提示符中,表示语法分析程序的状态。在第三提示符中,表示拼写检查;
在历史中,表示历史字符串

主提示符。在交互模式下,用户在主提示符下输入命令并回车。如果你不想使用默认提
示符,则可以在.tcshrc 文件中重新设置它。如果你只想在这个登录会话中改变主提示符,可
以在提示符下设置它。

实例 10.6

1 > set prompt = '[%n@%m %c]# '


2 [ ellie@homebound ~]# cd ..
3 [ ellie@homebound /home]# cd ..

说明
1 主提示符被设置为主机名,它跟在用户名的后面,然后是空格和当前目录。这些字符串包含在一
对方括号中,以#结束。
2 显示新提示符,~表示用户的主目录,cd 命令改变目录到上一层目录。
3 新提示符包括当前工作目录,/home。这种方式的好处是用户可以方便地知道自己的位置。

辅助提示符。当编写在线脚本时,就会出现辅助提示符。辅助提示符是可以改变的。无
论 Shell 的程序的结构怎样,只要这个结构没有完成,还需要新行,辅助提示符就会出现。
在命令行下练习写脚本,一旦回车就不能退回更改了,历史中也不保存辅助提示符下输入的
命令。

实例 10.7

1 > foreach pal (joe tom ann)


2 foreach? echo Hi $pal
3 foreach? end
Hi joe
Hi tom
Hi ann
4 >

说明
1 这是一个在线脚本的例子,foreah 循环没有结束,还需要继续输入因此出现辅助提示符。循环体
对于括号内的每个单词都响应一次。
2 在第一次循环中,joe 被赋值给变量 pal。用户 joe 把 memo 中的内容通过 mail 发送了出去。第二
次 tom 被赋值给变量 pal,如此这样循环下去。
3 end 语句结束了循环体。在括号内的每一个单词都被处理以后,循环结束并显示主提示符。
4 显示主提示符。

PDF created with pdfFactory Pro trial version www.pdffactory.com


364 第 10 章

实例 10.8

1 > set prompt2='%R %% '


2 > foreach name ( joe tom ann )
3 foreach % echo Hi $name
4 foreach % end
Hi joe
Hi tom
Hi ann
5 >

说明
1 辅助提示符被重新格式化,%R 表示第二行主提示符下的判断或者循环结构的名字,两个百分号
表示一个百分号。
2 启动 foreach 命令。循环结构必须以 end 结束,辅助提示符将一直显示到循环结构结束。
3 辅助提示符是 foreach%。
4 输入 end 命令以后,就执行循环。
5 主提示符出现等待用户的输入。

10.2.4 命令行
登录以后,TC Shell 显示主提示符(默认是>),Shell 就是你的命令翻译器。当 Shell 交
互运行时,它从终端读取输入,并把命令行分割为单词。一个命令行由一个或者多个单词组
成,它们之间依靠空格分割,命令行最终以换行符结束,换行符由回车产生。命令行的第一
个单词是命令后面的是参数和选项。命令可以是可执行程序、内建命令或者脚本。命令可以
包含特殊的字符,例如元字符集。如果最后一个字符是反斜杠,就表示下一行是这一行的继
续。6
退出状态和 prinexitvalue 变量。当一个命令结束后,它就返回一个退出状态值给其父进
程,该值在 0~255 之间。为了方便若程序成功退出就返回 0。而非 0 就表示出现了某种错
误。当程序非正常中断时,就在其状态值中加上 0200。当内建命令失败时返回 1,否则就返
回 0。
tcsh status 变量和?被设置为最后一个执行命令的退出状态值。程序的成功与失败由编
写该程序的作者决定。通过将 tcsh 变量设置为 prinexitvalue,可以使得在任何一个程序的退
出状态值为非 0 时自动打印。

实例 10.9

1 > grep "ellie" /etc/passwd


ellie:GgMyBsSJavd16s:501:40:E Quigley:/home/jody/ellie:/bin/tcsh
2 > echo $status or echo $?
0
3 > grep "nicky" /etc/passwd
4 > echo $status
1
5 > grep "scott" /etc/passsswd

6.命令行的长度可以达到 256 个字符长。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 365

grep: /etc/passsswd: No such file or directory


6 > echo $status
2
7 > set printexitvalue
> grep "xxx" /etc/passwd
Exit 1
>

说明
1 grep 程序在/etc/passwd 中搜索模式“ellie”成功,该行就显示出来。
2 status 变量表示 grep 退出状态值。0 表示成功,?变量包含退出状态值。该变量在 bash 和 ksh 中
用来检测退出状态(它不用于 csh) 。
3 grep 程序无法找到 nicky。
4 grep 程序不能找到匹配模式的字符串,返回退出状态值 1。
5 因为/etc/passwd 无法打开,所以 grep 失败。
6 grep 无法找到文件,返回退出状态值 2。
7 由于设置了特殊的 tcsh 变量 prinexitvalue,所以将自动打印非 0 的退出状态值

命令组。一个命令行可以包含多个命令。每个命令用逗号分割,而整个行用换行符结束。

实例 10.10
>ls; pwd; cal 2000

说明
命令由左到右的执行,直到换行符。

命令组一起执行命令,可以把输出重新定向给文件或者通过管道传递给其他命令。Shell
在子进程中执行命令。

实例 10.11

1 > ( ls ; pwd; cal 2000 ) > outputfile


2 > pwd; ( cd / ; pwd ) ; pwd
/home/jody/ellie
/
/home/jody/ellie

说明
1 每一个命令的输出都重新定向给文件 outputfile 了。如果没有括号,那么前两个命令输出就打印
到屏幕,最后一个命令的输出定向到输出文件。
2 pwd 显示当前路径。括号内的两个命令被子 Shell 处理。CD 是内建命令。在子进程中,目录被修
改为根,并显示路径。当退出子 Shell 以后,该路径恢复为原始路径。

命令的条件执行。条件执行的两个命令用两个&或者双竖线分隔。右边的命令是否执行
取决于左边的命令。

实例 10.12
> grep '^tom:' /etc/passwd && mail tom < letter

PDF created with pdfFactory Pro trial version www.pdffactory.com


366 第 10 章

说明
如果第一个命令成功(退出状态值为 0) ,第二个命令就执行。如果 grep 在 passwd 文件在中找
到 tom,右边的命令就执行——把 letter 文件 mail 给 tom。

实例 10.13
> grep '^tom:' /etc/passwd || echo "tom is not a user here."

说明
如果第一行失败(退出状态值非 0) ,右边的命令就执行。如果 grep 在 passwd 文件在中没找到

tom,右边的命令就执行——打印“tom is not s user here”

后台命令。通常情况下,当你执行一个命令时,它往往在后台运行且不打印任何字符到
屏幕,直到执行完毕才出现提示符。但是,并不是在所有情况下都需要这样等待。当你在命
令后面加一个&符号,Shell 就会直接显示执行的结果,而不是从第一个命令开始一直等待,
直到最后一个命令执行结束。后台处理的命令叫作后台作业(background job),其结果在处
理过程中将输出打印到屏幕。如果两个命令都输出结果到屏幕就可能造成混淆。为了避免这
种情况,你可以把后台运行的命令重新定向到文件或者其他设备,例如打印机。

实例 10.14

1 > man tcsh | lpr &


2 [1] 4664
3 >

说明
1 把 man 中关于 tcsh 程序的内容传递给打印机。最后的&符号使得该程序在后台运行。
2 屏幕上出现两个数字:方括号中的数字表示第一个作业将被放在后台执行。第二个数字是这个作
业的 PID。
3 提示符立刻出现了。当程序在后台运行时,提示符就在前台提示你输入新的命令。

10.3 命令行快捷方式
10.3.1 历史
TC Shell 内建的历史机制。它在内存中按照顺序保存一定数量的历史命令的记录,称为
事件,而这些事件就是你在命令行输入的命令。当 Shell 从终端读取命令时,它先把命令分
割为一些单词,接着再把这行命令保存在历史列表中,然后再分析语法,最后执行它。前一
个输入的命令永远被保存。你可以重新执行以前输入的而现在保存在历史中的命令,而无需
重新输入它。从登录会话开始,你输入的命令就被追加到历史列表中,直到退出,历史列表
被保存为一个文件,称为.history。7 历史文件和历史列表有时候容易混淆。历史列表表示当

7.可以通过对 Shell 变量 histfile 赋值的方法改变.history 文件的名字。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 367

前内存中保存的命令行的清单。历史文件指的就是.history 文件,它是一个文本文件,用来
保存历史命令以供将来使用。设置内建变量 savehist 使得在退出时把历史保存到历史文件中,
当再次登录的时候,Shell 会把历史文件读到内存中来。内建命令 history 显示历史清单,它
支持多个参数以控制如何显示清单。见表 10.2。

表 10.2 history 命令和选项

选项 含义
-h 打印没有序列号的历史清单
-T 在说明表格中打印时间戳
-r 打印清单自动换行
-S[filename] 保存清单,如果给出 filename 就保存在 filename 中
-L[filename] 追加清单,如果给出 filename 就追加在 filename 后面
-M[filename] 跟-L 选项类似,只是用现在的清单替代 filename 中的清单
-c 清除历史清单,但是不清除历史文件
n N 是一个数字,表示希望看到的第几行的历史命令

虽然历史文件的默认名是.history,但是它也可以通过给内建的 Shell 变量 histfile 赋值来


改变。history 变量的值是一个数字,表示在历史清单中可以有多少条记录。变量 histdup 可
以复制没有被保存到文件中的历史清单记录。

实例 10.15

(The Command Line)


> history
1 17:12 cd
2 17:13 ls
3 17:13 more /etc/fstab
4 17:24 /etc/mount
5 17:54 sort index
6 17:56 vi index

说明
历史清单显示出最后输入的命令。每一个事件前都有一个数字(事件号)和它们被输入的时间。

history 变量。TC Shell 的 history 变量所包含的数字表示可以在终端上显示出来的历史


事件的数量。通常这个值在用户初始化文件/etc/.cshrc 或者~/.tcshrc 中,其默认值是 100,你
也可以通过设置选项控制输出的格式。这里所使用的换码序列跟提示符的是一样的。history
的默认格式是%h\t%T\t%R\n。

实例 10.16

1 set history=1000

2 set history= ( 1000 '%B%h %R\n')

PDF created with pdfFactory Pro trial version www.pdffactory.com


368 第 10 章

3 history
136 history
137 set history = ( 1000 '%B%h %R\n')
138 history
139 ls
140 pwd
141 cal
141 pwd
142 cd

说明
1 通过设置 history 变量,在终端可以显示 1000 个历史命令。
2 显示 1000 个历史事件,格式是事件数(%h)是黑体(%B) ,然后是空格,最后是历史命令(%R)

跟着一个换行符(\n) 。
3 当输入命令 history 后,格式就显示出来。例子中显示的只是一部分。

保存历史和 savehist 变量。通过登录设置 savehist 变量可以保存历史。这个变量通常在


用户初始化文件.tcshrc 中设置。如果第一个值赋值给 savehist,它可以是一个比 history 变量
大的数字,如果第二个值赋值给 merge,那么历史清单就合并到现存的历史文件中,而不是
替换它。保存的顺序根据时间戳而定。

实例 10.17
1 set savehist
2 set savehist = 1000
3 set savehist = 1000 merge

说明
1 从历史清单中保存到历史文件中的命令将在下一次登录时显示在历史清单的顶端。
2 最后 1000 个历史清单替换历史文件并保存,在下次登录时将显示出来。
3 尽量替换已经存在的文件,当前的历史清单会替换已经存在的历史文件,并在下一次登录时导入
内存。

显示历史。history 命令用来显示历史清单中的事件。history 命令有用于控制显示的数量


和格式的选项,而事件号并不是必须显示的部分。若有 100 个命令储存在历史列表中,你可
以通过将 history 变量设置为 25,来只显示最后 25 个命令。

实例 10.18
1 > set history =10
2 > history
1 ls
2 vi filel
3 df
4 ps -eaf
5 history
6 more /etc/passwd
7 cd
8 echo $USER
9 set
10 ls

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 369

说明
1 由于 history 变量被设置为 10,所以不论存储了多少行,都只显示最后的 10 行。
2 显示最后 10 个命令,且每个命令都带有编号。

实例 10.19

1 > history –h print without line numbers


ls
vi filel
df
ps -eaf
history
more /etc/passwd
cd
echo $USER
set
history –n

2 > history -c

说明
1 选项 h 使得显示没有编号。
2 选项 c 清除了历史记录。

实例 10.20

> history –r # print the history list in reverse


11 history -r
10 history -h
9 set
8 echo $USER
7 cd
6 more /etc/passwd
5 history
4 ps -eaf
3 df
2 vi filel
1 ls

说明
历史列表按照逆序显示。

实例 10.21

> history 5 # prints the last 5 events on the history list


7 echo $USER
8 cd

PDF created with pdfFactory Pro trial version www.pdffactory.com


370 第 10 章

9 set
10 history -n
11 history 5

说明
执行历史列表中的最后 5 个命令。

从历史文件访问命令。这有几种方法可以访问和重复历史文件中的命令。可以用方向键
上下滚动历史列表,而用左右键在行内任意移动;可以用叫作历史替换的机制重新执行和修
复拼写错误;还可以用内建的 emacs 和 vi 编辑器恢复、编辑和执行以前的命令。下面我们
将详细讨论以上述方法,以便你选择。
1.方向键
你可以通过小键盘上的方向键访问历史清单中的命令,并通过上下移动和从左到
右,使用 backspace 和 deleting 等键做标准的编辑。回车键可以使该行重新执行。此外,
你也可以使用标准的 emacs 和 vi 编辑器编辑历史清单。方向键在 emacs 和 vi 中的使用
方法是一样的。见表 10.3 所示。
表 10.3 方向键

↑ 向上移动历史清单
↓ 向下移动历史清单
→ 在历史命令所在行向右移动
← 在历史命令所在行向左移动

2.重新执行和!
!(感叹号)用来启动历史替换以便重新执行历史命令。!可以出现在一行的任意位置,
而且可以通过反斜杠逃逸。!后面如果是空格、制表符或者换行符,它就不被翻译。这里有
多种执行历史清单中任意部分的方法。如果是两个! ,就执行最后一个命令。如果!后面是
一个数字,就执行历史清单中序号是该数字的命令。如果!后面跟一个字母,就执行历史清
单中最后一个以这个字母开头的命令。^也可以作为编辑以前命令的快捷方式。
在执行历史替换后,命令行显示的替换结果将更新历史清单。例如,!!将执行最后一个
命令并把它追加到历史清单的最后一项。如果你希望最后的命令按照其文字格式追加到历史
清单中,例如!!,就需要设置 Shell 变量 histlit。

实例 10.22

1 > date
Mon Feb 8 12:27:35 PST 2000

2 > !!
date
Mon Aug 10 12:28:25 PST 2000

3 > !3
date

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 371

Mon Aug 10 12:29:26 PST 2000

4 > !d
date
Mon Aug 10 12:30:09 PST 2000
5 > dare
dare: Command not found.

6 > ^r^t
date
Mon Apr 10 16:15:25 PDT 2000

7 > history
1 16:16 ls
2 16:16 date
3 16:17 date
4 16:18 date
5 16:18 dare
6 16:18 date

8 > set histlit

9 > history
1 16:18 ls
2 16:19 date
3 16:19 !!
4 16:20 !3
5 16:21 dare
6 16:21 ^r^t

说明
1 在命令行执行 Linux 的 date 命令,随后历史清单被更新,而 date 命令追加到最后一行。
2 !!从清单中提取最后一个命令并重新执行它。
3 历史记录中的第三个命令被重新执行。
4 历史清单中最后一个以字母 d 开头的命令被执行。
5 命令输入错误。
6 ^符号用来替换最后一行命令中的字母,在该行中第一个出现的 r 被替换为 t。
7 在历史替换后,用 history 命令显示历史清单。
8 通过设置 histlit 变量,执行历史替换,但是只是按照字面追加到历史记录中,具体说,就是完全
忠实于输入的原样。
9 通过设置 histlit 变量,history 命令输出所显示的清单完全忠实于历史替换发生以前输入的原样。

实例 10.23

1 % cat filel file2 file3

<Contents of files displayed here>

> vi !:l
vi filel

2 > cat filel file2 file3

<Contents of file, file2, and file3 are displayed here>

PDF created with pdfFactory Pro trial version www.pdffactory.com


372 第 10 章

> ls !:2
ls file2
file2

3 > cat filel file2 file3


> ls !:3
ls file3
file3

4 > echo a b c
a b c
> echo !$
echo c
c

5 > echo a b c
a b c
> echo !^
echo a
a

6 > echo a b c
a b c
> echo !*
echo a b c
a b c

7 > !!:p
echo a b c

说明
1 cat 命令把 file1 中的内容显示在屏幕上,且更新历史清单。把命令行分割为单词,第一个单词的
号码是 0,如果号码前面有一个冒号这个单词就可以从历史清单中提取出来。 !:1 的意思是“从
历史清单的最后一个命令行中提取第一个参数作为当前命令” (单词 D 是命令本身) 。
2 提取最后一个命令行中的第二个参数作为 ls 命令的参数。File2 被打印(File2 是第三个单词。)
3 ls !:3 意思是“把历史清单中最后一个命令行中的第四个单词作为当前命令 ls 的参数” (File3 是
第四个单词。)
4 !加$表示历史清单中最后一个命令行的最后一个参数。该参数即是 C。
5 ^表示第一个参数, !加^表示历史清单中最后一个命令行中命令后第一个参数。
6 *表示所有参数,!加*表示历史清单中最后一个命令行命令后所有的参数。
7 打印历史清单中最后一个命令行,但不执行它。历史清单被更新,在这一行中你不能使用^替换
字母。

表 10.4 替换和历史

事件指示符 含义
! 表示开始历史替换
!! 重新执行前面的命令
!N 重新执行历史清单中第 N 个命令
!-N 重新执行从当前命令向回数,第 N 个命令
!string 重新执行最后一个以 string 开头的命令
!?string? 重新执行最后一个包含 string 的命令

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 373

续表
事件指示符 含义
!?string?% 重新执行最后一个参数包含 string 的命令
!^ 在当前命令行使用历史清单中最后一个命令行的第一个参数
!* 在当前命令行使用理事清单中最后一个命令行的所有参数
!$ 在当前命令行使用理事清单中最后一个命令行的最后一个参数
!!string 追加 string 给前一个命令并执行
!N string 追加 string 给第 N 个命令并执行
!N:s/old/new/ 在前面第 N 个命令行中,将第一个出现的字符串中的 old 替换为 new
!Ngs/old/new/ 在前面第 N 个命令行中,将所有出现的字符串中的 old 替换为 new
^old^new^ 将最后一个历史命令的字符串中的 old 替换为 new
command !N:wn 执行当前命令并把历史清单中第 N 个命令行中的第 wn 个单词作为参数 wn 是
从 0 开始的数字,0 表示命令本身,1 表示第一个参数,以此类推
!N:p 把命令行追加到历史清单的底部并打印它,但是不执行它

10.3.2 内建命令行编辑器
你可以在命令行上使用在 emacs 和 vi 中使用的相同类型的控制键进行编辑,也可以使
用跟编辑器一样的命令向上或者向下滚动历史清单。一旦找到需要的命令,就可以编辑它,
并用回车再次执行它。当 Shell 被编译以后,它默认设置了一系列与 emacs 编辑器兼容的按
键设置。
内建的 bindkey 命令。内建的 bindkey 命令用来设置内建的按键设置是与 emacs 兼容还
是与 vi 兼容。与 vi 兼容的命令行编辑可以使用参数选项-v。如下所示:
bindkey –v

返回到与 emacs 兼容使用,则输入:


bindkey –e

查看编辑命令清单以及简短说明,则输入:
bindkey –l

查看按键与键操作联编的详细情况,则输入:
bindkey

要详细了解按键与命令的键操作联编情况,请参考“键操作联编按键部分” 。
内建 vi 编辑器。要编辑历史清单,则在命令行按 ESC 键。像标准的 i 编辑器一样,按 K
键向上滚动,按 J 键向下滚动。当找到你希望编辑的行后,使用跟 vi 一样的标准键编辑左右
移动、删除插入以及修改文本。参考表 10.5。当编辑结束后按回车键确定。命令就被执行并
追加到历史清单的最后一行中。如果你想增加或者插入文本,可以用任何插入命令(I、e、
o 及 O 等等) 。记住,Vi 有两中模式:命令模式和插入模式,在输入文本的时候,你可以一
直处在插入模式下,按 ESC 回到命令模式。

PDF created with pdfFactory Pro trial version www.pdffactory.com


374 第 10 章

表 10.5 vi 命令

命令 功能
在历史文件中移动
ESC k or + 向上移动历史文件
ESC j or - 向下移动历史文件
G 移动到历史文件的第一行
5G 移动到历史文件的第五行
/string 向上搜索历史文件
? 向下搜索历史文件
在行中移动
h 向左移动
l 向右移动
b 向后移动一个词
e or w 向前移动一个词
^ Or 0 移动到本行第一个字母的开始
$ 移动到行尾
编辑命令
aA 追加文本
iI 插入文本
dd dw x 删除文本到一个缓冲内
cc C 改变文本
uU 撤销
yy Y 复制一行到缓冲
pP 把缓冲区内的行插入当前行的上面或者下面
rR 替换一行内一个字母或者任何数量的文本

内建 emacs 编辑器。如果使用内建的 emacs 编辑器,则需要跟 vi 一样在命令行上启动,


而向上滚动历史文件使用^P,向下滚动使用^N。使用 emacs 编辑命令改变或者更正文本,
一旦按下回车键命令就被重新执行。见表 10.6。

表 10.6 emacs 命令

命令 功能
Ctrl-P 向上移动历史文件
Ctrl-N 向下移动历史文件
ESC < 移动到历史文件的第一行
ESC > 移动到历史文件的最后行
Ctrl-B 向后移动一个字母
Ctrl-R 向后搜索字符串

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 375

续表
命令 功能
EAC B 向后移动一个单词
Ctrl-F 向前移动一个字母
ESC F 向前移动一个单词
Ctrl-A 移动到行的开始
Ctrl-E 移动到行的末尾
ESC< 移动到历史文件第一行
ESC> 移动到历史文件最后行
用 emacs 编辑
Ctrl-U 删除行
Ctrl-Y 向后放置行
Ctrl-K 删除从光标到行末尾的内容
Ctrl-D 删除一个字母
ESC D 向前删除一个单词
ESC H 向后删除一个单词
ESC space 在光标所在位置设置一个标志
Ctrl-X Ctrl-X 交换光标和标志
Ctrl-P Ctrl-Y 把光标到标志之间的区域放入缓冲区(Ctrl-P)
,然后放下(Ctrl-Y)

键操作联编按键。内建命令 bindkey 显示一个包括 emacs 和键操作联编在内的标准键操


作联编清单。键操作联编被分为四个部分:标准键操作联编、备选键操作联编、多字符键操
作联编和方向键操作联编。bindkey 命令允许你改变当前的键操作联编。

实例 10.24

1 > bindkey
Standard key bindings
"^@" -> is undefined
"^A" -> beginning-of-line
"^B" -> backward-char
"^C" -> tty-sigintr
"^D" -> list-or-eof
"^E" -> end-of-line
"^F" -> forward-char
"^L" -> clear-screen
"^M" -> newline
... ....
Alternative key bindings
"^@" -> is undefined
"^A" -> beginning-of-line
"^B" -> is undefined

PDF created with pdfFactory Pro trial version www.pdffactory.com


376 第 10 章

"^C" -> tty-sigintr


"^D" -> list-choices
"^E" -> end-of-line
"^F" -> is undefined
...... .....
Multi-character bindings
"^[[A" -> up-history
"^[[B" -> down-history
"^[[C" -> forward-char
"^[[D" -> backward-char
"^[OA" -> up-history
"^[OB" -> down-history
... ....
Arrow key bindings
down -> down-history
up -> up-history
left -> backward-char
right -> forward-char

-l 选项允许 bindkey 列出编辑命令的清单和其用法。参考实例 10.25。

实例 10.25

> bindkey -1

backward-char
Move back a character
backward-delete-char
Delete the character behind cursor
backward-delete-word
Cut from beginning of current word to cursor – saved in cut
buffer
backward-kill-line
Cut from beginning of line to cursor – save in cut buffer
backward-word
Move to beginning of current word
beginning-of-line
Move to beginning of line
capitalize-word
Capitalize the characters from cursor to end of current
word
change-case
Vi change case of character under cursor and advance one
character
change-till-end-of-line
Vi change to end of line
clear-screen
Standard key bindings
..... ...

bindkey 也可以像实例 10.26 中那样显示单个键操作联编的值。默认显示 emacs 的键盘

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 377

映射,-a 选项可以选择显示 vi 的键盘映射。bindkey 可以指定一个键序列来替换当前编辑命


令已经捆绑好的键操作联编。你不仅可以把 emacs 与 vi 的编辑命令捆绑在一起,还可以将
其与 Linux 命令或者字符串捆绑。
表 10.7 键操作联编字符

字符 含义
^C Control-C
^[ ESC
^? SEL
\a Control-G(bell)
\b Control-H(backspace)
\e ESC
\f Formfeed
\n Newline
\r Returne
\t Tab
\v Control-K(垂线)
\nnn ASCII 十进制数

实例 10.26

1 > bindkey ^L
"^L" -> clear-screen

2 > bindkey ^C
"^C" -> tty-sigintr

3 > bindkey "j"


"j" -> self-insert-command
4 > bindkey -v

5 > bindkey -a "j"


"j" -> down-history

说明
1 bindkey 与一个控制键作为参数,显示这个控制键所绑定的命令。在这里,^L 的作用是清除屏幕。
2 Control-C 与终止符号捆绑在一起,通常用来终止一个进程。
3 小写的 j 是 emacs 下的一个自插入命令,只把一个字母放入缓冲区内。
4 为了查看可选择的 vi 键操作联编,需要用命令 bindkey –v 设置命令行 vi 编辑器。
5 通过选项 –a 显示可选的 j 的键映射。例如,vi 可以向下移动历史清单。

实例 10.27

1 > bindkey "^T" clear-screen

PDF created with pdfFactory Pro trial version www.pdffactory.com


378 第 10 章

2 > bindkey "^T"


"^T" -> clear-screen

3 > bindkey –a "^T"


"^T" -> undefined-key

4 > bindkey –a [Control-v Control t] clear-screen


Press keys one after the other

5 > bindkey –a [Control-v Control t]


"^T" -> clear-screen

6 > bindkey –s '\ehi' 'Hello to you!\n'


> echo [Esc]hi Press escape followed by 'h' and 'i'
Hello to you!
>

7 > bindkey '^[hi'


"^[hi" -> "Hello to you!"

8 > bindkey –r '\[hi'

9 > bindkey '\ehi'


Unbound extended key "^[hi"

10 > bindkey –c '\ex' 'ls | more'

说明
1 Control-T 绑定清除屏幕的命令,它是默认的 emacs 键盘映射。这个键本身并不绑定任何东西。
当 Control 和 T 一起按下的时候,屏幕就被清除了。
2 bindkey 命令后面用键序列作为参数,将显示这个键序列的映射。
3 用选项-a 和一个键序列作为参数,bindkey 显示可选映射,在这个例子中,Vi 没有给这个键序列
绑定任何命令或者字符串。
4 用选项-a,bindkey 可以绑定键序列到一个可选的映射。
5 bindkey 功能显示^T 和其命令的可选映射。
6 用选项-s,bindkey 绑定一个字符串到键序列。
7 bindkey 命令显示逃逸序列 hi 的绑定,^[是另一种实现 ESC 的方法。
8 用选项-r,bindkey 删除一个键操作联编。
9 因为键操作联编被删除,所以输出说这个扩展键序列没有找到。
10 用-c 选项,bindkey 绑定键盘到一个 Linux 命令。

表 10.8 bindkey 命令选项

绑定键 与该键绑定的功能
bindkey –a 允许可选键映射
bindkey –d 恢复默认绑定
bindkey –e 使用 emacs 绑定
bindkey –l 显示所有编辑命令及其用法
bindkey –u 显示使用信息
bindkey –v 使用 vi 键操作联编

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 379

续表
绑定键 与该键绑定的功能
bindkey key 显示 key 的绑定
bindkey key command 绑定 key 为 emacs 或者 vi 命令
bindkey –c key command 绑定 key 为 Linux 或者 UNIX 命令
bindkey –s key string 绑定 key 为字符串
bindkey –r key 删除 key 的绑定

10.3.3 命令、文件名和变量 completion


为了快速输入,tcsh 的 completion 机制允许你只输入命令、文件名或者变量的一部分,
然后按 Tab 键,则其他部分就自动完成。
如果只输入一个命令开头的几个字母,然后按 Tab 键,tcsh 就会尝试完成命令名。如果
tcsh 无法完成命令,那就是它不存在。终端将发出“哔”的一声,且光标停在命令行的末尾。
如果有不止一个命令以这样的字母开头,按 Control-D 则会列出所有以这些的字母开头的命
令清单。
文件名和变量 completion 跟命令是一样的。如果有多个文件名都匹配,tcsh 就显示最短
的文件名、扩展文件名直到字母不同为止,光标闪烁为你完成文件名。参考实例 10.28。
变量 autolist。如果变量 autolist 被设置,根据 Tab 键被按时的哪些类型的 completion 在
运行,会出现很多可能的完成,所有可能的命令、文件名和变量都将被显示出来。

实例 10.28

1 > ls
filel file2 foo foobarckle fumble

2 > ls fu[tab] expands to filename to fumble

3 > ls fx[tab] terminal beeps, nothing happens

4 > ls fi[tab] expands to file_ (_ is a cursor)

5 > set autolist

6 > ls f[tab] lists all possibilities


filel file2 foo foobarckle fumble

7 > ls foob[tab] expands to foobarckle

8 > da[tab] completes the date command


date
Fri Aug 9 21:15:38 PDT 2000

9 > ca[tab] lists all commands starting with ca


cal captoinfo case cat

10 > echo $ho[tab]me expands shell variables


/home/ellie/

PDF created with pdfFactory Pro trial version www.pdffactory.com


380 第 10 章

11 > echo $h[tab]


history home

说明
1 所有当前目录下的文件都被显示出来。
2 输入 fu 以后,按 Tab 键,文件名自动完成 fumble。
3 因为没有以 fx 开头的文件命令和变量,终端发出“哔”的一声,且光标在原地闪烁。
4 有一些文件以 fi 开头,知道不再有相同的字母,文件名才被完成。按 Control-D 显示整个清单。
5 设置 autolist 变量。如果有不止一个选择,你按 Tab 键,就显示所有的可能性。
6 按下 Tab 键,所有以 f 开头的文件名都被打印。
7 按下 Tab 键,文件名被完成为 foobarcle。
8 在输入 da 以后,按下 Tab,因为只有命令 date 以这个开头,因此命令被自动完成并执行。
9 因为 autolist 被设置,输入 ca 以后,
按下 Tab 键,所有以 ca 开头的命令都被显示出来。如果 autolish
被设置,按下 Control-d 显示所有以 ca 开头的命令。
10 以$开头表示,在按 Tab 键自动完成的时候,Shell 应该把它看做一个变量。变量 home 自动完成。
11 在这个例子中变量 completion 是模糊的,在按下 Tab 键尝试自动完成的时候,所有可能的变量都
显示出来。

变量 fignore。在使用文件名自动完成时,通过设置变量 fignore 可以忽略特定的文件


名扩展。例如你不希望自动完成以.o 结尾的文件名(因为它们是不可以读的)或者不希望
在使用自动完成时把.gif 文件误删除,又或者其他的原因,变量 fignore 的值可以是一系列
文件扩展名,这些扩展名的文件将不会在文件名自动完成中出现。

实例 10.29

1 > ls
baby box.gif file2 prog.c
baby.gif filel file3 prog.o

2 > set fignore = (.o .gif )

3 > echo ba[tab] Completes baby but ignores baby.gif


baby

4 > echo box[tab].gif fignore is ignored if only one completion


box.gif is possible

5 > vi prog[tab] expands to prog.c


Starts vi with prog.c as its argument

说明
1 显示当前目录下的文件名,注意,其中的一些文件名也扩展名。
2 设置变量 fignore,使得一些扩展名的文件在文件名自动完成时不出现,以.o 和.gif 扩展名结束的
文件名在文件名自动完成中将被忽略。
3 按下 Tab 键,只有文件 body 显示出来,文件 body.gif 被忽略,因为其扩展名是 gif。
4 当没有其他文件名可以匹配时,变量 fignore 也无效了,虽然文件的扩展名是.gif,但还是显示出
来了。
5 当 vi 编辑器被启动,prog 就被扩展为 prog.c。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 381

Shell 变量 complete。它一个功能强大的变量。tcsh 的 man 页中对其作用的描述很全面


但是也很复杂可以通过下面的一些例子来了解它。你可以控制自动完成的类型。例如,你可
能只需要完成目录名,或者根据在命令行的不同位置来完成文件名,也许你只想自动扩展特
定的文件甚至建立一个可被扩展的单词的列表。无论你想用自动完成功能做什么,Shell 变
量 complete 都可以帮你做到。
如果把 Shell 变量 complete 设置为 enhance,文件名自动完成可以变的十分复杂。这使
得 Tab 文件名自动完成将忽略一些事情,把连字号、周期和下划线作为单词分隔符,且把连
字号和下划线看做一样的。

实例 10.30

1 > set complete=enchance

2 > ls g..[tab] expands to gawk-3.0.3


gawk-3.0.3

3 > ls GAW[tab] expands to gawk-3.0.3


gawk-3.0.3

说明
1 把 Shell 变量 complete 设置为 enhance,文件名 completion 可以变的十分复杂。这使得 Tab 忽略
一些事情,把连字号、周期和下划线作为单词分隔符,把连字号和下划线看做等价。
2 通过设置 enhance,文件名 completion 扩展为 g..,表示以 g 开头后面跟任意两个字符的文件名,
任意两个字符可以是连字号和周期等等。
3 通过设置 enhance,文件名 completion 扩展为 GAW,表示文件名以 GAW 开头(其中 GAW 可以
是任意大小写的组合),而其他字符可以为任意字符,包括连字号、周期和下划线。

对 completion 编程。为了更好地个性化自动完成的功能,我们可以对自动完成编程,
并存储在~/.tcshrc 文件中,每次启动 tcsh 的时候,把它作为用户环境的一部分启动。编程的
目的在于提高效率和选择自动完成时发生作用的命令和参数。
自动完成的类型。有三种类型的自动完成:p、n 和 c。p 类型的自动完成是位置依赖型
的(position-depandent)。它的完成方式是依赖于单词在命令行中的位置,位置 0 是命令,位
置 1 是第一个参数,位置 2 是第二个参数等等。假设你希望在任何时候都能够保证内建命令
cd 可以“自动完成”,第一个也是惟一的一个参数就是目录名称,你可以按照如下的例子所
示进行编程:
complete cd 'p/1/d'

在 complete 命令后面跟着 cd 命令被称为“自动完成规则”(completion rule)。p 表示单


词在命令行中的位置。cd 在命令行中的位置是 0,第一个参数的位置是 1,规则部分的模式
是括号被的反斜杠(p/1/表示位置 1),这些规则在自动完成时将发挥作用。模式部分中的 d
被称为单词类型。参考表 10.9 学习全部的单词类型。这里的 d 表示只有目录才在自动完成
中有效。例如,文件名或者文件别名如果在自动完成中作为 cd 命令的第一个参数则是无效
的。这意味着,无论什么时候,当用 Tab 键完成 cd 命令的时候,第一个参数只出现目录。
Control-D 将列出所有的目录。参见实例 10.31 学习 p 类型的自动完成。

PDF created with pdfFactory Pro trial version www.pdffactory.com


382 第 10 章

实例 10.31

# p-type completions (positional completion)

1 > complete
alias 'p/1/a/'
cd 'p/1/d/'
ftp 'p/1/( owl ftp.funet.fi prep.ai.mit.edu )'
man 'p/*/c/'

2 > complete vi 'p/*/t/'

3 > complete vi
vi 'p/*/t/'

4 > set autolist

5 > man fin[tab] Completes command names


find find2perl findaffix findsmb finger

6 > vi b[tab] Completes only filenames, not directories


bashtest binded bindings bindit

7 > vi na[tab]mes

8 > cd sh[tab]ellsolutions/

9 > set hosts = ( netcom.com 192.100.1.10.192.0.0.200)

10 > complets telnet 'p/1/$hosts/'

11 > telnet net[tab]com.com


telnet netcom.com

12 > alias m[tab] Completes alias names


mc mroe mv

13 > ftp prep[tab]

说明
1 内建的 complete 命令在不带参数时列出所有已经编程的自动完成清单。后面的例子将使用这些规
则。
2 这个规则的含义是,如果在输入 vi 命令时使用 Tab 实现自动完成,则所有参数都必须都是 t 类型
的(例如,可以是文本文件) 。
3 若命令 complete 用一个命令名作为其参数,则将显示这个命令的自动完成规则。这里显示的是
vi 命令的自动完成规则。
4 通过设置内建命令 autolist,所有可能的自动完成都被自动打印。
5 man 命令有一个已经编程的 completion:complete man ‘p/1/c’这表示 man 的第一个参数必须是命
令名。c 被定义为表示命令名类型的参数。所以这里所有以 fin 开头的命令都被显示出来。
6 只有文件名才可以被自动完成,因为在 vi 命令的自动完成规则中已经规定,只有文件而不是目
录才可以作为 vi 的参数。
7 根据规则只有文本文件才可以作为 vi 的参数。
8 根据规则,cd 命令的第一个参数只能是目录,因此这里完成只显示目录名 shellsolutions。
9 变量 hosts 被设置为一系列 IP 地址或者主机名。
10 telnat 的自动完成规则表示第一个参数必须包含变量 hists 中的一个主机名。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 383

11 执行 telnet 命令,第一个参数单词以 net 开头,按下 Tab 键后,netcom.com 就自动完成。它是预


先设置的 hosts 变量中的一个元素。
12 如果用户输入 alias 后面跟着一个单词,alias 将显示所有这个单词可能扩展成为的单词清单,单
词类型 a 意味着别名扩展为 p 类型,位置为 1。

表 10.9 自动完成的单词类型

命令 含义
a 别名
b 编辑键操作关联命令
c 命令(内部的和外部的)
C 外部命令,以给定的前缀开头
d 目录
D 以给定的前缀开头的目录
e 环境变量
f 文件名但不包括目录
F 以给定的前缀开头的文件名
g 组名
j 作业
l 限制
n 无
s shell 变量
S 信号
t 文本文件
T 以给定的前缀开头的文本文件
v 任何变量
u 用户名
X 已经定义 completion 的命令名
x 同 n,但是按下 Control-D 才打印有关信息
C、D、F 和 T 同 c、d、f、t,但是从给定的目录中选择 completion
(list) 从单词列表中选择 completion

c 类型的自动完成用于完成当前单词的模式,当前单词只是包含在斜杠内的模式。它的
规则是如果模式被匹配,则任何的 completion 都可以来完成模式。

实例 10.32

# c-type completions

1 > complete
stty 'c/-/(raw xcase noflsh)/'
bash 'c/-no/(profile rc braceexpansion)/'

PDF created with pdfFactory Pro trial version www.pdffactory.com


384 第 10 章

find 'c/-/(user name type exec)/'


man 'c/per1/(delta faq toc data modlib locale)/'

2 > stty –r[tab]aw


stty -raw

3 > bash –nop[tab]rofile


bash -noprofile

4 > find / -n[tab]ame .tcshrc –p[tab]rint


find / -name .tcshrc -print

5 > man perlde[tab]lta


man perldelta

6 > uncomplete stty


> complete
bash 'c/-no/(profile rc braceexpansion)/'
find 'c/-/(user name type exec)/'
man 'c/perl/(delta faq toc data modlib locale)/'

7 > uncomplete *

说明
1 这是一个 c 类型的自动完成的示范。如果第一对斜杠所内的模式被输入,Tab 键就从括号内的单
词列表中找出一个完成这个模式。
2 当输入 stty 命令时,如果后面紧跟着一个-,并且输入一个 r,则按 Tab 就自动完成 raw。自动完
成只能从规则括号列表中选择一个单词完成模式。
3 输入 bash 命令,后面紧跟一个模式 –no 和一个 p,按 Tab 键,模式将自动完成为-nofrofile。自
动完成只能从规则括号列表中选择一个单词完成模式。
4 如果 find 命令的参数是-符号,后面跟一个单词,那么自动完成就在规则列表中寻找有关匹配的
单词,完成扩展。
5 输入 man 命令,由于模式后面有一个来自列表的单词,所以模式 perl 被自动完成为 perldelta。
6 内建命令 uncomplete 删除 stty“自动的完成规则”
,保留其他的“自动完成规则”。
7 若内建命令 uncomplete 的参数为*,则删除所有的“自动完成规则” 。

N 类型自动完成,一旦匹配第一个单词,就自动完成第二个。

实例 10.33

# n-type completions (next word completion)

1 > complete
rm 'n/-r/d/'
find 'n/-exec/c/'

2 > ls –ld testing


drwxr-sr-x 2 ellie root 1024 Aug 29 11:02 testing

3 > rm –r te[tab]sting

说明
1 这些例子用来说明 n 类型完成。如果输入第一对斜线内的单词并被匹配,就根据单词列表完成下
一个单词。complete 命令列出两个 n 类型完成,一个是为 rm 命令,另一个是 find 命令。当 rm

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 385

命令执行时,-r 作为开关,如果完成发生作用,-r 后面跟的一定是目录。find 命令的规则是:如


果给定-exe,完成发生作用,后面跟的参数必须是命令。
2 ls 命令的输出说明 testing 是一个目录。
3 因为尝试完成一个目录名 testing,因此 rm 命令的文件名完成成功。如果 testing 是一个文本文件,
自动完成就不会发挥作用。

10.3.4 处理目录堆栈
如果你发现,在工作时,经常需要使用 cd 命令在目录树上上上下下,且总是一些相同
的目录,就可以通过把它们放入目录堆栈并处理目录堆栈,从而简化对目录的访问。目录堆
栈经常被比喻为自助餐厅的盘子,第一个在最下面。内建命令 pushd 把目录推入堆栈,而 popd
命令则删除它们。目录堆栈就是一些目录的列表,最新被推入堆栈的目录在最上面。目录从
堆栈的顶部是 0 且由此开始,下一个是 1,以此类推。内建命令 dirs –v 显示目录堆栈号。
pushd 命令和 pop 命令。pushd 命令以一个目录作为其参数,从而在堆栈中加入一个新
的目录,并同时改变当前目录到该目录。如果参数是一个减号,减号表示前一个工作目录。
如果是+和一个数字 n,则 pushd 命令就把第 n 个目录从堆栈中抽出并推入堆栈的顶端,同
时改变到那个目录。如果没有参数就交换堆栈顶端的两个目录。有一些 Shell 变量用来控制
pushd 命令的行为。
要通过登录会话保存目录堆栈,则必须设置 savedirs 变量为 tcsh 的一个初始化文件。目
录堆栈保存在一个叫作~/cshdirs 的文件中,并在 Shell 启动的时候被自动读取。
popd 命令用于从堆栈顶端删除一个目录并改变到那个目录。
表 10.10 堆栈目录变量

pushdtohome 如果设置,就同于 pushd !~或者 cd


dunique 在把目录推入堆栈前删除堆栈中同名的目录
pushdsilent 执行 pusgd 时候不打印堆栈
deextract 如果设置,pushd +n 就先抽出第 n 个目录,然后再把它推入堆栈
pushtohome 若没有参数就推到~,即用户主目录
dirsfile 设置一个在登录时用于保存目录堆栈的文件名
savedirs 在登录时保存目录堆栈
dirstack 显示堆栈并给堆栈分配目录

实例 10.34

1 > pwd
/home/ellie

> pushd ..
/home ~

> pwd
/home

2 > pushd # swap the two top directories on the stack

PDF created with pdfFactory Pro trial version www.pdffactory.com


386 第 10 章

~ /home

> pwd
/home/ellie

3 > pushd perlclass


~/perlclass ~ /home

4 > dirs -v Directory stack


0 ~/perlclass
0 ~/perlclass
1 ~
2 /home 1 ~

5 > popd 2 /home


~ /home
> pwd
/home/ellie

6 > popd
/home

> pwd
/home

7 > popd
popd: Directory stack empty.

说明
1 第一个 pwd 命令显示当前工作目录。下一个 pushd 命令以..作为其参数,把父目录推入堆栈。pushd
命令输出显示/home 目录在堆栈的顶部。用户主目录/home/ellie 在堆栈的底部。pushd 命令改变目
录到最后一个被推入堆栈的目录。具体的说,..翻译为/ho me。用 pwd 显示新的目录。
2 没有参数的 pushd 命令,交换堆栈顶部的两个目录。在这个例子中,目录被切换回用户主目录
/home/ellie。
3 pushd 把参数~/perlclass 推入堆栈,并改变到那个目录。
4 内建命令 dirs 显示目录堆栈号,最上面的号码是 0。参考实例 10.34 中的堆栈目录图。
5 popd 命令从堆栈中删除顶端的目录并改变到那个目录。
6 popd 命令从堆栈中删除另外一个目录并改变到那个目录。
7 因为堆栈是空的,所以 popd 无法删除任何目录,最终显示一个关于这个错误信息。

10.3.5 拼写检查
拼写检查是加入到 TC Shell 中来的特性,用于检查文件名、命令和变量的拼写错误。如
果使用 emacs 内建编辑器可以通过与 Meta-S 或者 Meta-s 捆绑在一起的拼写检查键纠正拼写
错误,Meta$用来检查整行。提示符 prompt3 的值显示拼写检查信息。8
如果使用 vi 内建编辑器,则通过设置内建变量 correct,shell 就会提示你改正拼写。

实例 10.35

1 > fimger[Alt-s] Replaces fimger with finger

8.tcsh in man 页提示:“注意:拼写检查不保证总是工作准确。它只提供一种基于经验的特性,欢迎给我们提出建议和改进!”。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 387

2 > set correct=all

3 > dite

CORRECT>date (y|n|e|a)? yes


Wed Aug 11 19:26:27 PDT 2000

4 > dite

CORRECT>date (y|n|e|a)? no
dite: Command not found.
>

5 > dite

CORRECT>date (y|n|e|a)? edit


> dite▌ Waits for user to edit and then executes command

6 > dite

CORRECT>date (y|n|e|a)? abort


>

说明
1 通过按下 Meat 键(或 ESC 或 ALT)和一个 s,文件名、命令和变量的拼写就被纠正了。如果你
使用的是内建的 vi 编辑器则无法使用这项功能。
2 通过设置 correct 变量,tcsh 尝试在命令行修正所有的拼写错误。这个功能在内建的 emacs 和 vi
编辑器中都可以跟键盘绑定。
3 因为命令拼写不正确,第三提示符,prompt3,“CORRECT>data(y|n|e|a)?”出现在屏幕上,如果
想进行拼写检查就输入 y,否则输入 n,自己编辑就输入 e,输入 a 则中断整个操作。
4 如果用户不希望改变命令行,就输入 n。
5 如果用户希望通过编辑修改,则输入 2(将被提示手工修改命令)。
6 如果修改是错误或意外的,用户可以通过输入 a,终止拼写纠正。

表 10.11 correct 变量的参数

参数 功能
cmd 拼写检查命令
complete 完成命令
all 拼写检查整个命令行

10.3.6 别名
别名是 TC Shell 中用户自定义的命令缩写。如果命令有很多的参数和选项或者语法复杂,
别名就显得十分有用了。别名在命令行设置并不能被子 Shell 所继承。它通常在文件.tcshrc 文
件中设置。因为每次新 Shell 启动时它都要被执行一次,于是文件中所有别名设置在这时候都
将重置。别名也可以被传递到脚本中,但这会导致保护可移植性问题,除非它们在脚本中被
直接设置。
TC Shell 有一些预先设置好的别名,直到定义它们前都不会被定义。它们是:beepcmd、
cwdcmd、periodic 和 precomd。这些别名的定义请参考“特殊别名”部分。

PDF created with pdfFactory Pro trial version www.pdffactory.com


388 第 10 章

列别名清单。内建命令 alias 可以列出所有别名的清单。首先打印别名,后面是真实的


命令。

实例 10.36

> alias
apache $HOME/apache/httpd –f $HOME/apache/conf/httpd.conf
co compress
cp cp -i
lsl enscript –B –r –Porange –f Courier8 !* &
mailq /usr/lib/sendmail /bp
mc setenv MC '/usr/bin/mc –P !*'; cd $MC; unsetev MC
more more
mv mv -i
uc uncompress
uu uudecode
vg vgrind -t –sll !:l | lpr -t
weekly (cd /home/jody/ellie/activity; ./weekly_report; echo
Done)

说明
alias 命令显示所有别名清单。第一列是别名,第二列是与别名等价的真实命令。

创建别名。alias 命令可以用来创建别名,第一个参数是别名,其余部分由与别名等价的
真实命令组成。若有多个命令则用分号分隔,命令如果包含空格和元字符则需要用单引号引
用。

格式

alias
alias aliasname command
alias aliasname 'command command(s)'
unalias aliasname

实例 10.37

1 > alias m more


2 > alias mroe more
3 > alias lf ls-F
4 > alias cd 'cd \!*; set prompt = "%/ >"'
5 > cd ..
6 > /home/jody > cd / # new prompt displayed
/ >

7 > set tperiod = 60


> alias periodic 'echo You have worked an hour, nonstop'
8 > alias Usage 'echo "Error: \!* " ; exit 1'

说明
1 设置 more 命令的别名(昵称)为 m。
2 设置 more 命令的昵称为 m,这有助于你避免拼写错误。
3 lf 是 tcsh 的内建命令 ls –F 的别名,它的作用同 ls –F 一样,但是速度要快得多。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 389

4 当执行 cd 命令的时候,cd 命令的别名会使得 cd 切换到作为参数的目录,并重新设置提示符为当


前工作目录,后面是“>” 。!*在别名中的用法就跟它们在历史机制中的用法是一样的。反斜线用
来防止!*在被别名使用前就被历史机制赋值。\!*表示历史记录中最近的命令的参数。由于包
含空格,所以别名的定义需要双引号引用。
5 用 cd 命令改变当前目录后,提示符扩展为当前工作目录和“>”
6 新目录是/home/jody,提示符也相应改变。而当改变目录到 root 时,提示符也相应改变。
7 设置 tperiod 变量值为 60 分钟。别名 periodic 是预设置的别名,即每 60 分钟显示一次 echo 命令。
8 别名用在脚本中显示诊断信息并退出脚本。这种别名使用的例子可以参考实例 11.29。
a. lf using/bin/csh as your shell,replace%/ with $cwd when setting the prompt.

删除别名。unalias 命令用来删除别名。在别名前面加反斜线可以临时关闭别名。

实例 10.38

1 > unalias mroe


2 > \cd ..

说明
1 用 unalias 命令从别名列表中删除别名 more。
2 要执行命令本身,临时关闭别名 cd。

别名环。当一个别名指向另一个别名,这个别名又指向原来的别名的时候,就发生了别
名环(Alias Loop)。

实例 10.39

1 > alias m more


2 > alias more m
3 > alias m mroe # Causes a loop
4 > m datafile
Alias loop.

说明
1 定义别名 m 为命令 more 的别名。每次执行 m,命令 more 就执行。
2 定义别名 mroe 为别名 m 的别名,每次执行 more,就激活 m,执行命令 more。
3 这一行正是元凶。因为别名 m 已经用过了,它指向 more,现在 more 又指向回 m,因而出现了别
名环。这时候什么也不会执行,但是可以得到错误信息。
4 别名 m 已经被使用过,现在已经是环状了。m 激活 more,而 more 激活 m。TC Shell 将显示错误
信息,避免陷入无限循环中。

10.4 作 业 控 制
作业控制是 TC Shell 强有力的特征之一,它允许你运行一些程序,这些程序叫作作业,
在后台或者前台运行。通常,命令行输入的命令在前台运行直到结束。如果你有窗口程序,
作业控制就不是必须的了,因为你可以简单地打开一个新窗口来启动新任务。相反,在单终
端的情况下,作业控制就非常有用了。作业控制命令参见表 10.12。

PDF created with pdfFactory Pro trial version www.pdffactory.com


390 第 10 章

表 10.12 作业控制命令

命令 含义
jobs 显示所有正在运行的作业
^z 停止作业,显示提示符
bg 在后台运行被停止作业
fg 把一个后台作业放在前台
kill 给执行的作业发送 kill 信号

作业命令的参数 含义
%n 第 n 个作业
%string 名字以字符串开头的作业
%?string 名字包含字符串的作业
%% 当前作业
%+ 当前作业
%- 当前作业的前一个作业

10.4.1 后台作业
&符号。如果命令需要运行很长的时间才能完成,则可以在命令后追加一个&符号,使
得程序在后台运行。tcsh 的提示符立刻出现在屏幕上,你可以继续输入新命令。这样,这两
个命令就在同一时刻运行了,一个在前台一个在后台。它们都把自己的输入写到标准输出。
如果你在后台运行一个程序,把程序的输出重新定向到一个文件,或者通过管道到打印机也
是一个好主意。

实例 10.40

1 > find. –name core –exec rm {} \; &


2 [1] 543
3 >

说明
1 。a
find 命令在后台运行(如果没有-print 参数,find 不会打印任何信息到监视器)
2 方括号中的数字表示这是后台运行的第一个作业,这个作业的 PID 是 543。
3 立刻返回提示符等待新的命令。

a. find 语法要求 exe 语句后面必须有分号。如果在分号前有一斜杠,Shell 就不会去翻译分号而把它看做本身字面意义。

挂起键序列。挂起键^Z 用来挂起程序。作业现在被挂起,就显示提示符,直到输入 fg
或者 bg 命令,否则程序不会继续。你如果在程序挂起时尝试退出登录,屏幕上将显示信息
“There are suspended jobs”。
jobs 命令和 listjobs 变量。tcsh 的内建命令 jobs 用于显示当前活动的、在后台运行或者

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 391

挂起的程序。上面提到的“运行”是指正在后台运行。当一个作业被挂起,它就被暂停了,
不再被执行。在这种情况下,终端仍可以接受新命令。如果你想在作业被挂起过程中退出
Shell,就会得到信息“There are suspended jobs”。此刻如果你再次尝试退出,Shell 就会终止
挂起的作业。如果你希望在挂起作业时自动打印信息,则可以通过设置 tcsh 的内建变量
listjobs 来完成。

实例 10.41
(The Command Line)

1 > jobs
2 [1] + Suspended vi filex
[2] - Running sleep 25
3 > jobs -1
[1] + 355 Suspended vi filex
[2] - 356 Running sleep 25
4 [2] Done sleep 25

5 > set listjobs = long


> sleep 1000
Press Control-z to suspend job
[1] + 3337 Suspended sleep 1000
>
6 > set notify

说明
1 jobs 命令列出当前活动的作业。
2 [1]表示第一个作业,+表示它不是最近一个被放入后台的作业。斜线表示这是最近一个被放入后
台的作业。suspended 表示作业现在已经被^Z 停止,不再运行。
3 -l 选项显示作业数和每一个作业的 PID 号码。[2]表示第二个作业,在这里是最后一个被放入后台
的作业。斜线表示这是最近的作业。sleep 命令在后台运行。
4 在 sleep 命令运行 25 秒钟后作业结束,打印一条信息到屏幕说明作业已经完成。
5 若 listjobs 变量被设置为 long,则在挂起时将打印作业号和进程 id。
6 如果作业被停止了,则在打印提示符以前 Shell 通常会提示你,但是如果 notify 变量被设置,那
么一旦后台作业状态有变化,Shell 就会立刻通知你。例如你工作在 vi 编辑器中,后台一项作业
终止了,vi 窗口中会立刻显示如下信息:
[1]Terminated sleep20

10.4.2 前台和后台命令
fg 命令把后台的作业放到前台执行。bg 命令在后台启动一个被挂起的作业并运行它。
如果你选择作业进行作业控制,百分号和它后面一个表示作业号的数字可以用做 fg 和 bg
的参数。

实例 10.42

1 > jobs

PDF created with pdfFactory Pro trial version www.pdffactory.com


392 第 10 章

2 [1] + Suspended vi filex


[2] – Running cc Prog.c –o prog

3 > fg %1
vi filex
(vi session starts)

4 > kill %2
[2] Terminated c prog.c –o prog

5 > sleep 15
(Press ^z)

Suspended
6 > bg
[1] sleep 15 &
[1] Done sleep 15

说明
1 jobs 命令列出当前运行的进程,也称为作业。
2 第一个停止的作业是 vi 的会话,第二个作业是 cc 命令。
3 [1]作业被放入前台,号码前有百分号。
4 kill 命令是内建的。它默认发送 TERM 信号给进程。参数可以是进程号或者 PID。
5 sleep 命令被^Z 停止了。该命令不再占用 CPU,而是在后台挂起。
6 bg 命令启动并执行最后一个后台作业。sleep 程序要在重新执行以前数秒。a

a. 程序 grep、sed 和 awk 有自己的一套为模板匹配设计的元字符,叫作正则表达式元字符,不要把它与 Shell 元字符相互混淆。

10.4.3 作业调度
内建命令 sched 允许你创建一个作业列表来调度作业在指定的时间运行。没有参数的
sched 显示所有被调度的事件。其设置时间的格式是 hh:mm,小时可以是 24 小时或者 12
小时 AM/PM 的格式。也可以用+指定相对时间,而-可以把时间从列表中删除。

格式

sched
sched [+]hh:mm command
sched -n

实例 10.43

1 > sched 14:30 echo '^G Time to start your lecture!'

2 > sched 5PM echo Time to go home.

3 > sched +1:30 /home/ellie/scripts/logfile.sc

4 > sched
1 17:47 /home/scripts/logfile.sc
2 5PM echo Time to go home.
3 14:30 echo '^G Time to start your lecture!'

5 > sched -2

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 393

> sched
1 17:47 /home/scripts/logfile.sc
2 14:30 echo '^G Time to start your lecture!'

说明
1 sched 命令调度 echo 命令在 14:30 执行,执行时候先是一声 beep(Control-G)a,然后显示信息。
2 sched 命令调度 echo 命令在 5PM 执行。
3 脚本 logfile.sc 在现在 1 小时 30 分钟以后执行。
4 sched 命令显示被调度的事件,按数字顺序,从最后一个到第一个。
5 sched 命令和一个数字参数,数字所对应的作业将被从调度作业列表中删除。sched 的输出显示,
2 号作业被删除。

a.输入 Gtrl-M,然后输入 Gtrl-V,最后输入 Gtrl-G,可以把^G 输入到 echo 语句中。

10.5 元 字 符
元字符是一些特殊的字符,它们用于表示其自身以外的意思。那些既不是字母也不是数
字的字符就是元字符。Shell 有自己的元字符集,通常叫作 Shell 通配符。Shell 元字符可以用
来把一些命令整合在一起,缩写文件名和路径,重新定向和管道输入输出,以及放置命令在
后台等等。表 10.13 列出的是一部分 Shell 元字符列表。
表 10.13 Shell 元字符

元字符 作用 例子 含义
$ 变量替换 set name=Tom 设置变量 name 为 Tom,并显示该
echo $name Tom 变量值

! 历史替换 !3 再次执行历史记录中的第三项
* 文件名替换 rm * 删除所有文件
? 文件名替换 ls ?? 列出所有两字符的文件
[] 文件名替换 cat f[123] 显示文件 f1、f2 和 f3 的内容
; 命令分隔符 ls;data;pwd 按顺序执行每一个命令
& 后台处理 lp mbox& 后台打印,立刻返回提示符
> 从定向输出 ls>file 重新定向标准输出到文件
< 从定向输入 ls <file 重新定向标准输入来自文件
>& 从定向输出和错误 ls>&file 重新定向输出和错误到文件
>! 如果设置了 boclobber 就覆盖它 ls >!file 如果文件存在,即使 noclobber 已经
被设置,也覆盖该文件
>>! 如果设置了 boclobber 就覆盖它 ls >>!file 即使 noclobber 被设置,如果文件不
存在,就建立文件
() 在子 Shell 中执行一组命令 (ls;pwd)>tmp 执行命令并发送输出到文件 tmp
{} 在当前 Shell 中执行一组命令 {cd/;echo $cwd} 改变目录到 root 并显示当前目录

PDF created with pdfFactory Pro trial version www.pdffactory.com


394 第 10 章

10.5.1 文件名替换
在命令行方式下,Shell 可以通过使用匹配特定的元字符集来简化文件名和路径。文件
名替换元字符可以在表 10.14 中看到,元字符扩展为文件名的过程也叫作 globbing。与众不
同的是 C Shell 不能做文件名替换,Shell 会报告说“No matche”(无匹配项)。
表 10.14 Shell 元字符集和文件名替换

元字符 含义
* 匹配 0 个或者多个字符
? 仅仅匹配一个字符
[abc] 匹配 a b c 中的一个字符
[a-z] 匹配从 a~z 中的一个字符
[^abc] 匹配非 a、b、c 的任何字符
{a,ile,ax} 匹配一个或者一组字符
~ 把~替换为用户主目录
\ 禁用元字符

扩展元字符。Shell 通过给元字符替换为恰当的字母或者数字来替换文件名。
星号。幸好匹配文件名中 0 个或者多个字符。

实例 10.44

1 > ls
a.c b.c abc ab3 filel file2 file3 file4 file5

2 > echo *
a.c b.c abc ab3 filel file2 file3 file4 file5

3 > ls *.c
a.c b.c

4 > ls ^*.c
abc ab3 filel file2 file3 file4 file5

5 > rm z*P
No match.

说明
1 列出当前目录中的所有文件名。
2 echo 程序打印所有的参数到屏幕。星号作为通配符意味着匹配 0 个或者多个字符。所有当前目录
下的文件都匹配并显示在屏幕上。
3 列出以.c 结束的文件名。
4 列出不以.c 结束的文件名。
5 因为没有以 z 开头的文件,所以报告“No match”。

问号。问号匹配文件名中的一个字符。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 395

实例 10.45

1 > ls
a.c b.c abc ab3 filel file2 file3 file4 file5

2 > ls ???
abc ab3

3 > echo How are you?


No match.

4 > echo How are you\?


How are you?

说明
1 列出当前目录下的所有文件。
2 问号匹配文件名中的一个字符。列出所有由三个字符组成的文件名。
3 寻找所有以 y-o-u 后面是一个字符的文件名。当前目录下没有文件匹配,所以显示“No match”。
4 问号前的反斜线意味着关闭问号的特殊含义。现在只把问号作为字面的意思理解。

方括号。方括号表示文件名中的一个字符匹配一组字符中的一个或者一个范围中的一个
字符。

实例 10.46

1 > ls
a.c b.c abc ab3 filel file2 file3 file4 file5 file10
file11 file12
2 > ls file[123]
filel file2 file3

3 > ls file[^123]
file4 file5
4 > ls [A-Za-z][a-z][1-5]
ab3

5 > ls file1[0-2]
file10 file11 file12

说明
1 显示当前目录中的所有文件。
2 显示文件名以 file 后面跟 1、2 或者 3 结尾的文件。
3 显示文件名以 file 后面跟 1、2 或者 3 结尾的文件。
4 显示以大写字母开头,然后是一个小写字母和一个数字的文件名。
5 显示以 file1 开头后面紧跟 0,1 或者 2 的文件名。

花括号。花括号匹配文件名中的一个字符或一个字符串,而不论该文件是否存在。

实例 10.47

1 > ls

PDF created with pdfFactory Pro trial version www.pdffactory.com


396 第 10 章

a.c b.c abc ab3 ab4 ab5 filel file2 file3 file4 file5 foo
fpp fumble

2 > ls f{oo,aa,umble}
foo faa fumble

3 > ls a{.c,c,b[3-5]}
a.c ab3 ab4 ab5

4 > mkdir prog{1,2,3}

5 > echo tweedle{dee,dum}, {1,cl,m}ove{r}


tweedledee tweedledum, lover clover mover

说明
1 显示当前目录中的所有文件。
2 显示以 f 开头,后面是 oo、aa 或 umble 的文件名。花括号中的空格会导致错误信息“Missing}”

3 显示以 a 开头,后面是.c、c、b3、b4 或 b5 的文件名。
4 不必在 mkdir 命令后面输入参数 prog1、prog2 和 prog3,你可以用花括号来生成这些目录。
5 用括号中的字符串作为前缀或者后缀来扩展单词。括号中没有空格是十分重要的。

转义元字符和 nonomatch 变量。反斜线用来转义特定元字符的特定含义。被转义的元


字符只表示其自身含义。

实例 10.48

1 > got milk?


got: No match.

2 > got milk\?


got: Command not found.

3 > set nonomatch


> got milk?
got: Command not found.

说明
1 问号是一个文件替换元字符,表示单个字符。Shell 在当前工作目录下寻找包含字符 m-i-l-k 后面
是一个单个字符的文件名。如果找不到就报告“No match”。在 Shell 对 got 命令定位以前,元字
符就已经被赋值了。
2 反斜线保证元字符不被解释,通常叫作转义字符。现在不再报告“No match”了,但是没有找到
命令 got。
3 当内建变量 nonomatch 被设置后,无法找到匹配项的错误信息就被关闭了。Linux 不再报告“No
match”而只是报告 got 不是一个命令。

~符号。~匹配用户的主目录。当在它的前面追加了用户名后,它就表示用户主目录的完
整路径。当其前缀为一个路径时就表示主目录和其他路径。

实例 10.49

1 > echo ~
/home/jody/ellie

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 397

2 > cd ~/desktop/perlstuff
> pwd
/home/jody/ellie/desktop/perlstuff

3 > cd ~joe
> pwd
/home/bambi/joe

说明
1 ~表示用户主目录。
2 在~后面跟一个路径表示用户主目录。
3 ~跟在用户名后则表示用户的主目录,在这个例子中改变目录到用户主目录。

用 noglob 关闭元字符。如果设置了变量 noglob,元字符功能就被关闭了。关闭文件名


替换意味着所有的元字符都只表示其自身含义。它们不用于通配符。当用 grep、awk 或 sed
搜索包含元字符的模式时,这个功能就显得很有用了。

实例 10.50

1 > set noglob


2 > echo * ?? [] ~
* ?? [] ~

说明
1 设置 noglob 变量,关闭通配符的特殊含义。
2 没有任何解释,显示元字符本身。

10.6 重新定向和管道
通常标准输出(stdout)到屏幕,标准输入来自键盘,标准错误送到屏幕。Shell 允许你
使用特殊的元字符重新定向输入和输出到文件。重新定向符号(<、>、>>和>&)跟在文件
名后面。在符号左边的命令执行以前,符号右边的文件名就已经被打开了。
管道用竖线表示。允许把一个命令的输出作为另外一个命令的输入。管道左边的命令叫
作“写者”,因为它写入管道。管道右边的命令叫作”读者“,因为它从管道读取。关于重
新定向和管道的元字符参见表 10.15。

表 10.15 重新定向的元字符

元字符 含义
command < file 重新定向从文件取得输入
command > file 重新定向输入到文件
command >&file 重新定向输出和错误到文件
command >>file 重新定向输出追加的文件末尾
command >>&file 重新定向输出和错误追加到文件末尾

PDF created with pdfFactory Pro trial version www.pdffactory.com


398 第 10 章

续表
元字符 含义
command << WORD 从新定向第一个单词到 WORD 终结符,作为 command 的输入
<input> 把用户输入作为一个双引号引用的字符串文本
WORD WORD 表示输入的结束
command1|command2 连接第一个命令输出和第二个命令输入的管道
command1|&command2 连接第一个命令输出与错误和第二个命令输入的管道
command >!file 如果变量 noclobber 被设置,这个命令的结果打开或者覆盖 file
command >>!file 覆盖变量 noclobber。如果文件不存在就建立它,并把命令输出
追加到文件的末尾
command >>&!file 覆盖变量 noclobber。如果文件不存在就建立它,并把命令输出
和错误追加到文件的末尾

10.6.1 重新定向输入
可以不从键盘终端输入,而是一个文件重新定向。Shell 将打开<符号右边的文件,<符号左
边的程序从文件中读取输入。如果文件不存在就由 C Shell 报告错误“No such a file or directory”。

格式
command < file

实例 10.51
mail bob < memo

说明
文件 memo 被 Shell 打开,重新定向作为程序 mail 的输入。简单说,用户 bob 将接受到一封由
mail 程序发送的、叫作 memo 的邮件。

10.6.2 here 文档
here 文档提供了另外一种重新定向输入到命令的方式,也就是把双引号引用的文本块作
为输入。它使用 Shell 脚本建立菜单及处理从其他程序得到的输入。通常,程序从键盘那里
得到以^D 结束的输入。here 文档提供了另外一种输入方式,但是不以^D 结束。<<后面紧跟
用户自定义的单词,通常叫作终止符。输入被重新定向<<符号左边的命令,直到用户自定义
单词出现。最后的终结符应该是占用单独一行的终结符,周围没有任何空格。在 here 文档
内可以进行变量和命令替换。通常 here 文档用在 Shell 脚本中,以建立菜单和提供输入给例
如 mail、bc、ftp 等等见实例 11.34。

格式

command << MARK

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 399

... input ...


MARK

实例 10.52

(Without the "Here" Document)

(The Command Line)


1 > cat
2 Hello There.
How are you?
I'm tired of this.
3 ^d

(The Output)
4 Hello There.
How are you?
I'm tired of this.

说明
1 没有参数的 cat 程序,等待键盘输入。
2 用户从键盘输入。
3 用户输入^D 结束输入。
4 cat 程序把输出送到屏幕。

实例 10.53
(With the "Here" Document)

(The Command Line)


1 > cat << DONE
2 ? Hello There.
? How are you?
? I'm tired of this.
3 ? DONE
4 Hello There. <----The output from the here document
How are you?
I'm tired of this.

说明
1 cat 程序从第一个 DONE 开始读取输入,直到作为终结符的 DONE 出现。
2 问号作为辅助提示符,一直显示到用户自定义终结符 DONE 出现。 将这些行作为输入,
出现 DONE
后就不再接受输入。
3 终结符标志着输入的结束,在终结符的前后不能有任何空格。
4 在第一个 DONE 与最后一个 DONE 之间的文本作为 cat 程序的输出显示在屏幕上。最后一个
DONE 必须在其所在行的最左边,并且它的右边不能有空格和字符。

实例 10.54
(The Command Line)
1 > set name = steve

PDF created with pdfFactory Pro trial version www.pdffactory.com


400 第 10 章

2 > mail $name << EOF


3 ? Hello there, $name
4 ? The hour is now 'date +%H'
5 ? EOF
6 >

说明
1 Shell 变量 name 被赋值为 steve(通常这个例子都会包含在 shell 脚本中)

2 变量 name 在 here 文档中被展开。
3 以问号作为辅助提示符,一直显示到用户自定义终结符 EOF 出现。mail 程序将把 EOF 以前的文
本都作为输入。
4 here 文档内发生了命令替换。反引号内的命令被执行,命令执行的结果作为替换命令所在位置的
字符串。
5 到达终结符 EOF,mail 程序的输入结束。

10.6.3 重新定向输出
默认情况下,命令的的输出显示在终端屏幕上。把输出从屏幕定向到文件,需要使用>
符号。命令在符号的左边,文件名在其右边。Shell 负责打开符号右边的文件。如果该文件
不存在,则 Shell 就将创建它。如果文件存在则打开并覆盖它。使用重新定向时非常容易误
删除文件(tcsh 变量 noclobber 可以防止这样的问题,参见表 10.16)。

格式
command > file

实例 10.55
cat filel file2 > file3

说明
把 file1 和 file2 合并在一起并输出给 file3。记住,Shell 在执行 cat 程序以前就打开了文件 file3。
如果 file3 存在并包含有数据,那么该数据就将丢失,如果 file3 不存在则创建 file3。

追加输出到已经存在的文件。要追加输出到已经存在的文件,则使用>>符号。如果符号
右边的文件不存在就创建它,如果存在则打开它并把输出追加到这个文件的末尾。

格式
command >> file

实例 10.56
date >> outfile

说明
命令 data 的输出被重新定向并追加到文件 outfile 的末尾。

重新定向输出和错误。符号>&用来重新定向标准输出和错误。通常,如果命令执行成

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 401

功就发送输出到 stdout,若失败则发送失败信息到 stderr。一些递归程序(像 find 和 du)在


搜索目录树的时候,把输出和错误都显示在屏幕上。若使用>&符号,则可以把输出和错误
一起定向到文件。C Shell 不提供符号专门重新定向错误,但是可以通过让程序在子 Shell 中
运行的办法来只取得错误。参考图 10.3。

图 10.3 重新定向 stdout 和 stderr,见实例 10.57

实例 10.57

1 > date
Tue Aug 3 10:31:56 PDT 2000
2 > date >& outfile
3 > cat outfile
Tue Aug 3 10:31:56 PDT 2000

说明
1 data 命令的输出被发送到标准输出,即屏幕上。
2 输出和错误都被发送到 outfile 文件中。
3 因为没有错误所以只有输出被发送到文件 outfile 中,并显示文件的内容。

实例 10.58
1 > cp filel file2
2 > cp filel
cp: missing destination file
Try 'cp -- hilp' for more information
3 > cp filel >& errorfile
4 > cat errorfile
cp: missing destination file
Try 'cp -- help' for more information

说明
1 要复制文件,cp 命令需要源文件和目标文件。cp 命令把 file1 文件复制到 file2 文件中。因为语法

PDF created with pdfFactory Pro trial version www.pdffactory.com


402 第 10 章

正确,因此什么也不显示。
2 这次目标文件不存在因而导致 cp 命令出错,发送错误信息到 stderr 也就是终端。
3 >&符号用来重新定向输出和错误到文件 errorfile,因为命令的输出只有错误信息,所以文件名叫
作 errorfile。
4 显示文件内容,显示 cp 命令产生的错误信息。

实例 10.59

(The Command Line)


1 > find . –name '*.c' >& outputfile
2 > (find . –name '*.c' > goodstuff) >& badstuff

说明
1 find 命令从当前目录开始搜索所有以.c 结尾的文件,并把结果打印到文件 outputfile 中。如果产
生错误,则该错误也将被输出到 outputfile 中。
2 find 命令被放在括号中。Shell 将建立一个子 Shell 来处理这个命令。在建立子 Shell 以前,括号
外面的单词首先被处理。badstuff 文件将被打开,准备接收输出和错误信息。当子 Shell 启动后,
它从父进程继承了输入、输出和错误。所以子 Shell 有来自键盘的标准输入,而标准错误被定向
到 badstuff 文件。现在,子 Shell 将处理 >符号。标准输出将分配给文件 goodstuff,而将错误分
配给 badstuff。见图 10.4。

图 10.4 单个 stdout 和 stderr

变量 noclobber。C Shell 特殊的内建变量 noclobber,一旦设置就可以防止文件被误操作


覆盖。见表 10.16。

表 10.16 变量 noclobber

不设置 noclobber 文件存在 文件不存在


command > file 文件被覆盖 创建文件
command >> file 追加在文件末尾 创建文件
设置 noclobber

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 403

续表
不设置 noclobber 文件存在 文件不存在
command > file 错误信息 创建文件
command >>file 追加在文件末尾 错误信息
覆盖 noclobber
command >!file 如果变量被设置,命令的输出将覆盖文件
command >>!file 覆盖变量:如果文件不存在就创建文件并追加到文件末尾。见实例
10.60

实例 10.60

1 > cat filex


abc
123

2 > date > filex


3 > cat filex
Tue Mar 18 11:51:04 PST 2000

4 > set noclobber


5 > date > filex
filex: File exists.

6 > ls >! filex Override noclobber for this command only


> cat filex
abc
abl
dir
filex
plan.c

7 > ls > filex


filex: File exists.

8 > date >> XXX


XXX: No such file or directory.

9 > date >>! XXX Override noclobber for this command only

10 > unset noclobber Turn off noclobber permanently

说明
1 在屏幕上显示 filex 的内容。
2 data 命令的输出被定向到文件 filex,文件的原始数据被覆盖。
3 显示 filex 文件的内容。
4 设置 noclobber 变量。
5 因为 filex 已经存在并且变量 noclobber 被设置,所以 Shell 报告文件存在且不能覆盖。
6 ls 的输出重新定向给文件 filex,因为操作符>!覆盖了变量 noclobber 的效果。
7 操作符>!的效果是临时的。它并不关闭变量 noclobber 的作用,只是在其被执行时覆盖文件。

PDF created with pdfFactory Pro trial version www.pdffactory.com


404 第 10 章

8 由于在 noclobber 存在时,尝试重新定向 data 命令的输出到一个不存在的文件,所以导致错误信


息。
9 >>符号后面的感叹号覆盖了变量 noclobber。
10 释放变量 noclobber。

10.7 变 量
tcsh 的变量只能包含字符串或者一组字符串。有些变量是内建的,可以通过设置把它们
打开或者关闭。例如变量 noclobber 和 filec。其他变量可以分配字符串值,如 path 变量。你
可以建立自己的变量,并把字符串或者命令的输出赋给它们。变量名是大小写敏感的,最多
可以由 20 个字符(数字、字母和下划线)组成。
有两种类型的变量:本地变量和环境变量。变量的作用范围就是其可见范围。本地变量
只在定义它的 Shell 中是可见的,而环境变量通常被称做“全局”的,其范围是该 Shell 以及
其所有的子 Shell。如果用 set –r 建立本地变量,将表示这个变量是只读的,也就是既不能被
改变也不能被释放。
在这里,美元符号($)是一个特殊的元字符,当把它放在变量名前面就是告诉 Shell
提取变量的值。当 echo 命令后面是一个变量作为参数时,在 Shell 处理完命令行和命令替换
以后,该命令的效果就显示变量的值。
特殊符号$?追加在变量名前面可以告诉你这个变量是否已经被设置。如果返回 1 就表示
真,也就是变量已经被设置。如果返回 0 就表示假,即变量还没有被设置。

实例 10.61

1 > set autologout


2 > set history = 50
3 > set name = George
4 > set machine = 'uname –n'
5 > echo $?machine
1
6 echo $?blah
0

说明
1 设置内建变量 autologout 使得经过指定的交互时间后自动退出。
2 通过设置内建变量 histoey 为 50 来控制显示事件的数量。
3 设置用户自定义变量 name 为 Geoge。
4 设置用户自定义变量 machine 为 UNIX 命令的输出。
5 符号$?测试变量是否已经被设置。因为返回值是 1,所以可以认定该变量已经被设置。
6 由于$?测试返回值是 0,所以可以认定变量没有被设置。

10.7.1 打印变量值
echo 命令。内建 echo 命令打印参数到标准输出。echo 命令允许大量使用例如制表符、

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 405

换行符之类的转义序列做为它的参数。参考表 10.17 TC Shell 的风格与 BSO 和 SRV4 很接近,


允许你通过内建变量 echo_style 控制 echo 命令的行为。例如 set echo-style=bsd。
参考表 10.18。
表 10.17 echo 选项和换码序列

选项 含义
-n 禁止输出行最后的换行符
换码序列
\a 铃声
\b 退格符
\c 打印行但是不打印换行符
\f 表格馈送
\n 换行符
\r 回车符
\t 制表符
\v 垂直制表符
\\ 反斜线
\nnn 显示 ASCII 码为 nnn 的 ASCII 字符(八进制)

表 10.18 echo_style 变量

bsd 如果第一个参数是-n,换行符就被禁止
sysv 在 echo 字符串中扩展换码序列
both -n 和换码序列都有效
none sysv 和 bsd 都无效

实例 10.62

1 > echo The username is $LOGNAME.


The username is ellie.

2 > echo "\t\tHello there\c"


Hello there>

3 > echo –n "Hello there"


Hello there$

4 > set echo_style=none

5 >echo "\t\tHello there\c"


-n \t\tHello there\c

说明
1 echo 命令打印参数到屏幕。变量替换以后执行 echo 命令。
2 echo 命令默认支持换码序列,>符号是 Shell 提示符。
3 带有-n 选项的 echo 命令打印不带换行符的字符串。

PDF created with pdfFactory Pro trial version www.pdffactory.com


406 第 10 章

4 将变量 echo_stylr 赋值为 none,从而使得 BSD –n 开关和 SVR4 换码序列都无效。


5 用新的显示风格显示字符串。

printf 命令。Gnu 版本的 printf 命令可以用来格式化打印输出。在打印格式化字符串时,


用法类似于 C 语言中的 printf 函数的用法。格式由包含描述打印文本形态的格式化结构和
字符串组成。格式化结构用%后面再跟一个说明符来表示,%f 表示浮点数,%d 表示十进
制数。
若要查看 printf 函数的说明符及其用法,就在命令行输入命令:printdf--help。要想了解
你正在使用的 printf 的版本,输入命令 print--version。如果是在 bash2.x 版本中使用,内建命
令 printf 使用的格式跟/usr/bin 目录下的可执行版本一样。

格式
printf format [argument...]

实例 10.63
printf "%10.2f%5d\n" 10.5 25

表 10.19 printf 命令的格式化说明符

符号 含义
\” 双引号
\0NNN 八进制数,N 在 0 和 3 之间
\\ 反斜线
\a “哔”声音
\b 退格符
\c 终止输出
\f 表格馈送
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\xNNN 十六进制数,N 在 0 和 3 之间
%% 百分号
%b 字符串参数中带有\逃逸符号

实例 10.64

1 > printf --version


printf (GNU sh-utils) 1.16

2 > printf "The number is %.2f\n" 100


The number is 100.00

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 407

3 > printf "%-20s%-15s%10.2f\n" "Jody" "Savage" 28


Jody Savage 28.00

4 > printf "|%-20s|%-15s|%10.2f|\n" "Jody" "Savage" 28


∕Jody ∕Savage ∕ 28.00∕

5 >printf "%s's average was %.1f%%.\n" "Jody" $(( (80+70+90)/3 ))


Jody's average was 80.0%.

说明
1 打印 Gnu 的 printf 函数的版本。
2 100 被作为小数点后面保留两位的浮点数打印。字符串中的格式说明符是%.2。注意,跟 C 语言
不一样的是,这里不需要用逗号分隔参数。
3,4 这次有三种格式说明符:第一种是%-20s(向左对齐,30 个字符的字符串) ,第二种是%-15s(向
左对齐,15 个字符的字符串),最后一种是%10.2f(向右对齐,10 个字符宽的浮字数,小数点占
1 位,小数点后保留 2 位) 。每个参数都按照相应的%格式化(
“Jody”对应第一个%,“Savage”
对应第二个%,数字 28 对应第三个%) 。竖线用来说明域的宽度。
5 printf 命令格式化字符串 Jody 和数学计算的结果。若想打印一个%则需要两个在一起的%(%%) 。

花括号和变量。花括号用于将变量和其后面的字符分隔开来,也可以用来把字符串追加
到变量的末尾。

实例 10.65

1 >set var = net


> echo $var
net
2 >echo $varwork
varwork: Undefined variable.
3 >echo $(var)work
network

说明
1 变量旁边的花括号把变量名字从周围的字符中间隔离开来。
2 由于变量 varwork 没有定义,所以打印错误信息。
3 花括号把变量从追加在后面字符中分隔开来。展开变量$var,并把 work 追加在它的后面。

10.7.2 本地变量(可见性及命名)
本地变量只能在创建它的 Shell 中可见。如果本地变量在.cshrc 文件中被设置,那么每次
启动新 Shell 时变量都被重新设置。为了方便,本地变量名字通常采用小写字母命名。
设置本地变量。如果字符串中包含有不止一个单词,就需要引号引用。否则只有第一个
单词被赋值给变量。等号旁边有空格没有关系,但是需要注意的是如果等号的一边有空格那
么另外一边也一定要有空格。

实例 10.66

1 > set round = world

PDF created with pdfFactory Pro trial version www.pdffactory.com


408 第 10 章

2 > set name = "Santa Claus"

3 > echo $round


world

4 > echo $name


Santa Claus

5 > tcsh start a subshell


6 > echo $name
name: Undefined variable.

说明
1 本地变量 round 被赋值为 world。
2 本地变量 name 被赋值为 Santa Claus。双引号保证了 Santa Claus 是一个字符串而不是两个。
3 美元符号($)表示进行变量替换,也就是从变量中提取值。
4 变量替换。
5 启动一个新 C Shell(称为子 Shell)进程。
6 在子 Shell 中,变量 name 并没有定义,它只是在父进程中定义为本地变量。

只读变量。只读变量是本地变量的一种,一旦设置就不能改变或者释放,否则就会显示
错误信息。环境变量不能是只读变量。
实例 10.67

1 > set –r name = Tommy

2 > unset name


unset: $name is read-only.

3 > set name = Danny


set: $name is read-only

set 命令。set 命令用于打印当前 Shell 中所有的本地变量。


实例 10.68

(The Command Line)


> set
addsuffix
argv ()
cwd /home/jody/meta
dirstack /home/ellie/meta
echo_style both
edit
gid 501
group ellie
history 500
home /home/elli
i /etc/profile.d/mc.csh
owd /home/ellie
noclobber
Path (/usr/sbin/sbin/usr/local/bin /bin /usr/bin /usr/xllR6/bin)

Prompt [%n@%m %c]#

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 409

Prompt2 %R?
Prompt3 CORRECT>%R (y/n/e/a)?
savedirs
shell bin/tcsh
shlvl 2
status 0
tcsh 6.07.09
term xterm
user ellie
version tcsh 6.07.09 (Astron) 1998-07-07 (i386-intel-linux)
options 8b,nls,dl,al,rh,color

说明
打印当前 Shell 中所有的本地变量,
它们当中的很多,例如 history、dirstack、 noclobber 都是在.tcshrc
文件中定义的。其他的例如 atgv、cwd、shell、term、version 以及 status 则是预先设置的内建变量。

内建本地变量。在 Shell 预先定义的变量中有些只具备开和关两种状态。例如,当你设


置 noclobber,它就打开并有效。而当释放它后,它就关闭了。一些变量在设置时需要定义。
如果不要求在所有的交互式 TC Shell 和 tcsh 脚本状态下都有效,则通常在 tcshrc 中设置内建
变量。这里所说的内建变量有些已经在前面讨论过了,参见表 10.24。

10.7.3 环境变量
环境变量通常叫作全局变量,它们在创建它们的 Shell 中被定义,同时又被子进程所继
承。虽然父进程中的环境变量可以被子进程继承,但是子进程中定义的环境变量却不能传递
回父进程。继承只有从父到子的惟一方式。为了方便,环境变量名一般用大写字母拼写。

实例 10.69
(The command Line)
1 > setenv TERM wyse
2 > setenv PERSON "Joe Jr."
3 > echo $TERM
wyse
4 > echo $PERSON
Joe Jr.
5 > echo $$ $$ evaluates to the PID of the current shell
206
6 > tcsh start a subshell
7 > echo $$
211
8 > echo $PERSON
Joe Jr.
9 > setenv PERSON "Nelly Nerd"
10 > echo $PERSON
Nelly Nerd
11 > exit exit the subshell

12 > echo $$
206
13 > echo $PERSON back in parent shell
Joe Jr.

PDF created with pdfFactory Pro trial version www.pdffactory.com


410 第 10 章

说明
1 环境变量 TERM 被设置为终端 wyse。
2 自定义变量 PERSON 设置为 Joe Jr。双引号用来保护空格。
3 美元符号($)允许 Shell 做变量替换。
4 打印环境变量 PERSON 的值。
5 $$表示当前 Shell 进程的 PID 值,该 PID 是 206。
6 tcsh 命令启动一个新 Shell,叫作子 Shell。
7 打印当前 Shell 的 PID 值。因为这是一个新 Shell,所以有新的 PID 值,211。
8 环境变量 PERSON 被新的 Shell 继承。
9 PERSON 变量被重新设置为“Nelly Nerd” 。该变量将被所有这个 Shell 衍生出来的 Shell 所继承。
10 打印新的 PERSON 变量的值。
11 退出这个 Shell。
12 原始 Shell 还在运行。可以通过打印 PID 号来证明这一点,该 PID 正好是 206,说明这是子 Shell
启动前的那个 Shell。
13 PERSON 变量包含原始值。

显示环境变量的值。BSD 中的 printnv 和 SVR4 中的 env 打印当前 Shell 和其子 Shell


的所有环境变量值。在 UCB 和 SVR4 的 C Shell 版本中,setenv 命令打印变量和它们的
值。

实例 10.70

> env or printenv of setenv


USERNAME=root
COLORTERM=rxvt-xpm
HISTSIZE=1000
HOSTNAME=homebound
LOGNAME=ellie
HISTFILESIZE=1000
MAIL=/var/spool/mail/ellie
MACHTYPE=i386
COLORFGBG=0;default;15
TERM=xterm
HOSTTYPE=i386-linux
PATH=/usr/sbin:/sbin:/usr/local/bin:/bin:/usr/bin:/usr/xllR6/bin:/hom
e/ellie/bin;/root/bash-
2.03/:/usr/x11R6/bin:/home/ellie/bin;/root/bash-2.03/:/usr/XllR6/bin
HOME=/root
SHELL=bin/bash
PS1=[\u@\h \W]\$
USER=ellie
VENDOR=inte1
GROUP=ellie
HOSTDISPLAY=homebound:0.0
DISPLAT=0.0
HOST=homebound
OSTYPE=linux
WINDOWID=37748738
PWD=/home/ellie

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 411

SHLVL=6
_=/usr/bin/env

说明
用 env 或者 printenv 命令可以显示当前会话和所有当前 Shell 派生的进程的环境变量。许多应用
程序需要环境变量。例如 mail 程序就需要 MAIL 变量来定位用户的 mail 池,xterm 则需要 DISPLAY
变量来决定在终端使用哪个位图。当这些程序运行时,相关的变量就会传递给它们。

10.8 数 组
10.8.1 什么是数组
在 TC Shell 中,数组就是一组用空格或者制表符分隔开的、放在括号内的单词。数组元
素依靠从 1 开始的脚标区分。如果相应的脚标没有元素存在就显示错误信息“Subscript out of
range”。命令替换也会建立相应的数组。如果$#出现在数组名前面,就显示数组元素的个数。

实例 10.71

1 > set fruit = ( apples pears peaches plums )


2 > echo $fruit
apples pears peaches plums

3 > echo $fruit[1] Subscripts start at 1


apples
4 > echo $fruit[2-4] prints the 2nd, 3rd, and 4ch elements
pears peaches plums
5 > echo $fruit[6]
Subscript out of range.

6 > echo $fruit[*] Prints all elements of the array


apples pears peaches plums
7 > echo $#fruit Prints the number of elements
4
8 > echo $%fruit Prints the number of characters in the list
23
9 > echo $fruit[$#fruit] prints the last element
plums
10 > set fruit[2] = bananas Reassigns the second element
> echo $fruit
apples bananas peaches p1ums
11 > set path = ( ~ /usr/bin /usr /usr/local/bin .)
> echo $path
/home/jody/ellie /usr/bin /usr /usr/local/bin .
12 > echo $path[1]
/home/jody/ellie

PDF created with pdfFactory Pro trial version www.pdffactory.com


412 第 10 章

说明
1 单词列表放在括号内,且每个单词间用空格分隔。该数组叫作 fruit。
2 打印数组 fruit 中的单词。
3 打印 fruit 数组中的第一个元素。脚标是 1。
4 打印第二、第三和第四个元素,斜线使你能够指定范围。
5 由于数组没有第六个元素,所以脚标超出范围。
6 打印数组 fruit 中的所有元素。
7 $#加数组名字用于获取数组元素的个数。数组 fruit 包含四个元素。
8 $%加变量或者数组的名字显示单词中字符的个数。
9 因为脚标$#表示数组中所有元素的个数,因此如果使用它作为索引,例如[$#fruit],就可以打印
最后一个元素。
10 第二个元素被赋值为新值,打印这个值为 banana。
11 C Shell 中的 path 变量用来保存目录数组,以便搜索命令。一旦创建了这个数组,其中的单个元
素可以被访问和修改。
12 打印 path 数组中的第一个元素。

shift 命令和数组。如果内建命令 shift 把数组名字作为其参数,它就删除第一个元素,


数组元素的个数减 1。若没有参数,就删除内建数组 argv 的第一个元素。

表 10.20 变量修改操作符

特殊操作符 举例 含义
$? $? Var 如果变量被设置返回 1,否则返回 0
$# $#var 返回 var 中的单词数
$% $%var 返回 var 中的字符数

实例 10.72

1 > set names = ( Mark Tom Liz Dan Jody )

2 > echo $names


Mark Tom Liz Dan Jody

3 > echo $names[1]


Mark

4 > shift names

5 > echo $names


Tom Liz Dan Jody

6 > echo $names[1]


Tom

7 > set days = ( Monday Tuesday )

8 > shift days

9 > echo $days


Tuesdat

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 413

10 > shift days

11 > echo $days

12 > shift days


shift: no more words.

说明
1 数组名为 names。它是一对括号中的单词列表,且每个单词依靠空格分隔。
2 打印数组。
3 打印数组的第一个元素。
4 向左移动数组元素,第一个元素 Mark 被删除。
5 shift 命令以后数组元素个数减少 1。
6 shift 命令以后数组第一个元素是 Tom。
7 名为 days 的数组包含两个元素,Monday 和 Tuesday。
8 数组 days 向左移动一个元素。
9 打印数组,Tuesday 是剩下的惟一元素。
10 数组 days 再次向左移动,数组为空。
11 数组 days 为空。
12 这次,再次尝试 shift 命令,因为数组已经为空,因此 Shell 显示错误信息。

从字符串建立数组。你可以从引用的字符串中建立一个数组。将字符串放入一对括号中
即可以创建数组。

实例 10.73

1 > set name = "Thomas Ben Savage"


> echo $name[1]
Thomas Ben Savage

2 > echo $name[2]


Subscript out of range.

3 > set name = ( $name )

4 > echo $name[1] $name[2] $name[3]


Thomas Ben Savage

说明
1 变量 name 被赋值为字符串“Thomas Ben Savage”。
2 若把它看做数组,则该数组只有一个元素,就是整个字符串。
3 把变量放在括号内,创建一个单词数组,叫作 name。
4 显示数组的三个元素。

10.9 特殊变量和操作符
TC Shell 中有一些仅由一个字母构成的变量。$表示可以做变量替换。参见表 10.21。

PDF created with pdfFactory Pro trial version www.pdffactory.com


414 第 10 章

表 10.21 变量及其含义

变量 举例 含义
$?var echo $?name 如果变量已经被设置就返回 1,否则返回 0
$#var echo $#fruit 打印数组中元素的个数
$%var echo $%name 打印变量或者数组的字符个数
$$ echo $$ 打印当前 Shell 的 PID
$< set name=$< 从用户输入直到换行符为止读取一行输入
$? echo $? 与$STATUS 相同,包含最后一个命令的退出状态值
$! kill $! 包含最后一个放入后台的进程 ID 号

实例 10.74

1 > set num


> echo $?num
1

2 > echo $path


/home/jody/ellie /usr /bin /usr/local/bin
> echo $#path
3

3 > echo $$
245

> tcsh Start a subshell


> echo $$
248

4 > set name = $<


Christy Campbell
> echo $name
Christy

5 > set name = "$<"


Christy Campbell
> echo $name
Christy Campbell

说明
1 设置变量 num 为空。变量前的 $?使得如果变量已经被设置,无论是否为空,就返回 1,否则返
回 0。
2 打印 path 变量。它是一个有三个元素的数组。变量前的$#变量提取并打印数组元素。
3 在这个例子中,$$表示当前进程的 PID。
4 $<从用户以空格或者换行符为结束标志的命令行输入中提取第一个单词,然后保存在变量 name
中。最后显示变量 name。
5 当$<变量被双引号引用时,接收整个用户命令行输入并保存到变量 name,但是不包括换行符。
最后显示变量 name 的值。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 415

10.9.1 路径名操作符
如果把路径名赋值给一个变量,就可能通过在变量后面追加指定的 TC Shell 扩展来控制
路径变量。路径名分为四个部分:head、tali、root 和 extension。表 10.22 通过举例说明变量
操作符的和它们的作用。
表 10.22 路径操作符

set pn=/home/ellie/prog/check.c
操作符 含义 举例 作用
:r root echo $pn:r /home/ellie/prog/check
:h head echo $pn:h /home/ellie/prog
:t tail echo $pn:t Check.o
:e extension echo $pn:e C
:g global echo $pn:g Cankao 实例 10.75

实例 10.75

1 > set pathvar = /home/danny/program.c

2 > echo $pathvar:r


/home/danny/program

3 > echo $pathvar:h


/home/danny

4 > echo $pathvar:t


program.c

5 > echo $pathvar:e


C

6 > set pathvar = ( /home/* )


echo $pathvar
/home/jody /home/local /home/lost+found /home/per1/home/tmp

7 > echo $pathvar:gt


jody local lost+found perl tmp

说明
1 变量 pathvar 设置为/home/danny/program.c。
2 当:r 追加在变量后面,在显示的时候,扩展就被删除。
3 当:h 追加在变量后面,就显示路径的 head;这就是说,路径的最后一个元素被删除。
4 当:t 追加在变量后面,显示路径的 tail。
5 当:e 追加在变量后面扩展就显示出来。

PDF created with pdfFactory Pro trial version www.pdffactory.com


416 第 10 章

6 当变量被设置为/home/*,星号表示当前目录下从/home/开始所有的路径。
7 当:gt 追加在变量后面,每一个路径元素的 tail 都显示出来。

10.9.2 大写和小写操作符
特定的历史操作符可以用来修改变量中字符的大小写。

表 10.23 大小写操作符(tcsh)

:u 把单词中第一个小写字母改为大写字母
:l 把单词中第一个大写字母改为小写字母
:g 对每个单词修改符只发挥作用一次
:a 对每个单词修改符发生作用的次数不受限制

实例 10.76

1 > set name = nicky


> echo $name:u
Nicky

2 > set name = ( nicky jake )


> echo $name:gu
Nicky Jake

3 > echo $name:agu


NICKY JAKE

4 > set name = ( TOMMY DANNY )


> echo $name:ag1
tommy danny

5 > set name = "$name:agu"


> echo $name
TOMMY DANNY

说明
1 当 :u 追加在变量后面,变量值的第一个字母变成大写。
2 当 :gu 追加在变量后面,变量值的每一个单词的第一个字母变成大写。
3 当 :agu 追加在变量后面,变量值的每一个字母变成大写。
4 当 :agl 追加在变量后面,变量值的每一个字母变成小写。
5 变量被重置,所有字母都变成小写。

10.10 命 令 替 换

10.10.1 反引号
我们可以通过将命令放在反引号中把 Linux 命令的输出赋值给变量,这叫作变量替换。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 417

如果命令的输出被赋值给一个变量,它就以单词列表或者数组的形式被保存。

实例 10.77

1 > echo The name of my machine is 'uname –n'.


The name of my machine is stardust.

2 > echo The present working directory is 'pwd'.


The present working directory is /home/stardust/john.
3 > set d = 'date'
> echo $d
Tue Mar 28 14:24:21 PDT 2000

说明
1 Linux 命令 uname-n 被放在一对反引号中。Shell 计算反引号的个数,并执行反引号中的命令,
uname
–n,替换命令输出,stardust,保存为字符串。当 echo 命令打印参数到屏幕的时候,机器的名字
将是其中的一个参数。
2 Shell 执行 pwd 命令,并用输出结果替换字符串中的相应位置。
3 本地变量 d 被赋值为 data 命令的输出。输出保存为一个单词列表(一个数组) 。

单词列表和命令替换。当一个命令被反引号引用并赋值给一个变量时,其结果一定是一
个数组(单词列数),数组中的每一个元素都可以通过脚标来访问,脚标从 1 开始。如果脚
标大于数组所包含的单词个数,C Shell 就会显示错误信息“Subscript out of range”。如果命
令输出不止一行,换行符将被替换为一个空格。
当数组被创建以后,内建命令 shift 就可以用来从左边第一个开始删除其中的元素。单
词一旦被删除就无法恢复。见实例 10.79。

实例 10.78

1 > set d= 'date'


> echo $d
Tue Mar 28 14:04:49 PST 2000

2 > echo $d[1-3]


Tue Mar 28

3 > echo $d[6]


2000

4 > echo $d[7]


Subscript out of range.

5 > echo "The calendar for the month of March is ‘cal 3 2000’"
The calendar for month of March is March 2000 S M Tu W
Th F S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
22 23 24 25 26 27 28 29 30 31

说明
1 命令 d 被赋值为 Linux date,且命令的输出,且输出保存为数组。显示变量的值。
2 显示数组的前三个元素。

PDF created with pdfFactory Pro trial version www.pdffactory.com


418 第 10 章

3 显示数组的第六个元素。
4 由于数组没有第七个元素,所以显示错误信息。
5 因为输出多于一行,所以换行符被替换为空格。这也许不是你所期望的输出。

实例 10.79

1 > set machine = ‘rusers | awk ‘/tom/{print $1}'’

2 > echo $machine


dumbo bambi dolphin

3 > echo $#machine


3

4 > echo $machine[$#machine]


dolphin

5 > echo $machine


dumbo bambi dolphin

6 > shift $machine


> echo $machine
bambi dolphin

7 > echo $machine[1]


bambi
8 > echo $#machine
2

说明
1 rusers 命令被管道传给 awk。如果找到正则表达式 tom,awk 就打印第一个域。在这个例子中,
就是用户 tom 登录的机器名。
2 用户 tom 登录了三台机器,显示它们的名字。
3 使用$#得到数组元素的个数,3。
4 用元素的个数作为脚标显示最后一个元素。
5 显示数组。
6 shift 命令将数组向左移动。第一个元素被删除,脚标减少 1。
7 shift 后显示第一个元素。
8 shift 后显示数组长度,发现减少了 1。

10.11 引 用
TC Shell 有其自身具有特定含义的整套元字符。事实上,键盘上的所有按键几乎都是元
字符,它们都含有其自身以外的意义。下面是其中的一部分:
*?[]$~!^&{}()><|:;%

反斜线和引号用来转义 Shell 对于元字符的解释。反斜线每次只能转义一个字符,而引


号可以保护整个字符串。在使用引号时主要有如下的一些规则:
1.引号必须在同一行内成对使用。反斜线可以用来转义换行符,这样引号也可以在两

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 419

行内使用。
2.单引号保护双引号,双引号保护单引号。
3.除了历史字符(!)以外,单引号保护所有元字符。
4.除了历史字符(!)、变量替换符号($)和反引号以外,双引号保护所有元字符。

10.11.1 反斜线
反斜线用于引用单个元字符且只有反斜线才能转义历史字符序列(如!!、 !和!5)。反斜
线通常用来转义换行符。特殊的 tcsh 变量 back_quote 可以引用引号和反斜线,但是不能在 C
Shell 脚本中使用。见实例 10.80。

实例 10.80

1 > echo Who are you?


echo: No match.

2 > echo Who are you\?


Who are you?

3 > echo This is a very,very long line and this is where\


? I break the line.
This is a very, very long line and this is where I bread the line.

4 > echo "\abc"


\abc
> echo '\abc'
\abc
> echo \\abc
\abc

5 > echo 'I can\'t help it!'


Unmatched ‘.

6 > set backslash_quote


> echo 'I can't help it!'
I can't help it!

说明
1 问号用于文件名扩展,它只匹配单个字符。Shell 将在当前目录下寻找拼写为 y-o-u,后面跟一个
单个字符的文件名。因为没有这样的文件,所以 Shell 将报告“No match”。
2 Shell 不会尝试解释问号,因为有反斜线在问号前面。
3 反斜线转义了换行符,使得字符串在下一行继续。
4 反斜线被单引号或者双引号引用,打印反斜线。在没有包含引号的情况下,可以用反斜线转义其
本身。
5 因为包含在引号中,反斜线被忽略,仅仅作为一个被引用的字符。
6 如果 tcsh 的变量 backslash_quote 被设置,则反斜线总是引用反斜线、单引号和双引号。

10.11.2 单引号
单引号必须在同一行成对的出现,且除了!,单引号可以转义所有的元字符。

PDF created with pdfFactory Pro trial version www.pdffactory.com


420 第 10 章

实例 10.81

1 > echo 'I need $5.00'


I need $5.00

2 > cp file1 file2


> echo 'I need $500.00 now!!'
echo 'I need $500.00 nowcp file1 file2'

3 > echo 'I need $500.00 now\!\!'


I need $500.00 now!!

4 > echo 'This is going to be a long line so


Unmatched'

5 > echo 'This is going to be a long line so \


? I used the backslash to suppress the newline'\
This is going to be a long line so
I used the backslash to suppress the newline

说明
1 字符串被单引号引用。除了! ,其他符号都不能当作元字符解释。
2 执行 cp 命令以后执行 echo 命令。因为!!可以当作元字符解释,通过使用反斜线可以再次执行
最后一个命令,而命令 cp 则变成字符串的一部分。
3 通过反斜线引用!! ,它们不必做历史替换。
4 引号必须出现在同一行,否则将报告“Unmatched”。
5 如果需要续行,反斜线可以用来转义换行符,引号可以在下一行的末尾才被匹配。即使 Shell 忽
略了换行符,echo 命令也不会忽略换行符。

10.11.3 双引号
双引号必须成对出现,在双引号内可以进行变量和命令替换,但是不能进行历史符号替
换。双引号中的美元符号($)无法转义。

实例 10.82

1 > set name = Bob


> echo "Hi $name"
Hi Bob

2 > echo "I don't have time."


I don't have time

3 > echo "WOW!!" Watch the history metacharacter!


echo "Wowecho "Wow!!""

4 > echo "WOW\!\!"


Wow!!

5 > echo "I need \$5.00"


I need \.00

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 421

说明
1 本地变量 name 被赋值为 Bob,双引号中的美元符号($)可以用来做变量替换。
2 单引号在双引号内被保护。
3 无论是双引号还是单引号都无法避免 Shell 对叹号的解释。内建命令 history 用来查找最后一个以
引号开头的命令但是没有找到。
4 反斜线用来保护叹号不被解释成其他意思。
5 在双引号内使用反斜线无法使得美元符号($)逃避解释。

10.11.4 单引号与双引号的组合
伴随引用规则的融合在同一个命令中单引号与双引号有各种组合。

实例 10.83

1 > set name = Tom

2 >echo "I cat't give $name" ' $5.00\!\!'


I can't give Tom $5.00!!

3 > echo She cried, \"Oh help me\!\!'"', $name.


She cried, "Oh help me!!",Tom.

说明
1 本地变量 name 被赋值为 Tom。
2 双引号中的单词 can’t 的单引号不能被保护。如果双引号中有$5.00,那么 Shell 就尝试进行命令
替换,所以像$5.00 这样的字符串必须放在单引号当中。反斜线可以保护感叹号,因为不论是单
引号还是双引号都无法保护它。
3 第一个引号被反斜线所保护,感叹号也被反斜线保护。最后一个引号被包括在一组单引号中。单
引号将保护双引号。

10.11.5 成功引用的步骤
在更复杂的命令中,正确地使用引号是非常不容易的,如下的步骤将对你有所帮助(参
考附录 C)

1.了解 Linux 的命令和它的语法,在变量替换以前直接把变量的值写到命令行,来看
看是不是可以得到期望值。
> awk –F :'/^Zippy Pinhead/{print "Phone is " $2}' datafile Phone is 408-123-4563

2.如果 Linux 命令运行正常,再插入变量。这个时候不要改变和删除任何引号,仅仅


是把该值所在的位置替换为变量,在这个例子中把 Zippy Pinhead 替换为变量$name。
> set name="Zippy Pinhead"
> awk –F: '/^$name/{print "Phone is " $2}' datafile

3.引号的运用法则如下:从第一个单引号的左边开始,在变量的美元符号($)以前插
入相应的单引号,如下所示:
> awk –F: '/^'$name'/{print "Phone is " $2}' datafile

PDF created with pdfFactory Pro trial version www.pdffactory.com


422 第 10 章

在单词的右边,也就是$name 中 e 的右边,放上另外一个单引号。这个引号匹配花括号
外的引号。

> nawk –F: '$1 ~ / '$name'/{print $2}' filename

计算单引号的个数,从左边开始共有四个,是偶数,单引号中间的内容都被 Shell 忽略。


4.最后一步:双引号引用变量。把每一个变量都用双引号引用起来,双引号保护被替
换变量中的空格。例如,Zippy Pinhead 中间的空格就被保护起来。

> nawk –F: '$1 ~ / '"$name"'/{print $2}' filename

10.11.6 引用变量
当必须引用变量时可以用:x 和:q 操作符来完成。
用:q 引用变量
:q 用来替换双引号。

实例 10.84

1 > set name = "Daniel Savage"

2 >grep $name:q database


same as

3 > grep "$name" database.

4 > set food = "apple pie"

5 > set dessert = ( $food "ice cream")

6 > echo $#dessert


3

7 > echo $dessert[1]


apple

8 > echo $dessert[2]


pie

9 > echo $dessert[3]


ice cream

10 > Set dessert = ($food:q "ice creadm")

11 > echo $#dessert


2

12 > echo $dessert[1]


apple pie

13 > echo $dessert[2]


ice cream

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 423

说明
1 把变量赋值为字符串“Daniel Savage”。
2 当:q 追加在变量的末尾,变量被引用,这跟被双引号引用的效果是一样的。
3 双引号引用的变量可以发生变量替换,并保护其中的空格,如果没有双引号,grep 就在一个叫作
Savage 的文件中搜索 Daniel。
4 变量 food 被赋值为“apple pie”。
5 变量 dessert 被赋值为一个数组,包含“apple pie”和“icecream” 。
6 dessert 数组的元素是 3 个。当 food 被变量被替换,引号被删除。这里有三个元素,apple、pie 和
ice cream。
7 打印数组的第一个元素,如果没有引号变量被替换被两个单词。
8 打印数组的第二个元素。
9 因为“ice cream”被引用,所以它被看做一个单词。
10 数组 dessert 被赋值为,apple pie 和 ice cream。在某些情况下:q 可以用做与双引号相同的用途,
例如,$food:q 与"$food"这是一样的。
11 数组包含两个字符串,apple pie 和 ice cream。
12 打印第一个元素 apple pie。
13 打印第二个元素 ice cream。

用:x 操作符引用。如果创建了一个数组,且里面的每一个单词都包含元字符,:x 可以防


止其中的元字符在变量替换时被作为元字符翻译。

实例 10.85

1 > set things = "*.c a?? file[1-5]"


> echo $#things
1

2 > set newthings = ( $things )


set: No match.

3 > set newthings = ($things:x)

4 > echo $#newthings


3

5 > echo "$newthings[1] $newthings[2] $newthings[3] "


*.c a?? file[1-5]

6 > grep $newthings[2]:q filex

说明
1 变量 things 被赋值为一个字符串。每个字符串包含一个通配符。变量中的元素个数是 1。
2 当尝试把字符串 things 创建为数组中的一个值。C Shell 把 things 做为通配符做文件名替换,并打
印替换结果 No match。
3 :x 保护变量 things 中的通配符不被 Shell 解释。
4 数组 newthings 包含三个元素。
5 打印数组元素,它们必须被引用否则其中的通配符就会被 Shell 解释做文件名替换。
6 :q 引用变量的作用同于用双引号直接引用变量。程序 grep 将打印文件 filex 中包含有模式 a??
的行。

PDF created with pdfFactory Pro trial version www.pdffactory.com


424 第 10 章

10.12 内 建 命 令
除了一些保存在磁盘上的可执行文件命令外,TC Shell 还有一些包含 Shell 代码中,
直接可以在 Shell 中执行的命令,它们叫作内建命令。内建命令可以出现在 Shell 代码的
任何部分中,但是除了在 Shell 代码的最后,它都是在子 Shell 中执行。命令 buildins 显示
所有内建命令。

实例 10.86

1 > builtins
: @ alias alloc bg bindkey break
breaksw builtins case cd chdir complete continue
default dirs echo echotc else end endif
endsw eval exec exit fg filetest foreach
glob goto hashstat history hup if jobs
kill limit log login logout ls-F nice
nohup notify onintr popd printenv pushd rehash
repeat sched set setenv settc setty shift
source stop suspend switch telltc time umash
unalias uncomplete unhash unlimit unset unsetenv wait
where which while

表 10.24 列出了内建命令。
表 10.24 内建命令和它们的含义

内建命令 含义
: 翻译为空命令,什么也不执行
alias[name[wordlist]] 命令的别名。没有参数时就打印所有别名;有名字作为参数,就打
印这个名字的别名,并以名字和单词列表作为参数设置别名
alloc 显示动态的内存需求,划分已使用的和空闲的部分。根据系统情况
不同而发生变化
bg[%job] 在后台运行当前或者指定的作业
%job& 内建命令 bg 的替代名
bindkey[-l|-d|-e|-v|-u](+) 没有选项情况下第一种形式显示所有绑定的键和与这些键绑定在一
bindkey[-a][-b][-k][-r][--] key 起的编辑命令;第二种形式显示所有编辑命令和与这些命令绑定在
bindkey [-a][-b][-k][-c\-s] [--] key 一起的键;第三种绑定编辑命令到键。选项的含义如下:
command
-l 列出所有编辑命令和对这些命令的简单描述
-d 按照标准方式绑定默认编辑符到键
-e 按照 Gnu 的 emacs 风格方式绑定编辑符到键
-v 按照 vi 风格方式绑定编辑符到键
-a 列出或者改变键盘绑定方式
-b 这个键被翻译为控制字符,写做^X 或者 C-X。元字符写做 M-X。
功能键写做 F-string。扩展前缀,写作 X-A
-k 这个键翻译为方向键的名字,如“上”、“下”、“左”、“右”
(只在 tcsh 中有效)

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 425

续表
内建命令 含义
-r 删除键的绑定。注意,这个参数将清空所有键绑定
-c 把这个键翻译为内建或者扩展命令而不是编辑命令
-s 把这个键理解为表面所表示的字符,在输入字符串的时候这个键
就作为终结符。也可以理解为自我绑定,这种自我绑定不能超过 10
层(仅在 tcsh 中使用)
--强行退出选项处理过程,即使后面的单词是以-开头也不被看做是
一个选项(仅在 tcsh 中使用)
-u 打印用法信息
这个键可以是一个字符也可以是一个字符串。如果命令与一个字符
串绑定,那么字符串的第一个字符就与导入顺序(sequeuce-lead-in)
绑定,整个字符串与命令绑定
与控制键绑定的字符可以直接书写或在字符前加^,例如^A。删除
是^?键与命令可以通过反斜线转义。如:
\a Bell \f Form feed \t Horizontal tab
\b Backspace \n Newline \v Vertical tab
\e Escape \r Carriage return
\nnn ASII 字符对应的八进制数
\nullifies \或^之类字符的特殊含义
break 跳出最内层的 foreach 或者 while
breaksw 跳出 switch,回到 endsw
buildins 打印所有内建变量的名字(tcsh 专有)
bye 内建命令 logout 的同义词,用于查看 Shell 变量的版本(仅在 tcsh
中使用)
case label: switch 语句中的标签
cd [dir] 切换当前目录,如果没有参数就切换到用户主目录
cd[-p][-l][-n\-v][name] 若给定一个目录名称,就改变工作目录到这个目录。否则就切换到
主目录。如果 name 是-,就解释为回到前一个工作目录
-p 打印最后的目录堆栈,就像 dirs
-l,-n 和-v 标志用法一样,类似-p
chdir 跟内建命令 cd 一样
complete[command[word/pattern/lis 没有参数就列出所有的自动完成。如果有 command,就列出所有这
t[:select]/[[suffix]/]…]] 个命令的自动完成。(tcsh 专用)
continue 继续执行最近的一对 foreach 或者 while
default: 这个标签出现在 switch 语句中,放在所有标签的后面
dirs[-l][-n\-v] 第一种形式打印目录堆栈。堆栈的顶部打印在左边,堆栈的第一项
dirs –S\-L[filename] 就是当前目录。-l、~或者~name 被明确翻译为主目录
dirs –c (+)-n,表示条目在到达屏幕边以前换行
(+)-v,表示在每一行前打印它们在堆栈中的位置
-S 第二种形式把堆栈保存为文件
-L 把堆栈保存为 Shell 资源文件
如果文件名称没有给定,就默认使用 dirfile 和~/.cshdirs(tcsh 专用)
-C 选项,用于清空目录堆栈

PDF created with pdfFactory Pro trial version www.pdffactory.com


426 第 10 章

续表
内建命令 含义
echo [-n]list 把 list 中的单词,以空格为分隔符写到标准输出,如果没有-n,就
以换行符为终结符
echo[-n] word… 将 word 写到标准输出,以空格分隔,以换行符为终结符。echo_style
变量将被放置为 BSD/system v 的 echo 版本风格的仿真。
echotc[-sv]arg…
else if(expt2) then 参考“流控制与条件结构”
else 参考对 foreach、if、switch 和 while 语句的描述
end
endif
endsw
end 与 while 配对出现完成循环,while 和 end 都必须独占一行,break
和 continue 可以用来终止循环
evalarg 把参数作为 Shell 的输入,在当前 Shell 中执行命令。通常用于执行
那些由其他命令的结果或变量替换产生的命令,因为命令解析发生
在替换以前
eval command 把 command 作为 Shell 的输入,在当前 Shell 中执行命令。通常用
于执行那些由其他命令的结果或变量替换产生的命令,因为命令解
析发生在替换以前
exec command 在当前 Shell 中执行 command
exit [(expt)] 根据退出状态值或者表达式的值决定是否退出当前 Shell
fg[%job] 把指定作业或者当前作业放在前台运行
%job
filetest –op file 对每一个文件做 op 操作,返回的结果用空格分隔(tcsh 专用)
foreach name (wordlist) 参考循环命令

end
foreach var (worldlist) 参考 foreach 循环
getspath 打印系统执行目录(仅在 tcsh 中使用)
getxvers 打印测试版本前缀(仅在 tcsh 中使用)
glob wordlist 在单词表中做文件名扩展,但是不识别反斜线。输出以空字符作为
分隔
goto label 参考“goto”
goto word word 是一个名文件和一个标签形式的字符串、Shell 会按顺序读入
输入,并搜索包含标签的行,有可能这些行以空格或制表符开头,
Shell 从标签开始向下执行命令
hashstat 打印内部哈希表命令定位效果的统计。对 hash 函数显示可能会
被寻找的目录的每一部分都执行 exec,执行时这些部分前不要加
斜线
history[-hTr][n] 第一种形式打印历史事件清单。如果 n 被给定,只打印和保存最近
history –S\-L\-M[filename] 的 n 个记录。选项-h,打印历史清单不带号码。如果指定-T,在说
history –c 明形式下打印时间戳。-r 意味着打印顺序从最新到最旧(参见“历
史”),选项-c 表示清空历史记录(仅在 tcsh 中使用)

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 427

续表
内建命令 含义
hup[command] 向 command 发送挂起信号并退出 Shell,注意,命令本身可以设置
接收到挂起信号后的反应,而超越 hup,没有参数的时候(只允许
在脚本中) ,将导致 shell 退出而把脚本剩余的部分挂起(仅在 tcsh
中使用)
if(expr) then 如果表达式为真就执行到第一个 else 之间的命令,如果为假,就判

断第二个表达式,如果为真就执行与第二个 else 之间的命令,否则
else if (expr2) then
… 就执行第二个 else 与 endif 之间的命令(else 与 endif 必须在行的开
else 头,if 必须在行的开头或 else 后面)

endif
inlib shared –library 把共享苦添加到当前环境中
jobs[-l] 列出在当前作业控制的作业
kill[-sig][pid][%job] 杀死指定的进程,如果没有参数就杀死当前进程。-l 则显示当前所
kill –l 有可以被杀死的进程
limit[-h][resource[max-use]] 限制当前进程以及当前进程派生进程所消耗的某种资源的上限如
果没有设 max_use,就打印当前 limit 值;如果没设置 Resource 就打
印所有的 limit 的值,-h 选项表示用 hard limit 代替当前的 limit 值。
hard limit 的意思是当前 limit 值的极限大值。只有在极端情况下才
会用到这个值。resource 包括:
cputime,每个进程最多占用多少 CPU 时间
filesize,单个文件的最大尺寸
datasize,每个进程数据块的最大尺寸
stacksize,堆栈的最大尺寸
coredump,内核的最大尺寸
descriptors,文件描述符的最大尺寸
log 打印 watch 变量显示所有登录用户
login 重新登录
login[username|-p] 重新以 username 登录,-p 保存当前环境登录
logout 退出当前 Shell
ls –F[-switch…][file…] 像 ls-F 那样显示文件清单,它用符号区别每一类文件:
/目录
+隐藏目录(仅在 AIX 中使用)
*可执行
:网络特殊设备(仅在 HPUX 中使用)
#块设备
%字符设备
|管道(仅在有管道的系统中使用)
=套接口(仅在有套接口的系统中使用)
@符号连接(仅在有符号连接的系统中使用)
如果 listlink 变量被设置,那么该命令不显示符号连接更多的细节:
@符号连接到非目录文件
>符号连接到目录
&符号连接到当前位置
ls-F 命令可以按照不同的文件类型和扩展名,以不同的颜色显示文
件名

PDF created with pdfFactory Pro trial version www.pdffactory.com


428 第 10 章

续表
内建命令 含义
migrate[-site]pid|%jobid… 第一种形式把指定的进程或者作业移植到指定的或者系统自动确
migrate –site 定的位置。第二种形式等价于 Migrate –site $$
@ 第一种形式打印所有的 Shell 变量的值。第二种把表达式赋值给变
@name=expr 量。第三种把表达式赋值给变量的一个元素。第四或者第五种,表
@name[index]=expr 示递增或者递减
@name++|--
@name[index]++|--
newgrep [-] group* 与 exec newgrp 相同,需要 Shell 在编译时打开该项,参考 vision 变

nice [+number][command] 给 Shell 或者命令设置优先权
nohup[command] 忽略 hup 命令
notify[%job] 当前或者指定作业状态发生变化的时候不同步的通知用户
onintr[-|label] 控制 Shell 在中断的时候的行为
popd[+n] 弹出目录堆栈
printenv[name] 打印环境变量
pushed[+n|dir] 把目录推入目录堆栈
rehash 从新计算内部哈希表
repeat count command 重复执行命令指定次数
rootnode//nodename 改变根结点
sched 第一种形式打印时间表事件清单
sched[+]hh:mm command 第二种形式向清单中添加新的命令
sched –n
set 设置变量,其中-r 表示赋值为只读,-f 表示指定变量值的第一个位
set name … 置,-l 表示注定变量值的最后一个位置
set name=word…
set[-r][-f|-l]name=(wordlist)..(+)
setname[index]=word…
set –r
set –r name
set –r name=word…
set[var[=value]] 参考“变量”
setenv[VAR[word]] 设置环境变量
setenv[name[value]] 设置环境变量
setpath path 设置路径
setspath LOCAL\list\cpu 设置系统执行路径
settc cap value 使 Shell 相信终端兼容能力 cap(在 termcap(5)中定义的)的值是
value,而不做严格检查,虚拟终端的用户可以通过 set xn no 命令设
置输入的时候合适的换行位置
setty[-d\-q|-x][-a][[+|-]mode] 设置 tty 类型的终端模式,-d 表示编辑,-q 表示引用,-x 表示执行
setxvers[string] 设置实验版本前缀
shift[variable] 向左移动数组元素,并删除最左边的数组元素
source[-h]name 从 name 中读取命令。scource 可以嵌套

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 429

续表
内建命令 含义
stop[%job]… 停止当前或者后台指定作业
suspend 挂起 Shell
switch(string) 参考 switch 命令
telltc 列出所有终端的权能
time[command] 显示 Shell 运行时间或者 Shell 运行命令所耗费的时间
umask[value] 显示和设置文件创建掩码
unalias pattern 删除所有匹配的别名
uncomplete pattern 删除所有匹配的自动完成
unhash 禁止内部的哈希表
universe universe 设置 universe 为 universe
unlimit[-h][resource] 删除所有对资源的限制
unsetenv pattern 删除所有匹配的环境变量
unsetenv variable 从环境中删除变量
@[var=expr] 没有参数就显示变量的值,有参数就把表达式赋值给变量的第 n 个
@[var[n]=expr] 元素
ver[systype[command]] 没有 syspe 就打印它。有就把这个值赋值给 SYSPE,并在它下执行
命令
wait 在出现提示符前等待后台作业完成
warp universe 设置 universe 为 universe
watchlog 与 log 等价
where command 报告所有以知命令的位置
which command 显示命令替换、路径查找等以后将执行的命令
while (expr) 参考“循环命令”

10.12.1 tcsh 特有的别名


如果设置,TC Shell 的别名会在指定时间自动执行,它们初始情况都是未定义的。

beepcmd 发出声响
cwdcmd 改变目录后运行
periodic 每 tperiod 分钟运行一次,e.g., > set tperiod=30
> alias periodic date
precmd 在打印提示符前运行,例如 alias precmd date

10.12.2 特殊的内建 Shell 变量


Shell 内建变量有很多特定的含义来控制 Shell 命令执行时的行为。
因为它们是本地变量,
所以如果不能被传递给子进程,就主要在.tcshrc 文件中设置。

PDF created with pdfFactory Pro trial version www.pdffactory.com


430 第 10 章

当 Shell 启动的时候就自动设置如下变量:
addstuffix
argv
autilogout
command
echo_style
edit
group
home
loginsh
oid
path
prompt
prompt2
prompt3
shell
shlvl
tcsh
term
tty
gid
uid
user
version
除非用户要改变它们,否则它们的值是固定的。Shell 会跟踪特性变量的值的变化并定
期更新,例如 cwd dirstack owd status,用户退出登录的时候,Shell 就设置 logout 变量。
一些本地变量与环境变量的名称是一样的,其中一个在用户环境中被改变了,Shell 会
同步改变它们,保证它们之间的匹配。10 这样的变量有 afuser、group、home、path、shlvl、
term 和 user。(虽然 cwd 与 PWD 的意义是一样的,但是它们不会交叉匹配,虽然 PATH 与
它们格式不同,但是它们当中的任何一个改变,PATH 也会跟着改变) 。

表 10.25 特殊 Shell 变量

addsufix* 在文件名自动完成时自动在路径后面加斜线,并在文件名后加空格
afsuser* 如果设置,那么在退出登录时就使用这个变量值而不是本地用户名
ampm* 如果设置就以 12 小时的格式显示时间
argv Shell 命令行参数数组
autocorrect* 如果激活就自动检测命令、文件名或者变量的拼写

10.如果变量被设置为只读、同步将不能工作。

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 431

续表
autolist 如果设置了,在出现两可自动完成的情况下就列出所有的选择
autologout* 她的参数值是两次自动退出登录之间的时间的分钟数。第二个值是自动锁定屏幕前
的时间的分钟数
backslash_quote* 如果设置,反斜线就可以引用它自己,单引号和双引号
cdpath 如果当前目录找不到,cd 需要查找的其他的子目录的列表
color* 允许内建命令彩色显示
complete* 如果设置为 enhance,就把句号、连字符和下划线看做单词的分隔符
correct* 如果设置为 cmd,就自动检查命令拼写。如果设置为 complete,命令就自动完成。
如果设置为 all,就对整个命令行做拼写检查
cwd 当前工作目录的完整路径
dextract* 把第 n 个目录推入到目录堆栈的顶,而不是把这个目录旋转到堆栈的顶部
dirsfile 缺省 dirs –S 和 dirs –L 寻找历史记录的地方。如果没有设置缺省使用~/.cshdirs
dirstack* 列出目录堆栈中的所有目录
dunique* 不允许向堆栈中推入两个相同的路径
echo 如果设置,在执行任何命令之前都首先把命令和参数打印到屏幕
echo_style* 设置 echo 显示风格
edit 设置交互模式下命令行编辑符
ellpsis* 设置提示符顺序
fignore 设置在自动完成的时候忽略的文件名后缀
filec 在 tcsh 中文件名自动完成始终是开的,所以这个变量被忽略
gid* 用户的组 ID
group* 用户的组名称
histchars 设置决定历史替换的符号,它的第一个字符用来替换!,第二个用来替换^
histdup* 控制如何处理历史记录中相同的项目
histfile 默认保存历史文件的位置,如果没有设置的话,使用~./history
histlit* 在历史列表中插入事件
history 第一个单词表示可以保存的历史事件的个数,第二个单词表示历史事件打印的格式
home 主目录
ignoreeof 防止意外用 Control-D 退出登录,强迫使用 exit 才能退出 Shell
implicitcd* 如果设置 Shell 就把目录当作改变当前目录到这个目录的命令
inputmode* 可以设置为插入或者覆盖
listflags* 可以设置为 a,x 或者 A,或者它们的组合,配合 ls-F 使用
如果设置一旦一个作业挂起,就打印其他作业的清单。如果设置为 long 就按照长格
listjobs*
式打印
listlinks* 如果设置,在使用内建命令 ls –F 的时候就显示符号连接点的类型
listmax* list-choice 命令选项的做大个数
loginsh* 表示登录 Shell

PDF created with pdfFactory Pro trial version www.pdffactory.com


432 第 10 章

续表
logout* 又 Shell 设置,正常登录,设置为 NormaL,如果自动退出登录就设置为 automatic,
如果 Shell 被杀死,就设置为 hangup
mail 用来检查邮件的目录或者文件的名字
matchbeep 如果设置为 never,自动完成的时候就不发出声音;如果设置为 nomatch,就在没有
匹配的时候发出声音;如果设置为 ambiguous,就在多个匹配的时候发出声音
nobeep 关闭声音
noclobber 防止在设置重新定向的时候删除已经存在的文件
noglob 如果设置,在使用通配符的时候继承文件名和目录堆栈
nokanji* 如果设置这个变量同时 Shell 支持 Kanji,它就被禁止,以便保证元字符可以使用
nonomatch* 如果设置这个变量,在文件名替换和目录堆栈替换的时候,即使没有匹配任何文件
和目录也不打印任何错误信息
nostat* 一个特殊的目录列表
notify 如果设置,Shell 就不同步的通知用户作业状态,而不是等待作业结束才显示提示符
oid* 用户的真实的组织 ID
owd* 前一个工作目录
path 用来寻找可执行命令的目录列表
printexitvalue 如果被设置,一旦一个程序的退出状态值不是 0 就打印退出状态值
prompt 主提示符
prompt2* 辅助提示符
prompt3* 第三提示符
promptchars* 第一个符号给普通用户,第二个给超级用户
pushtohome* 如果设置 pushd 就理解为 pushd~
pushdsilent* 如果设置 pushd 命令和 popd 命令不打印结果
recexact* 如果设置,则自动完成只在精确匹配的情况下在有效
recognize_only_e
如果设置,只列出目录下可执行文件
xecutables*
rmstar* 如果设置,在删除所有文件以前提示用户确认
rprompt* 当提示符显示在左边的时候,字符串显示在屏幕的右边。该变量可识别提示符的格
式,并让提示符自动的消失并在需要的时候显示出来,以确保命令行输入不被遮挡,
它只在提示符,命令行输入和该变量合适一齐出现的第一行中出现。如果没有被设
置为编辑状态,rprompt 就在提示符之后,命令行输入开始前打印自己
savedirs* 如果设置在退出 Shell 前保存设置
savehist* 如果设置,在 Shell 退出前保存历史文件
sched* 设置内建命令 sched 打印时间表的格式
shell Shell 命令解释器文件
shlvl* Shell 嵌套的个数
status 返回最后一个命令的状态
symlinks* 可以设置为多个不同的值来控制符号连接
tcsh* 显示版本号,顺序是先是主版本好,然后是小版本号,然后是补丁号

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 433

续表
term 终端类型
time 如果设置为一个数字,内建命令 time 会在任何一个命令占用 CPU 时间超过 time 秒
以后自动执行。如果数字后面还有字符,那这些字符是用来格式化 time 命令的输出
的,格式化可以用如下的符号:
%U 用户态进程占用 CPU 时间的秒数
%S 核心态进程占用 CPU 的秒数
%E 系统时钟占用的时间
%P CPU 的运用百分比(%U+%S)/%E
%W 进程切换的次数
%X 共享文本空间的平均尺寸
%D 非共享数据空间的平均尺寸
%K %X 加%D 的总尺寸
%M 进程用过的最大内存量
%F 主要内存页缺失个数
%R 次要内存页缺失个数
%I 输入操作次数
%O 输出操作次数
%r 接收到的套接口信息数
%s 发送的套接口信息数
%k 接到的信号数
%w 自发文本切换次数
%c 非自发文本切换次数
以上这些中的头四个可以为没有 BSD 资源限制的函数的系统支持,默认的时间格式
是%Uu%Ss%E%P%X+%Dk%I+%Oio%Fpf+%Ww 给支持资源使用报告的系统,对于
不支持该报告的系统的格式是%Uu%Ss%E%P
在 DYNIX/ptx 中,%X,%D,%K,%r 和%s 不能用,但是也有额外的符号可用:
%Y 系统调用的个数
%Z 全部为 0 的页的个数
%i 由于内核使得进程保存设置尺寸的次数增加量
%d 由于内核使得进程保存设置尺寸的次数减少量
%l 该系统调用的次数
%m 写系统调用的次数
%p 从原始设备读的次数
%q 向原始设置写的次数
默认的时间格式是%Uu%Ss%E%P%I+%Oio%Fpf+%Ww。注意:多 CPU 的情况下,
CPU 的使用百分比可以超过 100%
tperiod* 在两个 periodic 执行之间的分钟数
tty* Tty 终端名
uid* 真实的用户 ID
user 用户登录名
verbose 如果设置将导致每一个命令的所有单词在历史替换以后都打印出来
version 版本 ID 戳
* 仅在 tcsh 中使用。

PDF created with pdfFactory Pro trial version www.pdffactory.com


434 第 10 章

Shell 命令行开关。TC Shell 有一些命令行开关,参考表格 10.26。


表 10.26 Shell 命令行开关

- 指定登录 Shell
-b 退出选项处理过程,这个选项以后的选项都不被看做选项
-c 如果-C 后面有一个参数,Shell 就从这个参数读取数据
-d Shell 从~./cshdirs 装入目录堆栈
-Dname[=value] 设置环境变量
-e 如果任何程序没有正常退出,也就是退出状态值是非 0,就退出 Shell
-f 快速启动,忽略~./tcshrc 文件
-F 用 fawk(2)替代 vfawk(2)来产生新的子进程
-i 在交互模式下从命令行输入,即使使用的不是标准终端。如果输入和输出连
接的是标准终端,这个选项就不必要了
-l 如果只有-l 标志,那么当前 Shell 就是登录 Shell
-m 即使不是有效的用户也装入~./tcshrc 文件
-n 用于脚本除错。进行脚本语法检查但是不运行脚本
-q 在用做除错状态下,Shell 接受 SIGQUIT 信号并作出反映。这时候作业控制
被禁止
-s 从标准输出取得输入
-t Shell 读取并执行一行输入,引号中间可以进行命令替换。这个选项用于除错
-v 设置冗长的变量的时候,在完成历史替换以后,需要引用
-x 设置回应 Shell 变量,在执行以前,历史替换和变量替换以后,需要引用这个
选项用于除错
-V 在执行~./tcshrc 文件以前设置冗长变量
-X 在执行~./tcshrc 文件以前设置回应 Shell 变量

TC SHELL 练习

练习 1——启动

1.init 进程处理的是什么事情?
2.login 过程的功能是什么?
3.怎样才能知道自己使用的是哪一种 Shell?
4.如何修改你的登录 Shell?
5.解释.tcshrc、.cshrc 和.login 文件之间的区别,指出哪个文件是可执行的。
6.按照如下的要求编辑你的.tcshrc 文件:
a.建立你自己的三个别名

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 435

b.用主机名、时间和用户名重新设置你的提示符
c.设置实例 10.87 中的变量并在每个变量后面用注释说明这个变量

实例 10.87
noclobber # protects clobbering files
# from redirection overwriting
history
ignoreeof
savehist
prompt2

7.输入如下的命令:
source .tcshrc

source 命令的作用是什么?
8.按照如下的要求编辑的.login 文件:
a.欢迎用户
b.添加用户主目录到路径中
c.显示.login 文件源代码
9.解释 path 与 PATH 之间的区别。

练习 2——历史

1.在你退出登录以后哪个文件用于保存历史?哪个变量负责控制显示多少个历史事
件?savehist 变量的作用是什么?
2.按反顺序打印你的历史事件清单。
3.打印没有行号的历史清单。
4.输入如下的命令:
a.ls –a
b.data ‘+%T’
c.cal 2000
d.cat /etc/passwd
e.cd
5.当在命令行输入 hist 命令后,输出结果是什么样的?
a.如何再次执行最后一条命令?
b.现在输入:type a b c
使用 history 命令再次执行 echo 命令和最后一个参数 c。
6.利用历史记录执行历史记录清单中最后一个以字母“d.”开头的命令。
7.执行最后一个以字母“c.”开头的命令。
8.执行 echo 命令和上一条命令的最后一个参数。
9.利用历史替换命令把 data 命令中的字母“T”替换为“H.” 。
10.如何使用 bindkey 命令启动命令行编辑器 vi?

PDF created with pdfFactory Pro trial version www.pdffactory.com


436 第 10 章

11.如何列出编辑命令清单并显示它们的用法?
12.如何查看编辑键绑定情况?
13.描述变量 fignore 的用处。

练习 3——Shell 元字符

1.在提示符下输入:
touch ab abc a1 a2 a3 all al2 ba ba.1 ba.2 filex filey Abc ABC ABc2 abc

2.写出并测试可以完成如下功能的命令:
a.列出所有以字母 a 开头的文件
b.列出所有以至少一个数字结尾的文件
c.列出所有不以字母 a 或者 A 开头的文件
d.列出所有以一个句号后面跟一个数字结尾的文件
e.列出所有刚好包含两个 alpha 的文件
f.列出所有只包含三个大写字母的文件
g.列出所有以 11 或者 12 结尾的文件
h.列出所有以 x 或者 y 结尾的文件
i.列出所有以一个数字或者一个大写字母或者一个小写字母结尾的文件
j.列出所有包含字母 b 的文件
k.删除所有以 a 开头的两个字母的文件

练习 4——重新定向

1.跟终端捆绑在一起的三个文件的名字是什么?
2.什么是文件描述符?
3.使用什么命令能完成下列工作:
a.重新定向 ls 命令的输出到文件 lsfile
b.重新定向 data 命令的输出并追加到文件 lsfile
c.重新定向命令 who 的输出到文件 lsfile,将发生什么事情?
d.如果你只输入 cp 命令而不给任何参数,将发生什么事情?
e.你如何把如上所述这些命令的错误信息保存在一个文件中?
f.使用 find 命令从上级目录开始找出所有的文件,然后把输出结果保存到文件
found,并把输出的错误信息保存到文件 found.errs
g.什么是 noclobber?你如何克服它?
h.取得三个文件的输出并把这些输出重新定向到文件 gottemall
i.通过对 ps 命令和 wc 命令使用管道找出当前系统正在运行的进程有多少?

练习 5——变量和数组

1.变量和环境变量之间的区别是什么?
2.如何把所有的本地变量列表显示出来?如何把所有环境变量列表显示出来?

PDF created with pdfFactory Pro trial version www.pdffactory.com


交互式 TC Shell 437

3.你可以在哪个初始化文件中保存本地变量?为什么?
4.建立一个叫作 fruit 的数组,并把 5 种不同的水果的名字放入这个数组。
a.打印数组
b.打印数组最后的一个元素
c.打印数组元素的个数
d.删除数组中第一个元素
e.如果在数组中保存一个非水果的元素,是否可以?
5.描述词表与字符串之间的区别。

PDF created with pdfFactory Pro trial version www.pdffactory.com


第 11 章

用 TC Shell 编程
11.1 创建 Shell 脚本的步骤
Shell 脚本通常用编辑器创建,由命令和注释的组成。注释是用来说明的文本,在行首
以# 符号加以识别。

11.1.1 首行
在左上角,由#!引导的程序行(通常念做“shbang”)标明用以解释执行脚本的程序。
这种程序行一般是:
#!/bin/tcsh

#!是个神奇的符号,在脚本中,系统内核通过它来识别解释命令的程序。当程序载入内
存时,系统内核检查它的首行。假如首行是二进制数据,程序就会作为已编译程序来执行;
假如首行含#! ,系统内核就会按#!之后的路径找到该程序作为解释器来执行程序。如路径
是/bin/tcsh,系统就用 TC Shell 来解释脚本。该行必须是脚本的首行,否则它就会被视为注
释行。
当启动脚本时,首先执行.tcshrc 文件,这是为了让这个文件内的所有命令都成为脚本的
一部分。在 TC Shell 程序后加–f(fast)即略过不读这个文件。具体写出来是这样的:
#!/bin/tcsh –f

11.1.2 注释
所谓“注释”就是前带 # 的程序行,用于对脚本进行说明。假如没有注释,有时候很
难理解脚本的意图。尽管注释很重要,但一般也是少而分散的,甚至根本不用。为了使别人
和你自己更容易明白,应该尽可能地养成做注释的习惯。因为在两天之后,你可能就已经记
不清你现在所做的事情了。

11.1.3 让脚本运行起来
当你创建了脚本之后,该脚本还没有执行权限。我们需要用 chmod 命令赋予它可执行
的权限以使它运行起来。
440 第 11 章

实例 11.1

1 > chmod +x myscript


2 > 1s-F myscript
-rwxr--xr--x 1 ellie 0 Jul 13:00 myscript*

说明
1 chmod 是为用户、属组与其他用户打开执行权限的命令。
2 ls 命令的输出表示 joker 文件对所有的用户开放执行权限。文件名结尾的星号 * (之前用了-f 选
项才会出现)表明了它是个可执行程序。

11.1.4 脚本范例
在下面的例子中,用户将在编辑器里创建脚本。并在存盘后,使用 chmod 命令打开执
行权限使脚本运行。程序中如有错误,C Shell 会即时报错。

实例 11.2

(The Script - info)


#!/bin/tcsh –f
# This script is called info
1 echo Hello ${LOGNAME}!
2 echo The hour is ‘date +%H‘
3 echo "This machine is ‘uname –n‘"
4 echo The calendar for this month is
5 cal
6 echo The processes you are running are:
7 ps au | grep "^ *$LOGNAME"
8 echo "Thanks for coming. See you soon\!\!"
(The Command Line)
9 > chmod +x info
10 > ./info
1 Hello ellie!
2 The hour is 09
3 This machine is jody
4 The calendar for this month is
5 January 2000
S M Tu W Th F S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
7 The processes you are running are:
< output of ps prints here >
8 Thanks for coming. See you soon!!

说明
1 屏幕上显示欢迎的字样。LOGMAME 环境变量用于保存用户名,这等同于 BSD 系统里的 USER
环境变量。花括号用于将变量和惊叹号分开。在这里,惊叹号(!)无需转义,因为它不会被解
释为一个历史符号,除非在其后面有一个字符。
2 我们看到,date 命令用单引号括着,此时 Shell 就会执行命令替换。当前日期及时间的输出会替
用 TC Shell 编程 441

换掉 echo 后的字符串。
3 uname –n 命令显示机器名。
4 cal 命令没有括到单引号里,是因为当 Shell 执行替换时,新的结果会紧跟着一行行地输出,这样
就会出现一个很难看的日历。但若把 cal 命令当作一行的话,日历的格式就能保留下来。
5 显示该月日历。
6,7 打印用户进程。
8 打印字符串,在注意两个惊叹号前都有 \ 符号,这是为了防止历史替换。

11.1.5 变量(回顾)
当我们编写 Shell 程序时,常常需要用变量来记录一些信息。变量要么直接在脚本里被
赋值,要么通过命令行赋值,再或者通过用户直接输入赋值。 “变量”和“环境变量”中有
关于局部变量和环境变量的具体描述。

实例 11.3

set name = "Ellie" # local variable assignment


setenv NAME "Tom" # environment variable assignment
echo $name # printing valur of local variable
echo $NAME # printing valur of environment variable

11.2 读取用户输入

11.2.1 $<变量
在创建一个交互式的脚本时,我们常用特定的 TC Shell 变量接收标准输入。$<变量从标
准输入读入一个词,该词为第一个字符到第一个空格前(但不包括换行符) ,并把这个词赋
1
值给该变量。将$>放到双引号或圆括号里时, 就读入一整句,但不包括换行符。

实例 11.4
(The Script - greeting)
#!/bin/tcsh –f
# The greeting script
1 echo –n "What is your name? "
2 set name = "$<"
3 echo Greetings to you,$name.

(The Command Line)


> chmod +x greeting
> greeting
1 What is your name? Dan Savage
3 Greetings to you, Dan Savage

说明
1 显示双括号里的语句。echo 命令后的–n 让下一句紧跟第一句之后。而在其他版本中的 echo 命令,

1.在 C Shell 中读取一整行并不需要把$<放在双引号中。


442 第 11 章

将\c 放在后面,也是这样的功用。如:echo“hello\c”。
2 无论你从终端输入什么,在换行符前的字符串都会原封不动地赋值给 name 变量。
3 变量替换后,显示此行。

11.2.2 由输入的字符串创建单词列表
由于用$<引导的读入信息都被看作字符串,你也许会想把字符串截成一个单词列表。也
可以用 Linux 的 head 命令读入用户的输入信息,并直接储存为单词列表。(“命令替换”部
分有详细讲述) 。

实例 11.5

1 > echo What is your full name\?


2 > set name = "$<"
Daniel Leo Stachelin

3 > echo Hi $name[1]


Hi Daniel Leo Stachelin
4 > echo $name[2]
Subscript out of range.
5 > set name = ( $name )

6 > echo Hi $name[1]


Hi Daniel
7 > echo $name[2] $name[3]
Leo Stachelin
8 > echo Where do you live\?
9 > set city = ‘head –1‘
Chico, California
10 > echo "$city[1] is a college town,isn't it?"

说明
1 要求用户输入信息。
2 特定变量$<在接收输入信息时,将把输入的信息当成字符串格式。
3 由于值“Daniel Leo Stachelin”被当成一个单独的字符串,所以下标 [1] 显示整字符串(下标从
1 开始) 。
4 由于这个字符串只包括了一个词,所以当使用下标[2]时,shell 就会报错“Subscript is out of range”。
5 字符串放到圆括号里就成了单词列表。这样就创建了一个数组。字符串被断成了一组单词,并赋
值给变量 name。
6 打印数组的第一个元素。
7 打印数组的第二和第三个元素。
8 要求用户输入信息。
9 在 Linux 的 head 命令后加 –1 表明只接收一行的输入(–2 表示可接收两行) 。head 命令放在单引
号里就是让 shell 执行命令替换,也就是说,把 head 命令的结果返回,并赋值给变量 city,而用
户的输入再被输出。当执行命令替换时,返回的值被存为一组用空格隔开的字符串。变量 city 是
个数组,Chico 是数组的第一个元素。
10 打印 city 数组的第一个元素。
用 TC Shell 编程 443

11.3 计 算
我们当然并非要在一个 Shell 脚本里做算术题,但有时计算也是必要的,譬如说,增/
减循环计数器。TC Shell 只支持整数计算,符号@负责将计算结果赋值给数字变量。

11.3.1 算术运算符
表 11.1 列举了在运算中要用到的运算符及其作用。这些运算符和 C 语言里的运算符一
模一样。运算符的优先权顺序参考表 11.6 所示。运算符的简化也一样是从 C 语言借用来的,
见表 11.2。
表 11.1 运算符

功用 运算符
加 +
减 -
除 /
乘 *
取模 %
左移 <<
右移 >>

表 11.2 简化运算

运算符 例子 等价于
+= @ num += 2 @ num = $num + 2
-= @ num -= 4 @ num = $num - 4
*= @ num *=3 @ num =$num * 3
/= @ num /= 2 @ num =$num / 2
++ @ num ++ @ num = $num + 1
-- @ num -- @num =$num – 1

实例 11.6

1 > @ sum = 4 + 6
echo $sum
10

2 > @ sum++
echo $sum
11

3 > @ sum += 3
echo $sum
14
444 第 11 章

4 > @ sum--
echo $sum
13

5 > @ n = 3+4
@: Badly formed number

说明
1 将 4 和 6 的相加结果赋值给变量 sum(@后必须加个空格)。
2 变量 sum 增 1。
3 变量 sum 增 3。
4 变量 sum 减 1。a
5 在@后、运算符前后都要求加空格。

a. 优先权相等的运算中,运算顺序应该从右到左。如例:(b * c/a),先做除法再做乘法。具体见“优先权和综合运算”。

11.3.2 浮点运算
由于该 Shell 不支持浮点运算,所以若需要更复杂的数学运算,则需要用到 Linux 的其
他工具。
bc 和 nawk 命令可用于复杂的数学运算。

实例 11.7

(The Command Line)


1 set n=‘echo "scale=3; 13 / 2" | bc‘
echo $n
6.500

2 set product=‘awk –v x=2.45 –v y=3.124 \


'BEGIN{printf "%.2f\n", x * y }'‘

> echo $product


7.65

说明
1 echo 命令的输出被重新定向至 bc 命令。精度设置为 3,也就是说,则显示小数点后三位有效数
字。运算表达式为 13 除以 2。整个管道输出被括在符号“”里。命令替换的输出结果将被赋给
变量 n。
2 awk 命令从命令行的参数表得到它的值。每个传递给 awk 命令的参数前都带一个-v 开关。例如,
-v x=2.45 和 –v y=3.124。当二者相乘后,printf 函数格式化并输出结果,该结果精确到小数点后
两位数字。最后,输出被赋给变量 product。

11.4 调 试 脚 本
一些简单的语法和逻辑错误常导致 TC Shell 脚本不能运行。tcsh 命令的选项能帮助你调
试程序。见表 11.3。
用 TC Shell 编程 445

表 11.3 echo (-x)和 verbose (-v)

tcsh 的选项
tcsh –x 脚本名 显示在变量替换后、程序执行前所有脚本源程序
tcsh –v 脚本名 程序执行前显示其每一行脚本源程序
tcsh –n 脚本名 解释但不执行命令
set 命令的参数
set echo 显示变量替换后、脚本执行前所有脚本源程序
set verbose 程序执行前显示其每一行脚本源程序
作为脚本的首行
#!/bin/tcsh –xv 同时打开 echo 和 verbose 的功能。这两个选项会分别激活或与其他的
csh 调用的参数激活

实例 11.8

(The –v and –x Options)


1 > cat practice
#!/bin/tcsh -f
echo Hello $LOGNAME
echo The date is ‘date‘
echo your home shell is $SHELL
echo Good-bye $LOGNAME

2 > tcsh –v practice


echo Hello $LOGNAME
Hello ellie
echo The date is ‘date‘
The date is Mon May 24 12:26:07 PDT 2000
echo your login shell is $SHELL
your login shell is /bin /csh
echo Good-bye $LOGNAME
Good-bye ellie

3 > tcsh –x practice


echo Hello ellie
Hello ellie
echo The date is ‘date‘
date
The date is Mon May 24 12:26:15 PDT 2000
echo your login shell is /bin/tcsh
your login shell is /bin/tcsh
echo Good-bye ellie
Good-bye ellie

说明
1 显示 TC Shell 脚本中的内容。 执行变量和命令替换的程序行也同时显示, 以便区别 echo 和 verbose
的不同。
2 由于用了 tcsh 的–v 选项,verbose 激活。此脚本的所有程序行输入都原封不变地显示在屏幕上。
最后程序执行。
3 tcsh 的–x 选项启动了 echo 命令。当变量和命令进行替换后,脚本中的所有程序行都输出在屏幕
446 第 11 章

上,然后程序才执行。由于此参数能让你看到变量/命令替换中什么被替换掉了,所以它比 verbose
选项更常使用。

实例 11.9

(Echo and Verbose)


1 > cat practice
#!/bin/tcsh –f
echo Hello $LOGNAME
echo The date is ‘date‘
set echo
echo your home shell is $SHELL
unset echo
echo Good-bye $LOGNAME

2 > chmod +x practice

3 > practice
Hello ellie
The date is Mon May 24 12:25:16 PDT 2000
--> echo your login shell is /bin/tcsh
--> your login shell is /bin/tcsh
--> unset echo
Good-bye ellie

说明
1 设置和取消 echo 命令。这样,你只需在脚本出错的时候调试那一部分程序就可以了,免去了一
行行查看整个脚本的工夫。
2 用 chmod 命令打开执行权限。
3 在-->符号处 echo 命令被打开。在变量和命令替换后,脚本的每一行都被输出,最后执行程序。

实例 11.10

1 > cat practice


#!/bin/tcsh –f
echo Hello $LOGNAME
echo The date is ‘date‘
set verbose
echo your home shell is $SHELL
unset verbose
echo Good-bye $LOGNAME

2 > practice
Hello ellie
The date is Mon May 24 12:30:09 PDT 2000
--> echo your login shell is $SHELL
--> your login shell is /bin/csh
--> unset verbose
Good-bye ellie

说明
1 verbose 在脚本里被设置和取消。
2 在-->符号处 verbose 被打开。每一行如用户输入时原样输出,然后程序执行。
用 TC Shell 编程 447

11.5 命令行参数
Shell 脚本可接受命令行参数,而参数又能从某种程度上影响程序的执行。TC Shell 赋值
命令行参数给位置参数, 对能赋值的参数个数没有限制(Bourne Shell 规定只能设置 9 个参数)

位置参数属于数字参数。脚本名赋值给$0,脚本名后的各个词分别赋值于$1、$2、$3……$ {10}
和${11}等。$1 是第一个命令行参数。除了位置参数之外,TC Shell 还提供内置数组 argv。

11.5.1 位置参数和 argv


如果你正在使用 argv 这个内置数组符号,那么就必须通过命令行向合法脚本提供参数,
否则 TC Shell 就会发出“subseript out of range”的错误信息。argv 数组不包括脚本名。第一
个参数是 sargv[1],可通过$#argv 命令来显示变量的个数,而且也没其他方法可以显示变量
个数了。见命令行参数的列表,表 11.4。
表 11.4 命令行参数

参数 含义
$0 脚本名
$1, $2, ……${10}…… 第一、第二个位置参数用$符号后紧跟数字来表示。用花括号括起数字
10 是为了不让系统误认为是第一个位置参数后面跟着数字 0
$* 所有的位置参数
$argv[0] 无效:没有任何输出。C Shell 数组下标是从 1 开始的
$argv[1] $argv[2]… 第一个参数、第二个参数、……
$argv[*] 所有的参数
$argv 所有的参数
$#argv 参数个数
$argv[$#argv] 最后一个参数

实例 11.11

(The Script)
#!/bin/tcsh –f
# The greetings script
# This script greets a user whose name is typed in at the
# command line.

1 echo $0 to you $1 $2 $3
2 echo Welcome to this day ‘date | awk '{print $1, $2, $3}'‘
3 echo Hope you have a nice day, $argv[1]\!
4 echo Good-bye $argv[1] $argv[2] $argv[3]

(The Command Line)


> chmod +x greetings

> greetings Guy Quigley


1 greetings to you Guy Quigley
448 第 11 章

2 Welcome to this day Fri Aug 28


3 Hope you have a nice day, Guy!
4 Subscript out of range

说明
1 显示脚本名和前三个位置参数。因为命令行里只有两个位置参数,Guy 和 Quigley,所以$1 代表
Guy,$2 代表 Guiley,而$3 未定义。
2 用单引号括起 awk 命令是为了避免 shell 把 awk 的列数$1、$2、$3 与位置参数$1、$2、$3 混淆。
注意,不要混淆 awk 的列数$1、$2、$3 与位置参数$1、$2、$3。
3 argv 数组的值来自于命令行。Guy 赋值于 argv[1],同时输出此值。在脚本里可用 argv 数组或者
位置参数引用命令行参数。它们的区别在于:引用一个未赋值的位置参数,不会产生任何错误,
而未赋值的 argv 将会导致脚本程序退出并显示错误信息“Subscript out of range”。
4 由于引用了未赋值的 argv[3],Shell 报错“Subscript out of range”。

11.6 流程控制和条件语句
我们用 if、if/else、if/else if/else 和 switch 命令来选择控制。这些命令通过判断表达式的
“真”、“假”来控制程序的流程。

11.6.1 测试表达式
表达式由操作符分隔的操作数构成,表 11.5 和表 11.6 列出了操作符。为了测试表达式
的值,表达式必须以括号括起来。TC Shell 计算表达式的值,返回零或非零值。如果返回值
非零,则表达式认为是“真” ,否则为“假”。
当计算含逻辑与(&&)的表达式时,Shell 从左向右进行运算。当第一个表达式(在&&
前)的值为“假”时,整个表达式的即被赋成“假” ,而不会再检查剩余的表达式。也就是
说,如果在表达式中使用了&&操作符,而且第一个表达式的值为“假”时,整个表达式的
值即为“假”。如果&&两边的表达式的值都为“真” ,整个表达式的值也就为“真” 。
当计算含逻辑或(||)的表达式时,如果位于||左边的第一个表达式为“真”值,整个表
达式的值即被赋予“真”值,而不会再继续检查表达式的其余部分。在一个逻辑或表达式中,
只需其中一个表达式为“真” ,整个式子即为“真” 。
逻辑非是单目操作符,也就是它只需一个表达式即可计算。如果在 NOT 操作符右边的
表达式的值为“真”,整个表达式就为“假” ,否则为“真” 。
表 11.5 比较和逻辑操作符

操作符 含义 例子
== 是否等于 $x==$y
!= 是否不等于 $x!=$y
> 是否大于 $x>$y
>= 是否大于或等于 $x>=$y
< 是否小于 $x<$y
<= 是否小于或等于 $x<=$y
=~ 字符串匹配 $ans =~ [Yy]*
!~ 字符串不匹配 $ans !~ [Yy]*
用 TC Shell 编程 449

续表
操作符 含义 例子
! 逻辑非 ! $x
|| 逻辑或 $x || $y
&& 逻辑与 $x && $y

优先与顺序。像 C 一样,当测试表达式时,TC Shell 也遵循优先和顺序规则。下面是一


个含混合操作符的的表达式:
@ x = 5 + 3 * 2
echo $x
11

Shell 以某种顺序读取操作符。优先是指操作符的优先顺序。结合(Associativity)指的
是当优先权相等时,表达式从左向右还是从右向左读取。和算术运算不同(可能在 Shell 脚
本中你永远也用不到) ,如果优先权相等的话,结合的顺序由左向右。也可以通过括号来改
变运算顺序(参见表 11.6)。
@ x = ( 5 + 3 ) * 2
echo $x
16

表达式可以是算术运算、比较运算也可以是逻辑运算。算术表达式使用下列算术操作符:
+ - * / ++ -- %
比较表达式用下列操作符计算“真”(非零)
“假”
(零)值:
> < >= <= == !=
逻辑表达式使用下列操作符:
!&& ||
表 11.6 操作符的优先顺序

优先权 操作符 含义
高 () 改变优先权;group
~ 余数
! 逻辑非,相反的
! /% 乘、除、模
+- 加、减
<<、>> 左位移和右位移
> >=、< <= 比较操作符:大于、小于
= =、!= 是否相等:相等或不相等
=~、!~ 模式匹配:匹配、不匹配
& 位与
^ 位非
| 位或
低 && 逻辑与
|| 逻辑或
450 第 11 章

11.6.2 if 语句
最简单的条件语句是 if 语句。如果 if 后的表达式的值为 “真” ,那么从关键字 then 到 endif
之间的命令就会执行。关键字 endif 是 if 语句块的结束标志。只要每一个 if 都对应一个 endif,
if 语句就可以进行嵌套。每个 endif 和最近的 if 匹配组成一个 if 语句块。

格式
if ( expression ) then
command
command
endif

实例 11.12

(In the Script-Checking for Arguments


1 if ( $#argv != 1 ) then
2 echo "$0 requires an argument"
3 exit 1
4 endif

说明
1 这行的意思是:如果参数($#argv)的个数(由命令行取得)不等于,那么…
2 如果第一行是“真”的,执行这一行和第三行的命令。
3 程序退出,并置返回值为 1,表示失败退出。
4 每一个 if 语句块都必须以 endif 结束。

测试未赋值或空值的变量。$?加上变量名表示测试该变量是否被赋值。假如变量是空值
则返回“真”

实例 11.13
(From .tcshrc File)
if ( $?prompt ) then
set history = 32
endif

说明
在本例中,首先测试变量 prompt 是否被赋值,如果已赋值,表明你在运行交互式 Shell,而不
是一个脚本。只有在交互模式下,prompt 变量才会被赋值。因为命令历史机制只有在交互模式的时
候才有用,所以在运行脚本的时候 Shell 不会激活命令历史机制。

实例 11.14
(The Script)
echo –n "What is your name? "
1 set name = "$<"
2 if ( "$name" != "" ) then
grep "$name" datafile
endif
用 TC Shell 编程 451

说明
1 要求用户输入。假如用户直接键入回车,变量 name 就被设置,但不是空值。
2 变量用双引号括起是为了当用户键入一个以上的词时,依然能赋值给变量 name。若是去掉双引
号,此时用户键入姓、名,shell 就会退出本脚本程序,并报错“Expression syntax”。空双引号
表示空字符串。

11.6.3 if/else 语句
if/else 的结构是个双向转移的控制命令。若在 if 后的语句为逻辑真,就执行其后的块程
序。反之,就执行 else 之后的程序。endif 语句与内部语句 if 匹配,用于结束语句。

格式

if ( expression ) then
command
else
command
endif

实例 11.15

1 if ( $answer =~ [Yy]* ) then


2 mail bob < message
3 else
4 mail john < datafile
5 endif

说明
1 此行表明:若 answer 的值与 Y 或者 y 匹配,可接 0 个或者更多的字符,然后执行第二行命令;
若不匹配就执行第三行命令(*符号是一个 shell 元字符) 。
2 文件 datafile 的内容发送给用户 bob。
3 第一行若是为逻辑假则执行 else 下的命令。
4 文件 datafile 的内容发送给用户 john。
5 用命令 endif 结束 if 板块。

11.6.4 调试表达式
在 TC Shell 中,-x 选项(相等于 echo)能够在程序执行时追踪运行中的进程。若不能
肯定程序运行到何处,这将会是调试脚本程序的好方法。

实例 11.16
(The Script-Using Logical Expressions and Checking Values)
#!/bin/tcsh –f
# Script name: logical
set x = 1
set y = 2
set z = 3
1 if ( ("$x" && "$y" ) || ! "$z" ) then
# Note: grouping and parentheses
452 第 11 章

2 echo TRUE
else
echo FALSE
endif

(The Output)
3 > tcsh –x logical
set x = 1
set y = 2
set z = 3
if ( ( 1 && 2 ) || ! 3 ) then
echo TRUE
TRUE
else
>

说明
1 求逻辑表达式的值。第一个表达式用圆括号括起(其实不必要用圆括号括起,因为&&要优先于
||)
。圆括号里不要有空格,而否定运算符(! )则要求后面必须有个空格。
2 若表达式的值为逻辑真,则执行此行。
3 在选项 –x 打开的状态下执行 tcsh 程序。这样就打开了 echo 命令。当执行变量替换后,脚本中
的所有程序行都会重新输出。

11.6.5 if 语句和单命令
如果表达式后面跟着一个单个命令,则 then 和 endif 这样的关键字就不再需要。

格式
if ( expression ) single command

实例 11.17
if ($#argv == 0) exit 1

说明
测试表达式。如果命令行参数的个数,$#argv 为 0, 则程序状态为 1。

11.6.6 if/else if 语句
if/ else if 结构提供了一个多向的解决方案机制。它测试全部的表达式,若里面一个的值
是真,就执行其后的语句。假如表达式值都不为真,就执行 else 后面的语句块。

格式

if ( expression )then
command
command
else if ( expression ) then
command
用 TC Shell 编程 453

command
else
command
endif

实例 11.18

(The Script – grade)


#!/bin/tcsh –f
# This script is called grade
echo –n "What was your grade? "
set grade = $<
1 if ("$grade" < 0 || "$grade" > 100 ) then
echo "Illegal grade!"
exit 1
endif
2 if ( $grade >= 90 && $grade <= 100 ) then
echo "You got an A\!"
3 else if ( $grade > 79 ) then
echo "You got a B"
4 else if ( $grade > 69 ) then
echo "You're average"
else
5 echo "Better study"
6 endif

说明
1 假如 grade 小于 0 或大于 100,则不合逻辑。注意此处的“或” (||) 。有且只有一个表达式可为“真” 。
2 如果 grade 在 90~100 之间(包括 90 和 100),则输出“You got an A!”。&&左右的表达式都必须
为“真” ,否则程序执行 else if 语句。
3 如上面的结果为“假”则测试此行,若为“真”则输出“You got a B”。
4 若第二、第三行都为“假” ,则测试此行。若为“真”则输出“You’re average.”。
5 如上述的语句都为“假” ,则执行 else 板块。
6 用 endif 语句结束整个 if 结构。

11.6.7 退出状态与状态变量
所有的 Linux 最后都返回到退出状态。若命令执行,则返回到 0(退出)状态。若命令
没有顺利执行,则返回到非 0(退出)状态。你可用 TC Shell 的 status 或者?变量来测试程
序是否顺利进行。状态变量表达的是程序中最后一个命令的执行状况。

实例 11.19
1 > grep ellie /etc/passwd
ellie:pHAZk66gA:9496:41:Ellie:/home/jody/ellie:/bin/csh
2 > echo $status or $?
0 Zero shows that grep was a success

3 > grep joe /etc/passwd


4 > echo $status
1 Nonzero shows that grep failed
454 第 11 章

说明
1 grep 找到在/etc/passwd 路径下的模式 ellie。
2 若找到模式 ellie,程序 grep 则返回到 0 状态。
3 在/etc/passwd 下,程序 grep 没有发现模式 joe。
4 若此模式没有被发现则返回非 0 状态。

11.6.8 退出脚本
exit 命令使脚本程序退回到提示符状态下,它用整数值表示退出的状态。非 0 参数表明
执行失败,而 0 表明执行成功,且此值在 0~255 之间。

实例 11.20

(The checkon Shell Script)


#!/bin/tcsh –f
1 if ( $#argv != 1 ) then
2 echo "$0 requires an argument"
3 exit 2
4 endif

(At the command line)


5 > checkon
checkon requires an argument
6 > echo $status
2

说明
1 若从命令行($#argv)输入的参数值不等于 1,则执行第二行。
2 用 echo 命令打印出脚本名($0)和字符串“require an argument”。
3 程序退出到提示符状态,值为 2。此值将会赋值给母程序的 status 变量。
4 if 引导的程序退出。
5 在此命令行,在无参数的情况下执行 checkon 程序
6 程序退出,其值为 2。此值将赋值给 status 变量。

11.6.9 在脚本中使用 status 变量


在脚本中,status 变量用于测试命令的状态。最后一个命令执行后的输出状态值会赋给
status 变量。

实例 11.21

(The Script)
#!/bin/tcsh –f
1 ypmatch $1 passwd >& /dev/null
2 if ( $status == 0 ) then
3 echo Found $1 in the NIS database
endif
用 TC Shell 编程 455

说明
1 ypmatch 程序检查 NIS 数据库的用户名是否在数据库里。此用户名是作为第一个变量导入的。
2 若最后的命令执行后返回给 status 是 0,则执行 then 板块。
3 若 if 测试表达式值为真则执行此行。

11.6.10 在条件语句中求命令的值
TC Shell 在有条件的情况下求表达式的的值。在条件语句中求命令的值,命令都必须用
圆括号括起。如命令成功执行,则返回退出状态,其值为 0。此时,圆括号指示 Shell 赋值
给该表达式为“真” (1)2。若命令失败,则退出状态值为非 0。同时表达式赋值为“假”(0)

在条件语句中使用一个命令,知道它的状态是很重要的。比如说,当 grep 程序发现它
所需的模式,就以 0 值退出状态返回,否则就返回 1。如果是没发现这个文件的话就返回 2。
如果是用 awk 或者 sed 来查找模式,则程序都会返回一个 0 值无论程序是否执行成功。awk
和 sed 判断程序是否执行成功,是以语法的对错来作为标准的,也就是说,只要你输入的命
令正确,那么 awk 和 sed 就会返回一个 0 值。
假如在表达式前放置一个! (惊叹号),则否定整句表达式。也就是说,若原为“真” ,
加了!之后就变成“假” 。其他情况也由此类推。注意,在!之后必须有一个空格,否则 Shell
就会激活历史机制。

格式
if { { command } } then
command
command
endif

实例 11.22
#!/bin/tcsh –f
1 if { ( who | grep $1 >& /dev/null ) } then
2 echo $1 is logged on and running:
3 ps au | grep "^ *$1" # ps –ef for SVR4
4 endif

说明
1 who 命令输送给 grep 命令。所有的输出都输送到/dev/null 即 Linux 的“bit bucket”(字节储存桶)。
who 命令的结果输送给 grep,grep 查找储存在变量$1(第一个命令行参数)的用户名。若 grep
成功的找到该用户,则返回退出状态,其值为 0。然后 Shell 会把 grep 命令的退出状态的值转化
为 1 或者“真” 。如 Shell 求得表达式的值为“真”,则执行在 then 和 endif 之间的命令。
2 如 TC Shell 求得第一行表达式的值为真,则执行第二、第三行。
3 显示$1 所代表的所有进行中的进程。
4 endif 结束 if 语句。

格式
if ! { (command) } then

2.命令退出状态值与 Shell 表达式的真假正好相反。


456 第 11 章

实例 11.23

1 if ! { ( ypmatch $user passwd >& /dev/null ) } then


2 echo $user is not a user here.
exit 1
3 endif

说明
1 若是在网络上,ypmatch 命令用于查找 NIS passwd 文件。若该命令在 passwd 文件中成功找到用
户($user),表达式值就为“真”。若在表达式前加了!就否定/求反整个表达式,则原为“真”
的话就变做“假” ,以此类推。
2 若用户没找到则表达式值为“假” ,执行该行。
3 endif 语句结束此 if 板块。

11.6.11 goto 语句
goto 能使程序跳到某个标签处,并以此点为起点开始执行程序。尽管 goto 常令程序员
头疼不已,但它在中断嵌套循环方面还是十分得力的。所谓标签,就是后面带一个冒号的、
由用户自己定义的词。标签一般独占一行。

实例 11.24

(The Script)
#!/bin/tcsh –f
# Scriptname: grades2
1 startover:
2 echo -n "What was your grade? "

set grade = $<


3 if ( "$grade" < 0 || "$grade" > 100 ) then
4 echo "Illegal grade"
5 goto startover
endif
if ( $grade >= 89 ) then
echo "A for the genius\!"
else if ($grade >= 79 ) then
.. < Program continues >

说明
1 标签是后面带一个冒号的,且由用户自己定义的词。此处的标签叫作 startover。在程序执行的过
程中,Shell 往往忽略标签,除非在程序里清楚地指出需要跳到标签处。
2 要求用户输入。
3 表达式若为真,则当用户输入一个小于 0 或大于 100 的值(考试分数)时,则出现字符串“Illegal
grade”,然后 goto 使程序跳到已命名标签 startover 处继续执行。
4 用 if 语句得到“假”值,则显示此行。
5 goto 使得程序跳到标签 startover 处,并以该点为始继续执行。

11.6.12 测试文件
TC Shell 有一整套选项来测试文件的属性。比如说,
“Is the file a directory, a plain file (not
用 TC Shell 编程 457

a directory), or a readable file,”(这文件是一个目录、一个无格式文件,还是一个可读文件


呢?) 。在表 11.7 中列出的运算符根据实用户测试文件或目录的属性,结果为真则返回 1 值,
结果为假则返回 0 值。文件查询的内置选项均在表 11.7 中列出了。TC Shell 允许选项的叠加,
而在 C Shell 中是不允许的。如:-rwx 与–r && -w && -x 等同。

表 11.7 测试文件

测试标记 结果若为“真”,则……
-b 此文件是字块特殊文件
-c 此文件是字符特殊文件
-d 此文件是目录
-e 文件存在
-f 此为无格式文件
-g 设置主 ID 位
-k 设置粘贴位
-l 符号链接
-L 从运算符列表里选取后来的运算符,并应用于符号链接,而不应用于该符号链接所链
接的文件
-o 当前用户为此文件属主
-p 文件指派为 pipe(fifo)
-r 当前用户可读此文件
-s 非空文件
-S 插口专门文件
-t file 对设备开放的文件描述符,此文件必须为一个数字
-w 当前用户可写此文件
-x 当前用户可执行此文件
-z 空文件
-L 从运算符列表里选取后来的运算符,并应用于符号链接,而不应用于该符号链接所链
接的文件
-R 已经被移用(仅用于凸检验)
-S 插口专门文件

实例 11.25

#!/bin/tcsh –f
# Scriptname: filetest1
1 if ( -e file ) then
echo file exits
endif
2 if ( -d file ) then
echo file is a directory
endif
3 if ( ! –z file ) then
echo file is not of zero length
endif
458 第 11 章

4 if ( -r file && -w file && -x file) then


echo file is readable and writable and excutable.
endif
5 if ( -rwx file ) then
echo file is readable and writable and executable.
endif

说明
1 读入语句 if the file exists, then…。
2 读入语句 if the file is a directory, then..。
3 读入语句 if the file is not of zero length, then..。
4 读入语句 if the file is readable and writeable, then…文件名前可放一个单选项。如:-r file && -w file
&& -x file。
5 文件测试标记可叠加,如:-rwx 文件名。此为 tcsh 的新功能。

实例 11.26

#!/bin/tcsh –f
# Scriptname: filetest2
1 foreach file (‘ls‘)
2 if ( -rwf $file ) then
3 echo "${file}: readable/writeable/plain file"
endif
end

(Output)
3 complete: readable/writeable/plain file
dirstack: readable/writeable/plain file
file.sc: readable/writeable/plain file
filetest: readable/writeable/plain file
glob: readable/writeable/plain file
modifiers: readable/writeable/plain file
env: readable/writeable/plain file

说明
1 在由 Linux ls 程序生成的文件列表中反复执行 foreach 循环,将文件列表里的每一个文件都赋值
给变量 file。
2 如此文件为可读可写的无格式文件,则执行第三行。叠加选项在 tcsh 中合法而 csh 而不合法。
3 若此文件名检验确定为可读、可写、可执行,则执行该行。

内置命令 filetest。tcsh 的内置命令 filetest 将一个文件查询操作符应用于一个文件或文


件列表,然后返回一个或一组用空格分隔的数值:其中 1 表示真,0 表示假。

实例 11.27

1 > filetest –rwf dirstack file.sc xxx


1 1 0
用 TC Shell 编程 459

2 > filetest –b hdd


1

3 > filetest –lrx /dev/fd


1

说明
1 测试所有的文件看是否为可读可写的无格式文件。开头的两个文件返回“真”值(1 1) ,而最后
的文件返回“假”值(0) 。
2 若文件 hdd 为块专门文件则 filetest 命令返回 1 值,反之则返回 0 值。
3 若被测文件 fd 是可读可执行的符号连接,则 filetest 返回 1 值,否则返回 0 值。

tcsh 附加的文件测试操作符。tcsh 还独有一套附加的文件测试操作符,可返回关于文件


的信息。由于返回的值没有所谓的“真”和“假” ,所以 –l 就表明失败(除了 F,用这个选
项的话就会返回一个“: ”)。
表 11.8 附加的文件测试操作符

-A 最后一个文件的存档时间
-A: 等同-A,但在时间格式里使用,如:Fri. Aug. 27 16:36:10 1999
-M 最后一个文件的修改时间
-M: 等同-M,但在时间格式里使用
-C 最后一个索引节修改时间
-C: 等同 C,但在时间信息格式中使用
-F 在格式 device:inode 中的复合文件标识符
-G 属组 id 号
-G: 如属组名未知,则为属组名或属组 id 号
-L 符号链接所指向的文件名
-N 硬链接的个数
-P 八进制权限,结果前不带 0
-P: 等同–P,但结果前带一个 0
-Pmode 等同-P file & mode,如:-P22 文件名,若该文件能为一个或多个组所写,则返回值
22;若只能为一个组所写,则返值 20;0 值则表示不能写入
-Mode: 等同 –Pmode:,但结果前带一个 0
-U 用户 id 号
-U: 用户名,如用户名未知则为用户 id 号
-Z 用 bit 计量的文件大小

实例 11.28

1 > date
Wed Jan 12 13:36:11 PST 2000

2 > filetest –A myfile


460 第 11 章

934407771

3 > filetest –A:myfile


Wed Jan 12 14:42:51 2000

4 > filetest –U myfile


501

5 > filetest –P: myfile


0600

> filetest –P myfile


600

说明
1 显示当前日期。
2 当 myfile 最后存档的时候,带-A:选项的 filetest 内置命令用纪元格式输出日期。
3 带-A:选项的 filetest 内置命令用时间格式输出日期。
4 带-U 选项的 filetest 内置命令输出 myfile 属主的 id 号。
5 带-P:选项的 filetest 内置命令输出八进制权限的状态值,结果前带一个 0。若没有“:”的话则
输出结果前不带 0。

11.6.13 嵌套条件
条件语句是可以嵌套的。每个 if 都必须有一个 endif 与之对应(而 else if 不需要 endif
与之对应)。通过使嵌套的语句缩进,而让 if 和 endif 齐头排列是个很好的方法,这样可以更
高效地读取和测试程序。

实例 11.29

(The Script)
#!/bin/tcsh –f
# Scriptname: filecheck
# Usage: filecheck filename
1 alias Usage 'echo " Usage: $0 filename\!*" ; exit 1'
2 alias Error 'echo " Error: \!* "; exit 2'
3 set file=$1
4 if ( $#argv == 0 ) then
Usage
endif
5 if ( ! –e $file )then
Error "$file does not exist"
endif
6 if ( -d $file ) then
echo "$file is a directory"
7 else if (-f $file) then
8 if ( -rx $file ) then # nested if construct
echo "You have read and execute permission on $file"
9 endif
else
print "$file is neither a plain file nor a directory. "
10 endif

(The Command Line)


用 TC Shell 编程 461

$ filecheck grade
You have read and execute permission of file testing.

说明
1 别名 Usage 能引起一个错误信息并退出程序。
2 调用别名 Error 时将显示出错信息,后面跟着程序所传递的任何参数。
3 把变量 file 赋值给命令行输送过来的第一个参数,$1。
4 若所输送的参数个数为 0,则没有参数输送的话,则别名 Usage 将输出它的信息。
5 变量替换后若 file 是个不存在的文件(请注意否定操作符!) ,别名 Error 则在关键字 then 后输出
它的信息。
6 若 file 是个目录,则显示“testing is a directory”。
7 若 file 不是一个目录,而(else if)是个无格式文件,就执行 then 后的语句(另一个 if 语句) 。
8 该 if 是嵌套在前一个 if 里面的。若 file 是可读可执行的,则(then)…该 if 有 endif 与之匹配。
此 endif 齐头排列表明了它属于的哪一个 if。
9 endif 结束内嵌的 if 结构。
10 endif 结束外嵌的 if 结构

11.6.14 switch 命令
switch 命令是 if-then-else if 结构的简化,它有时候能使程序更清楚,特别是在有多个选
项的时候。switch 表达式里的值与所谓“labels(书签) ”表达式相匹配,而书签表达式是跟
在关键字 case 之后的。case 书签支持常数表达式和通配符并以“: ”结束。default 标签是个
可选项,若没有其他的 case 匹配 switch 表达式就执行 default 的操作。breaksw 是用于传输
执行给 endsw 的。如省略 breaksw,并且有一个书签被匹配就执行被匹配的书签下的所有语
句。直到出现 breaksw 或 endsw 中一个。

格式

switch (variable)
case constant:
commands
breaksw
case constant:
commands
breaksw
endsw

实例 11.30

(The Script – colors)


#!/bin/tcsh –f
# This script is called colors
1 echo –n "Which color do you like? "
2 set color = $<
3 switch ("$color")
4 case bl*:
echo I feel $color
echo The sky is $color
5 breaksw
6 case red: # Is is red or is it yellow?
7 case yellow:
462 第 11 章

8 echo The sun is sometimes $color.


9 breaksw
10 default:
11 echo $color not one of the categories.
12 breaksw
13 endsw

(The Output)
1 Which color do you like? red
8 The sun is sometimes red.
1 Which color do you like? Doesn't matter
11 Doesn't matter is not one of the categories.

说明
1 要求用户输入。
2 输入信息赋值给 color 变量。
3 switch 语句求变量的值。将变量括在双引号里是因为用户有可能输入一个以上的词。switch 对单
个字符求值,若字符串被括在双引号里的话就对字符串求值。
4 case 书签是 bl,表明 switch 表达式将回匹配任何以 bl 开头的字符。若用户输入如 blue、black、
blah、blast 等,就执行在该 case 书签下的命令。
5 breadsw 传输程序控制于 endsw 语句。
6 若 switch 语句匹配书签 red,就执行该语句直到出现第 9 行的 breaksw。执行第 8 行。显示“The
sun sometimes is red”。
7 若第 4 行没有可匹配的,则测试 cases“red”和“yellow” 。
8 若 red 和 yellow 中的任一个可匹配,则执行该行。
9 breadsw 传输程序控制给 endsw 语句。
10 若没有任何一个 case 书签匹配 switch 表达式,则进行 default 书签后面的语句。
这样的状况和 if/else
if/else 结构是一样的。
11 若用户输入的信息不和任何一个上述 cases 匹配的话,则显示此行。
12 此 breaksw 是任选项,因为这个 switch 命令将在这里结束。不过,建议还是在这里加个 breaksw
为好,因为以后假如更多的 case 被加近来的话,它的重要性不容忽略。
13 endsw 终结 switch 语句。

嵌套 switch 命令:switch 是可以嵌套的,也就是说,一个 switch 命令和它的 cases 可以


包含其他的 switch 语句,使它们做为自身的一个 case。必须用 endsw 去结束每一个 switch
语句,而 default 的 case 却不是必需的。

实例 11.31

(The Script – systype)


#!/bin/tcsh –f
# This script is called systype
# Program to determine the type of system you are on.
#
echo "Your system type is: "
1 set release = ( ‘uname –r‘)
2 switch (‘uname -s‘)
3 case SunOS:
4 switch ("$release")
5 case 4.*:
echo "SunOS $release"
breaksw
6 case [5-8].*:
用 TC Shell 编程 463

echo "Solaris $release"


breaksw
7 endsw
bradksw
case HP*:
echo HP-UX
breaksw

case Linux:
echo Linux
breaksw
8 endsw

(The Command Line)


> systype
Your system type:
SunOS 5.5.1

说明
1 将 uname -r 的输出结果赋给变量 release,得到的数字是操作系统的版本信息。
2 switch 命令求 uname –s 输出结果的值,也就是操作系统名。
3 若系统类型是 SunOS,则执行第 3 行的 case 命令。
4 每一个 case 的变量 release 的值都被求出、匹配。
5 测试所有的 release 的值为 4 的 case。
6 测试所有的 release 的值为 5~8 的 case。
7 终止内嵌 switch 语句
8 终止外嵌 switch 语句。

11.6.15 here 文档与菜单


在 Shell 脚本里,here 文档用于生成菜单。它经常与 switch 语句连用。当用户在菜单中
选择一个选项后,其选择即与 switch 语句中的某个 case 相匹配。here 文件通过减少 echo 语
句的数量而使得程序更清晰易懂。

实例 11.32
#! /bin/tcsh
1 echo "Select from the following menu:"
2 cat << EOF
1) Red
2) Green
3) Blue
4) Exit
3 EOF
4 set choice = $<
5 switch ("choice")
case 1:
echo Red is stop.
breaksw
case 2:
echo Green is go!
breaksw
case 3:
echo Blue is a feeling...
464 第 11 章

breaksw
case 4:
exit
breaksw
default:
echo Not a choice\!\!
endsw
echo Good-bye

(The Output)
Select form the following menu:
1) Red
2) Green
3) Blue
4) Exit
2
Green is a go!
Good-bye

说明
1 要求用户选择菜单选项。
2 here 文档以此为始。EOF 是用户定义的终结符。从第一个 EOF 后的文本都作为引用字块输送给
cat 程序,直到遇到一个顶行写的 EOF。
3 该 EOF 终止 here 文件。
4 用户从菜单选项中选择 1、2、3 或 4。
5 switch 语句用语求用户所选项的值。

11.7 循 环
循环结构能使用户重复执行同一个语句。C Shell 支持两种类型的循环:foreach 循环和
while 循环。当需要执行在一个选项列表里的命令时就用 foreach 循环,每个选项执行一次。
譬如说,文件列表或者用户名列表。而当你想一直执行某个命令直到遇到某种结束提示或情
况时就用 while 循环。

11.7.1 foreach 循环
foreach 命令后面跟着变量或括在圆括号里的单词列表。一旦进入该循环,就将此列表
的第一个词赋值给变量。列表中的第一个词往左移至顶格,同时程序进入循环体。随后,程
序再次指向循环的顶端。列表上的第二个词赋值给变量,执行 foreach 后的命令直到到达 end
处。然后再回到 foreach 循环的顶端,找到下一个词,如此反复循环。当执行完了整个列表
后,循环结束。

格式
foreach variable (wordlist)
commands
end
用 TC Shell 编程 465

实例 11.33

1 foreach person (bob sam sue fred)


2 mail $person < letter
3 end

说明
1 foreach 命令后跟着一个变量 person, 圆括号里括着单词列表。变量 person 将被赋值 bob 作为首次
循环。一旦 bob 被赋值给变量 person 之后,bob 就会左移至顶格,而 sam 就成了列表的第一个词。
当程序达到 end 语句时,循环就又会从顶端开始,此时,sam 就赋值给变量 person。这样的过程
一直持续到最后一个 fred 才告结束。此时列表已经到尽头而程序也就终结了。
2 第一次循环运行的结果是:文件 letter 的内容将会寄给用户 bob。
3 当达到 end 语句的时候,循环控制就回到 foreach 处,然后(列表中)第二个项就被赋值给变量
person。

实例 11.34

(The Command Line)


> cat maillist
tom
dick
harry
dan

(The Script – mailtomaillist)


#!/bin/tcsh –f
# This script is called mailtomaillist
1 foreach person ( ‘cat maillist‘)
# Here Document follows
2 mail $person <<EOF
Hi $person,
How are you? I've missed you. Come on over
to my place.
Your pal,
$LOGNAME@‘uname -n‘
EOF

3 end

说明
1 命令替换,此处需要用到圆括号来实现。文件 maillist 的内容成为列表。列表中的每个名字(tom、
dick、harry、dan)按顺序一一赋值给变量 person。循环语句执行到 end 后程序控制就回到 foreach
重新执行,列表中的最先的名字被赋值于变量 person,然后其后的名字,再赋值,再执行。列表
一个名字一个名字的减少,直到列表中所有的名字都全部替换掉。
2 此处使用 here 文档。从第一个 EOF 开始把输入信息输送到 mail 程序,直到遇到最后一个 EOF
结束。 (注意:最后一个 EOF 必须得左顶格,而且前后都不能有空格。 )mail 会寄给列表中的每
一个人。
3 foreach 循环中的 end 语句标志着在该循环其中的一个循环体的终结。程序控制再次指向循环顶端。

实例 11.35

1 foreach file (*.c)


466 第 11 章

2 cc $file –o $file:r
end

说明
1 foreach 命令的单词列表是一个在当前目录下的以.c 结尾的文件列表。(即是所有的 C 源文件)。
2 编辑列表中的所有文件。例如,若执行的第一个文件是一个 C 程序,Shell 就会将 cc 命令行扩展
至:
cc program.c –o program
而:r 则会消除.c 的扩展。

实例 11.36

(The Command Line)


1 > runit f1 f2 f3 dir2 dir3

(The Script)
#!/bin/tcsh –f
# This script is called runit.
# It loops through a list of files passed as
# arguments

2 foreach arg ($*)


3 if ( -e $arg ) then
... Program code continues here

else
... Program code continues here
endif
4 end
5 echo "Program continues here"

说明
1 脚本名为 runit,命令行参数为 f1、f2、f3、dir2 和 dir3。
2 $*变量赋值给一个列表中的所有的参数(均为位置参数) ,这些参数是从命令行传送过来的。单
词表中的各个词,f1、f2、f3、dir2 和 dir3,按顺序一一执行 foreach 命令。循环中的每一次程序
的执行,单词表中的第一个词都会赋值于变量 arg。当一个词被赋值出去后,单词表就会去掉最
左边的单词,减少了单词的单词表会整体左移,最左边的那一个单词那一个词就会再赋值给 arg,
直到单词表已无词可赋值为止。
3 每遇到列表中一个项,此块中的命令都会执行一次,直到达到 end 语句为止。
4 当单词列表空了的时候,end 语句终结此次循环。
5 当循环结束,程序继续。

11.7.2 while 循环
while 循环用于求一个表达式的值,主要表达式为真(非 0) ,就执行 while 语句下的所
有命令直到到达 end 语句。 然后控制就会回到 while 表达式处,对表达式求值, 如果仍为 “真”,
则再次执行 while 下的命令……如此类推。当 while 表达式值为“假”时,循环终结,程序
控制就开始执行 end 语句后的程序。
用 TC Shell 编程 467

实例 11.37
(The Script)
#!/bin/tcsh –f
1 set num = 0
2 while ($num < 10>
3 echo $num
4 @ num++ (See arithmetic)
5 end
6 echo "Program continues here"

说明
1 设置变量 num 始值为 0。
2 进入 while 循环并测试表达式。如果 num 的值小于 10,则表达式为真,执行第 3 行和第 4 行。
3 在每次循环时,都显示 num 的值。
4 变量 num 的值增大了。如果省略该语句的话,循环就会一直执行下去。
5 end 语句终结此块可执行语句。一旦达到该行,控制就会重新回到 while 循环的顶端,同时表达
式重新被赋值。这个过程一直继续到 while 表达式出现“假”为止。
6 循环终止后,程序从此处继续执行。

实例 11.38

(The Script)
#!/bin/tcsh –f
1 echo –n "Who wrote \"War and Peace\"?"
2 set answer = "$<"
3 while ( "$answer" != "Tolstoy" )
echo "Wrong, try again\!"
4 set answer = "$<"
5 end
6 echo Yeah!

说明
1 要求用户输入。
2 无论用户输入什么都会原封不动地赋值给变量 answer。
3 while 命令求表达式的值。若果$answer 的值不和字符串“Tolstoy”完全一致的话,即显示“Wrong,
try again!”,此时程序暂停,等待用户的输入。
4 变量 answer 被赋予新的输入信息。该行是十分重要的,如果变量 answer 的值始终不变,则循环
表达式就永远不能为“假” ,那么循环就会无限地循环下去。
5 end 语句终止 while 循环中的程序块。
6 若果用户输入“Tolstoy”, 则循环表达式测试为“假” ,控制就指向此行并显示“Yeah! ”

11.7.3 repeat 命令
repeat 命令有两个参数,一个数字和一个命令。该命令重复执行次数即是参数中数字的
值。

实例 11.39

> repeat 3 echo hello


468 第 11 章

hello
hello
hello

说明
echo 执行 3 次。

11.7.4 在循环语句中使用的循环命令
shift 命令。shift 命令并不需要数组名作为它的参数。它在 argv 参数组中从左到右一个
个把参数转换掉,使参数数组中的参数逐渐减少。一旦全部转换完,参数组这个元素就不复
存在了。

实例 11.40

(The Script)
#!/bin/tcsh –f
# Script is called loop.args
1 while ($#argv)
2 echo $argv
3 shift
4 end

(The Command Line)


5 > loop.args a b c d e
a b c d e
b c d e
c d e
d e
e

说明
1 $#argv 求命令行参数个数的值。假如有 5 个命令行参数,a、b、c、d 和 e,第一次循环中$#argv
的值为 5。表达式被测试并得到 5,真。
2 输出命令行参数。
3 argv 参数组中的一个参数左移。程序从只剩下 4 个参数中的 b 开始。
4 到达循环的终结处时,控制回到循环的顶端。重新求表达式的值。此时,$#argv 为 4。输出这些
参数,同时参数组再次左移,一直到所有的参数都左移完为止。这个时候如果再求表达式的值,
得到 0,也就是为“假” ,循环退出。
5 参数 a、b、c、d、e 从 argv 参数组输送给脚本程序。

break 命令。break 命令用于退出一个循环,然后程序控制执行 end 语句后的程序。它终


止最内部的一个循环,但仍然继续执行 end 语句之后的程序。如果在一行中同时列出有多重
break,则很有可能是用来退出多重循环的。

实例 11.41

#!/bin/tcsh –f
# This script is called baseball
1 echo –n "What baseball hero died in August, 1995? "
用 TC Shell 编程 469

2 set answer = "$<"


3 while ("$answer" !~ [Mm]*)
4 echo "Wrong\! Try again."
set answer = "$<"
5 if ( "$answer" =~ [Mm]* ) break
6 end
7 echo "You are a scholar."

说明
1 要求用户输入。
2 用户的输入信息直接赋给变量 answer(答案:Mickey Mantle) 。
3 while 表达式读入的是:While the value of answer does not begin with a big M or little m, followed by
zero or more of any character, enter the loop(假如答案不是 M 或者 m 开头,则进入循环) 。
4 用户再次输入。变量重新设置。
5 假如变量的答案与 M 或 m 相匹配,则终止循环。到达 end 语句,并以此为始执行它下面,即第
7 行的语句。
6 end 语句在循环执行完后终结此块语句。
7 退出循环后,执行此行,则控制从此开始。

实例 11.42

#!/bin/tcsh –f
# This script is called database
1 while (1)
echo "Select a menu item"
2 cat << EOF
1) Append
2) Delete
3) Update
4) Exit
EOF
3 set choice = "$<"
4 switch ($choice)
case 1:
echo "Appending"
5 break # Break out of loop; not a breaksw
case 2:
echo "Deleting"
break
case 3:
echo "Updating"
break
case 4:
exit 0
default:
6 echo "Invalid choice. Try again."
endsw
7 end
8 echo "Program continues here"

说明
1 这是个无限循环。表达式的值永远是“真” 。
2 这是个 here 文档,屏幕此时显示一个菜单。
3 用户从菜单选择。
470 第 11 章

4 switch 命令求变量的值。
5 如果用户选择了一个有效选项。即 1~4,就执行此书签所匹配的命令。break 语句使得循环终止
并从第 8 行重新开始。不要把 break 语句和 breaksw 语句相混淆,breaksw 仅在遇到 endsw 语句
的时候才终止。
6 如果 default case 可匹配,则表明没有一个 case 可匹配。程序控制到达循环的末端 end 后再回到
循环的顶端处重新开始执行循环。由于在 while 后的表达式值总为“真” ,则一次次的不断进入
循环,一次次的显示菜单。
7 循环的终结处 end。
8 退出循环后,执行此行。

多重嵌套循环和 repeat 命令。比起 goto 命令,repeat 命令能够终止多重循环。但若将


repeat 命令和 continue 命令一起使用的话就失去了这个功能。
实例 11.43
(Simple Script)
#!/bin/tcsh –f
# Script name: looper
1 while (1)
echo "Hello, in lst loop"
2 while (1)
echo "In 2nd loop"
3 while (1)
echo "In 3rd loop"
4 repeat 3 break
end
end
end
5 echo "Out of all loops"

(The Output)
Hello, in lst loop
In 2nd loop
In 3rd loop
Out of all loops

说明
1 开始执行第一重 while 循环。
2 进入第二重 while 循环。
3 进入第三重 while 循环。
4 用 repeat 命令连续执行 break 三次。第一次终止最里面的一个循环,然后是外面的那重循环,最
后是最外围的循环。接着,控制从第五行开始。
5 循环终止后,程序控制从此开始。

continue 命令。continue 语句使得程序从最里层循环的顶端开始执行。


实例 11.44

1 set done = 0
2 while ( ! $done )
echo "Are you finished yet?"
set answer = "$<"
3 if ("$answer" =~ [Nn]*) continue
4 set done = 1
5 end
用 TC Shell 编程 471

说明
1 将变量 done 赋值为 0。
2 测试表达式。读入的是:“while (!0)”
。Not 0 被认为是“真”(本地 NOT)。
3 如果用户输入的是 No、no 或 nope(以 N 或 n 开头的任何字词) ,表达式为真。continue 语句的
使得程序控制回到循环的顶端,重新求表达式的值。
4 如果输入的答案并不是以 N 或者 n 开头,则变量 done 的值为 1。当到达循环的底部时,控制又
回到循环的顶端,测试表达式并重新开始。读入:“while (! 1)” 。非 1 的时候则为“假” ,退出循
环。
5 end 标志着 while 循环的终结。

实例 11.45

(The Script)
#!/bin/tcsh –f
1 if ( ! –e memo ) then
echo "memo file non existent"
exit 1
endif
2 foreach person ( anish bob don kar1 jaye)
3 if ("$person" =~ [Kk]arl) continue
4 mail –s "Party time" $person < memo
end

说明
1 检查文件。如果 memo 文件不存在,就把错误信息发送给用户,同时退出程序,其退出状态值为
1。
2 循环将列表中的每一个人名一个个地赋值给变量 person。然后再把人名按顺序逐个从列表中删
除。
3 如果有人名为 Karl 或者 karl,continue 语句就从 foreach 循环顶端开始(Karl 没有收到 meno,因
为该名字赋值出去后已经从列表中删除) 。接着将下一个名字赋值给 person。
4 除了 karl 之外,列表中的所有人都会收到一个称之 memo 的文件。

11.8 中断处理/操作
用中断操作键来中断一个脚本程序,此时脚本终结,程序控制返回到 TC Shell,也就是
说,你能重新看见提示符。onintr 命令用于脚本中做处理中断,它能够在退出之前略过对程
序的另一部分的中断或转移操作。一般而言,interrupt 命令和书签合用,在程序退出之前进
行“清屏”。不带参数的 onintr 命令则储存错误(default)操作。

实例 11.46

(The Script)
1 onintr finish
2 < Script continues here >
3 finish:
4 onintr - # Disable further interrupts
5 echo Cleaning temp files
6 rm $$tmp* ; exit 1
472 第 11 章

说明
1 onintr 命令后跟着一个书签名。finish 书签是用户定义的。当程序中断时控制即转换到 finish 书签。
通常而言此行都放在脚本的开头,脚本一开始运行它就发挥作用。
2 程序执行间,只要按下^C(中断键),即可执行脚本的其他部分。同时,程序控制转换到书签处。
3 这是个书签。当中断时,程序继续执行书签下的语句。
4 用 onintr 防止此部分的脚本被终止。如果此时键入 Ctrl-c 的话,此处就被忽略。
5 显示此行。
6 删除所有的 tmp 文件。tmp 文件前有前缀 Shell PID 数字($$)
,后面可加任何字符。程序退出,
状态值为 1。

11.9 setuid 脚本
暂时运行 setuid 程序的用户,被认为是该程序的属主, 同时他/她拥有和属主一样的权限。
passwd 程序是 setuid 程序的一个例子。当用户在运行 setuid(且仅在运行 setuid 之时)时改
变密码,用户即可暂时成为超级用户。这就是为什么你可以修改/etc/passwd(或/etc/shadow)
文件密码的原因,而普通用户是没有修改此文件的权限的。
Shell 程序可以是 setuid 程序。也许你正需要一个脚本访问一个文件,该文件内含其他
普通用户不能访问的信息(如工资和人事资料) ,假如该脚本是一个 setuid 脚本,那么使用
该程序的用户就可以访问、修改数据了,而普通用户还是不能访问这些数据的。setuid 程序
包括以下内容:
1.脚本中的首行应该是:
#!/bin/tcsh –feb

The –feb options:


-f fast start up; don't execute .cshrc
-e abort immediately if interrupted
-b this is a setuid script

2.修改脚本权限使 setuid 程序得以运行。


> chmod 4755 script_name
or
> chmod +srx script_name
> ls -1
-rwsr-xr-x 2 ellie 512 Oct 10 17:18 script_name

11.10 储 存 脚 本
创建好(数个)脚本后,一般都把它们放到一个脚本目录里,然后修改路径使得可以从
任何位置执行这些脚本。
用 TC Shell 编程 473

实例 11.47

1 > mkdir ~/bin


2 > mv myscript ~/bin
3 > vi .login

In .login reset the path to add ~/bin.


4 set path = ( /usr/ucb /usr /usr/etc ~/bin . )

5 (At command line)


> source .login

说明
1 在根目录下创建一个名为 bin 的新目录,当然,也可以用其他的名字命名。
2 把基本没有 bug 的脚本放到 bin 目录里。如果此处所放的程序 bug 太多会产生麻烦。
3 进入.login 文件重新设置路径。
4 新路径包含了目录 ~/bin,shell 在该目录里寻找可执行程序。由于该目录在路径列表的末端,因
此 shell 会先找到同名的系统文件并执行它。
5 从.login 采集源数据后,路径的修改就会受到影响。不必重新注册进入。

11.11 内 置 命 令
不像 Linux 的可执行命令文件那样贮存在硬盘里,内置命令是 TC Shell 的一部分内码,
而且在 Shell 内执行。如果一个内置命令作为一条执行语句流水线的一部分(不能是流水线
的末端部分) ,则该内置命令是在子 Shell 中执行的。tcsh 命令被适时地激活,builtins 命令列
出全部内置命令(见下面的例子 11.48)。每个内置命令的具体阐述见表 10.24。

实例 11.48

1 > builtins
: @ alias alloc bg bindkey break
breaksw builtins case cd chdir complete continue
default dirs echo echotc else end endif
endsw eval exec exit fg filetest foreach
glob goto hashstat history hup if jobs
kill limit log login logout ls-F nice
nohup notify onintr popd printenv pushd rehash
repeat sched set setenv settc setty shift
source stop suspend switch telltc time umask
unalias uncomplete unhash unlimit unset unsetenv wait
where which while

11.11.1 Shell 命令的行开关


TC Shell 能通过一些命令行开关(又称为标志参数)
,来控制和修改它本身的行为。命
令行开关列表见表 11.9。
474 第 11 章

表 11.9 Shell 的命令行开关

- 设置该 Shell 为注册 Shell


-b 使得选项终止运行。从此开始所有的 Shell 参数都不再被认为是选项。假如 Shell 被设
置为用户 id 的话就必须有此选项
-c 如果-c 后跟着的是单个参数,就从参数开始读入命令。而将剩下的参数赋给 Shell 的
argv 变量
-d Shell 从~/.cshdirs 处加载目录栈
-Dname[=value] 求环境变量名的值
-e 当调用的命令非正常退出或得到一个非 0 的退出状态值时,Shell 退出
-f 当开始一个新的 TC Shell 时,使 Shell 忽略~/.tcshrc,叫作快捷启动
-F Shell 使用 fork(2)而不使用 vfork(2)来产生进程。(只能用于 Convex/OS)
-i Shell 是交互及由光标输入的,它甚至不是一个终端。如果输入和输出都是连接到一个
终端的话,则该选项就不是必须的
-l 假如-l 是设置的惟一标志,Shell 就是个注册 Shell
-m 即使~/.tcshrc 不属于有效用户,Shell 还是加载~/tcshrc
-n 用于调试脚本。Shell 解析命令但并不执行它们
-q 当 Shell 在调试器下运行的时候,它接受 SIGQUIT 标记并对标记做出反应。程序控制
在此时失效
-s 命令输入由标准输入得到
-t Shell 读入并执行一个单行的输入。反斜杠(\)用于跳过该行末端的换行符,并继续
执行其他的程序行
-v 用于测试 Shell 脚本。设置 Shell 的 verbose 变量,使得命令输入可以在历史替换之后
显示出来
-x 用于测试 Shell 脚本。设置 Shell 的 echo 变量,使得命令行能在执行之前,历史和变
量替换之后显示出来
-V 在执行 ~ /.tcshrc 文件之前,设置 Shell 的 verbose 变量
-X 在执行~ /.tcshrc 文件之前,设置 Shell 的 echo 变量

TC SHELL LAB 练习:

练习 1——第一个脚本程序

1.写一个名为 greetme 的脚本,使其能够:


a.欢迎用户
b.打印出日期和时间
c.打印出该月的日历
d.打印出机器名
e.打印出主目录的文件清单
f.打印出正在使用的所有进程
用 TC Shell 编程 475

g.打印出 TERM、PATH 和 HOME 变量的值


h.打印出“Please couldn’t you loan me $50.00”
i.告诉用户“Good bye”和当前时间
2.确定你的脚本能够执行:
chmod +x greetme

3.脚本的第一行是什么?

练习 2——提取用户的输入

1.编写一个名为 nosy 的脚本,要求它能:


a.寻问用户全名,包括姓、名和中间名(如果有的话)
b.以用户的名欢迎用户
c.寻问用户的出生年,同时算出他/她的年龄
d.寻问用户的注册名,显示用户 id(从 /etc/passwd 调出)
e.告诉用户当前所在的主目录
f.告诉用户当前所使用的程序进程
g.告诉用户当前为星期几和当前时间
最后还应该能显示:
"The day of the week is Tuesday and the current time is 04:07:38 PM."

2.创建一个名为 datefile 的文本文件(除非该文件早已经存在)。每次输入都包括以下


内容,不同的内容间用冒号分隔:
a.姓、名
b.电话号码
c.地址
d.生日
e.工资
3.创建一个名为 lookup 的脚本,要求它:
a.内含一个备注,内容包括脚本名、你的名字、日期和你编写此脚本的原因。编写
此脚本的原因就是按排好的顺序来显示文件 datafile
b.按姓来给文件 datafile 排序
c.显示文件 datafile 中的内容
d.告诉用户在文件里的登录号
4.用 echo 和 verbose 命令来测试该脚本。

练习 3——命令行变量

1.编写一个名为 rename 的脚本,要求它:


a.取两个文件名作为命令行参数,第一个文件是个老文件而第二个文件是新的
b.把老文件的名字改为新文件名
c.把目录里的文件都列出来,看看有何变化
476 第 11 章

2.编写一个名为 checking 的脚本,要求它能:


a.把用户的登录名设为命令行参数
b.测试,看看该命令行参数是否已经给出过
c.测试用户名是否已包含在/etc/passwd 文件里,如果在,就打印:
"Found <user> in the /etc/passwd file."

如果不在,就打印:
"No such user on our system."

练习 4——条件和文件的测试

1.在 lookup 脚本里,询问用户是否想在文件 datafile 上加上一个入口,如果回答是 yes


或 y 的话,即:
a.用户需要输入新的姓名、电话号码、地址、生日和工资。每一项都将分别储存在
一个变量里,它们需要用冒号来分隔,并把这些信息挂到 datafile 文件上
b.按姓来对该文件排序。告诉用户已经加上了入口,并且按命令行前的数字顺序来
显示命令行
2.重写脚本 checking,使它能:
a.在检查用户名是否在文件/etc/passwd 里之后,再检查用户是否已经登录。如果已
经登录,就打印所有正在运行的程序进程,否则就显示:
"<user> is not logged on."

3.lookup 脚本需要在 datafile 的基础上才能运行。检查 lookup 脚本,确定 datafile 文件


是否存在,若存在,再检查其是否可读写。
4.给 lookup 脚本加上一个菜单,能够把以下的各项串起来:
[1]加上入口
[2]删除入口
[3]浏览入口
[4]退出
5.“加上入口” (Add entry)这部分的脚本程序已经写好。 “加上入口”这部分脚本应该
包括可测试用户名是否存在于 datafile 文件的源代码。如果存在,就告诉用户存在;如果不
存在,就加上一个入口。
6.编写“删除入口” 、“浏览入口”和“退出”部分的源代码。
7.“删除入口”部分在删除入口之前需要先检查入口是否存在。如果入口并不存在,告
知用户;如果存在,则在删除之后告知用户。退出时用一个数字来表示退出状态。
8.怎样从命令行检测退出状态?

练习 5——开关语句

1.使用开关语句重写以下脚本:
#!/bin/tcsh -f
用 TC Shell 编程 477

# Grades program

echo –n "What was your grade on the test? "


set score = $<
if ( $grade >= 90 && $grade <= 100 ) then
echo You got an A\!
else if ( $grade >= 80 && $grade < 89 ) then
echo You got a B.
else if ( $grade >= 79 && $grade < 79 ) then
echo "You're average."
else if ( $grade >= 69 && $grade < 69 ) then
echo Better study harder
else
echo Better luck next time.
endif

2.重写 lookup 脚本,并用开关语句来控制每个菜单选项。

练习 6——循环

1.编写一个名为 picnic 的程序,给用户名单中的用户每人发一封请柬,邀请他们参加


一个野餐会。用户名单在名为 friend 的文件里。friend 文件里的用户名单中有一个叫 Popeye。
a.请柬放在名为 invite 的文件里
b.测试文件,确定两个文件是否都存在并可读
c.循环将会重复使用在该用户名单的操作上。当碰到 Popeye 时,就跳开发给后面
其他的用户,也就是说,不发请柬给 Popeye
d.创建数组,保存一份所有收到请柬的用户的名单。给每一个用户寄信后,输出收
到请柬的所有用户的数量和他们的名单
建议:假如你有时间的话,还可以把你的 invite 文件弄得更漂亮一点,使请柬上都写上
用户的姓名。譬如说,将请柬的开头写成下面这样:
Dear John,
Hi John, I hope you can make it to our picnic…

要达到这样的效果,invite 文件就要如下所示的那样编写:
Dear XXX,
Hi XXX, I hope you can make it to our picnic…

用 sed 和 awk 命令就能把 XXX 替换为用户名。若用户名是大写的话就比较麻烦了,因


为用户名一般都是用小写字母的。
2.给 lookup 脚本加上一个新的菜单,内容包括以下:
[1]加上入口
[2]删除入口
[3]修改入口
[4]浏览入口
[5]退出
478 第 11 章

当用户选择好有效的入口选项,选项的操作已经完成后,再问用户是否要再看菜单一次。
假如选择的是无效的入口,程序就打印:
Invalid entry, try again.

此时菜单重新显示。
3.在 lookup 脚本下的 View entry(浏览入口)处创建一个附加菜单,寻问用户是否想
查看其选定的个人资料:
a.电话
b.住址
c.生日
d.工资
4.给脚本程序加上 onintr 命令,使用书签。当程序从书签那儿开始执行,所有的临时
文件都将会被删除。在荧屏显示 Good-bye 后,退出程序。
附录 A

Shell 程序员
的实用工具
apropos——在数据库中查找字符串
apropos keyword...

apropos 在一些对系统命令进行简要描述的数据库文件(参见目录/usr/man/whatis)中查
找与 keyword 相匹配的关键字,并把它写到标准输出中。类似的命令还有 man –k。

实例 A.1
1 $ apropos bash
bash (1) -GNU Bourne-Again SHell

2 $ man –k tcsh
tsh (1) -C shell with filename completionand
command line editing

说明
1 apropos 查找 bash 关键字,并显示其功能的简要描述。
2 man –k 的作用和 apropos 类似。

arch——显示机器类型
arch

目前,Linux 系统上的 arch 命令可以列出下面这些机器类型:i386、i486、i586、alpha、


sparc、arm、m68k、mips 和 ppx。

实例 A.2
$ arch
i386

at-at、atq、artm 及 batch——在指定的时间执行命令

at [-V] [-q queue] [-f file] [-mldbv] TIME

PDF created with pdfFactory Pro trial version www.pdffactory.com


480 附录 A

at –c job [job...]
atq [-V] [-q queue] [-v]
atrm [-V] job [job...]
batch [-V] [-q queue] [-f file] [-mv] [TIME]

at 和 batch 命令从标准输入中读入将要执行的命令。at 允许你指定这些命令何时执行。


用 batch 命令排队的作业将在系统负载水平允许的时候执行。这些命令在将来的某些时候从
标准输入或文件中载入。除非输出被重新定向,否则命令执行的结果将会以 mail 的形式发
给用户。
除非是超级用户,否则 atq 只列出用户待处理的作业。超级用户可以列出所有用户的作
业。执行结果类似于 at –l。
atrm 用于删除作业。atrm 3 4 5 的作用类似于 at –d。

实例 A.3
1 $ at 6:30am Dec 12 < program
warning: commands will be executed using /bin/sh
job 2 at 1999-12-12 06:30
2 $ at teatime today < program
warning: commands will be executed using /bin/sh
job 4 at 1999-10-20 16:00
3 $ at 7:45 pm August 9 < program
warning: commands will be executed using /bin/sh
job 5 at 1999-08-09 19:45
4 $ at now + 3 hours < program
warning: commands will be executed using /bin/sh
job 9 at 1999-10-20 23:18
5 $ at 2am tomorrow
at> man bash / 1pr
at> <EOT>
warning: commands will be executed using /bin/sh
job 7 at 1999-10-19 02:00
6 $ atq
6 1999-10-19 12:00 a
7 1999-10-19 02:00 a
7 $ at –f file 17:05 monday
warning: commands will be executed using /bin/sh
job 9 at 1999-10-18 17:05

说明
1 作业将在 12 月 12 日早上 6:30 被启动执行。每次键入该命令时都会显示警告信息。
2 作业将在当天下午 4:00 被启动执行。
3 8 月 9 日晚启动该作业。
4 3 小时后启动该作业。
5 明天凌晨 2 点,执行下列命令。at>是 at 的提示符,按 Ctrl-D 结束命令的输入。
6 atq 列出用户当前待处理的作业。
7 在 –f 选项后面的是文件名。这个文件就是将要在星期一 7:05 执行的程序。

awk(gawk)——模式查找处理语言
gawk [ POSIX or Gnu style options ] –f program-file [ - ] file...

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 481

awk 以行为单位在每一个指定文件中查找所有由 prog 参数指定的模式(参见第 5 章)。


在下面的例子中,使用 awk、nawk 还是 gawk 取决于你所使用的 awk 的版本。

实例 A.4
1 awk '{print $1, $2}' file
2 awk '/John/{print $3, $4}' file
3 awk –F: '{print $3}' /etc/passwd
4 date | awk '{print $6}'

说明
1 输出文件的前两列,列之间以空格分隔。
2 如果找到 john,输出第 3 和第 4 列。
3 以冒号作为分隔符,输出/etc/passwd 文件的第 3 列。
4 把 date 命令的输出作为 awk 的输入,awk 输出第 6 列。

banner——制作标语
banner 以大字符形式输出给定的字符串(每个字符串最长可达 10)到标准输出。

实例 A.5
banner Happy Birthday

说明
以标语形式输出字符串“Happy Birthday”。

basename——分离带路径文件名的路径部分
basename string [ suffix ]
dirname string

basename 命令从给定的字符串中删除前缀和后缀(如果有后缀的话),前缀是以/(正斜
杠)结束符的部分,并把结果写到标准输出。

实例 A.6

1 basename /usr/local/bin
2 scriptname="‘basename $0‘"

说明
1 去掉/usr/local 前缀,并输出 bin。
2 把该脚本的名字——$0 赋值给变量 scriptname。

bash——Gnu Bourne Again Shell


bash [options] [file[arguments]]
sh [options] [file[arguments]]

©1989,1991。
bash 是自由软件联合会的注册商标, bash 是一种脚本兼容命令语言说明器,
它从标准输入或文件中读取命令执行。bash 兼有 Korn Shell 和 C Shell(ksh 和 csh)的某些

PDF created with pdfFactory Pro trial version www.pdffactory.com


482 附录 A

优点(参见第 8 和第 9 章)。
bc——处理精确数学运算
bc [ -lwsqv ] [long-options] [ file ... ]

bc 是一个交互式的程序,它能处理类似于 c 的数学表达式,但在精确度上几乎没有限
制。
它从给定的文件中取得表达式,并读取标准输入。

实例 A.7

1 bc << EOF
scale=3
4.5 + 5.6 / 3
EOF
Output : 6.366
--------------------------
2 bc
ibase=2
5
101 (Output)
20
10100 (Output
^D

说明
1 这是一个即时输入文件,
bc 命令读取从第一个 EOF 标志到最后一个 EOF 标志之间的表达式。scale
设置了小数点前的最大位数,屏幕上显示了计算结果。
2 计算基数被设定为时,参与计算的数据被转化为二进制(ATT 专用) 。

biff[ny]——邮件到达时通知
n 设定通知
y 取消通知
cal——显示日历
cal cal [-jy] [month [year]]

cal 输出指定年份的日历。如果指定月份,则输出指定月份的日历。如果都没有指定,
只输出当月的日历。
-j 按 julian 格式输出日历(天数以 1 月 1 日为基数递增)。
-y 显示本年日历。

实例 A.8
1 $ cal
October 1999
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 483

31

2 $ cal –j
October 1999
Sun Mon Tue Wed Thu Fir Sat
274 275
276 277 278 279 280 281 282
283 284 285 286 287 288 289
290 291 292 293 294 295 296
297 298 299 300 301 302 303
304

说明
1 打印当前月份。
2 以 julian 格式打印当前月份日历。

cat——连接并显示文件
cat [-benstuvAET] [--number] [--number-nonblank]
[--squeeze-blank] [--show-nonprinting] [--show-ends]
[--show-tabs] [--show-all] [--help] [--version] [file...]

cat 顺序读入每一个给定的文件,并写到标准输出。如果没有给定输入文件,或指定了-
参数,cat 将从标准输入中读取输入。可使用--help 选项显示 cat 命令的简要描述。

实例 A.9

1 cat /etc/passwd
2 cat –n filel file2 >> file3
3 cat –T datafile
4 cat –b datafile

说明
1 显示/etc/passwd 文件的内容。
2 连接文件 file1 和 file2 并把输出追加到文件 file3。-n 开关选项指明为每一行进行编号。
3 以^I 表示 tab 字符。
4 对所有非空行进行编号。

chfn——改变 finger 信息
chfn [ -f full-name ] [ -o office ] [ -poffice-phone ]
[ -h home-phone] [ -u ] [ -v ] [ username ]

chfn 通常用来修改 finger 信息,这些 finger 信息存在/etc/passwd 文件中,finger 命令可


以显示这些信息。Linux 的 finger 命令可以显示四类信息,且这些信息都可以用 chfn 命令来
修改。这四类信息是:你的真实姓名、工作地点、办公电话和家庭电话。
chmod——改变文件的权限
chmod [-Rcfv] [--recursive] [--changes] [--silent]
[--quiet] [--verbose] [--help] [--version] mode file...

chmod 命令改变文件的存取模式。存取模式标明了权限和其他属性信息。存取模式可以

PDF created with pdfFactory Pro trial version www.pdffactory.com


484 附录 A

以绝对方式表达,也可以以符号方式表达。

实例 A.10

1 chmod +x script.file
2 chmod u+x,g-x file
3 chmod 755 *

说明
1 对文件 script.file 的属主、属组和其他用户打开可执行权限。
2 对文件 file 的属主打开执行权限,同时取消文件属组的执行权限。
3 对当前目录下所有文件的属主打开读、写和执行权限,而对属组和其他用户开放读和执行权限。
属性值以八进制表示(111 101 101),rwxr-xr-x。

chown——改变文件的用户和组拥有权
chown [-Rcfv] [--recursive] [--changes] [--help] [--version]
[--silent] [--quiet] [--verbose] [user][:.][group] file...

chown(Gnu)根据它的第一个非选项参数改变每个文件的用户和/或组的拥有权。如果
只有一个用户/用户 id,那该用户就具有给出的所有文件的所有权,而这些文件的属组不
变。如果用户名后有一个冒号或一个点,之后是组名(组 id),而且它们之间没有空格,
这些文件的组属性也会改变;如果在冒号/点之后并没有组名跟着,用户就成为这些文件
的拥有者而文件的组属性被改为此用户所在的组。假如冒号/点,组都给出,而用户名默
认,就仅是改变文件的组属性。在这里,chown 的作用犹如 chgrp。只有超级用户才能够
用 chown。

实例 A.11

1 chown john filex


2 chown –R ellie ellie

说明
1 把用户 id 从 filex 改为 john,只有超级用户才能改变所有权。
2 递归改变 ellie 目录下的所有文件的所有权为 ellie。

chsh——改变登录 Shell
chsh [ -s shell ] [ -1 ] [ -u ] [ -v ] [ username ]

chsh 用于改变登录 Shell。假如 Shell 在命令行里没有给出,chsh 会提示加上。所有有效


Shell 都在/etc/shells 文件里列出。

-s, --shell 指定登录 Shell


-l, --list-shell 输出/etc/shell 文件里列出的 Shell 列表后推出
-u, --help 显示帮助信息后退出
-v, --version 显示版本信息后退出

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 485

实例 A.12

1 $ chsh –1
/bin/bash
/bin/sh
/bin/ash
/bin/bsh
/bin/tcsh
/bin/csh
/bin/ksh
/bin/zsh

2 $ chsh
Changing shell for ellie.
New shell [/bin/sh] tcsh
chsh: shell ust be a full path name.

说明
1 在此 Linux 系统中列出所有可用的 Shell。
2 要求用户输入完整的路径名以登录一个新的 Shell。如不输入类似/bin/tcsh 这样的完整的路径名,
系统将会报错。

clear——清屏
cmp——比较两个文件
cmp [ -1 ] [ -s ] filename1 filename2

比较上面的文件。如果它们完全一样,cmp 则不作结论。如不一样,就会报出第一个不
同处的所在的行数和字节数。

实例 A.13
cmp file.new file.old

说明
如文件相异,则显示不同处所在字节数和行数。

compress——compress、uncompress、zcat 压缩解压文件或显示压缩文件
compress [ -f ] [ -v ] [ -c ] [-V ] [ -r ] [ -b bits] [ name ...]
uncompress [ -f ] [ -v ] [ -c ] [ -V ] [ name ... ]
zcat [ -V ] [ name ... ]

compress 用适应性 Lempel-Ziv 编码方式压缩文件。原文件被一个.Z 扩展名文件所取代,


而属主属性、访问时间和修改时间都保持不变。如果没有指定的文件,就从标准输入读入、
压缩并写到标准输出。

实例 A.14

1 compress –v book

PDF created with pdfFactory Pro trial version www.pdffactory.com


486 附录 A

book:Commpression:35.07% -- replaced with book.Z


2 ls
book.Z

说明
1 把 book 压缩成一个压缩文件 book.Z 并显示压缩比以及新的文件名。
2 用 ls 显示文件名。

cp——复制文件
cp [options] source dest
cp [options] source... directory

cp 命令可以复制一个文件/目录到另外一个目标文件/目录里去。源文件/目录名与目标文
件/目录名不能相同。如果目标不是一个目录,在它前面就只能指定一个文件;如目标是一
个目录,就能指定不止一个文件。假如目标不存在,cp 就创建一个名为 target 的文件。如果
目标存在,而且不是一个目录,内容就被覆盖;如果目标是一个目录,源文件就复制到目录
里。

实例 A.15

1 cp --help
2 cp chapter1 book
3 cp –r desktop /usr/bin/tester

说明
1 显示 cp 的信息及其选项,然后退出。
2 把 file 1 的内容复制到 file2 中。
3 把 chapter 1 的内容复制到目录 book 中。在 book 目录里,chapter 1 的文件名依然保留。递归复制
整个桌面目录到 /usr/bin/tester.

cpio——复制进/出存档文件
cpio –i [ bBcdfkmrsStuvV6 ] [ -C bufsize ] [ -E filename ]
[ -H header ] [ -I filename [ -M message ] ] [ -R id ]
[ pattern ... ]
cpio –o [ aABcLvV ] [ -C bufsize ] [ -H header ]
[ -O filename [ -M message ] ]
cpio –p [ adlLmuvV ] [ -R id ] directory

根据用户指定复制文件到磁带或者目录里,通常是作备份之用。

实例 A.16
find . -depth –print | cpio –pdmv /home/john/tmp

说明
在当前目录下,用 find 命令得到一个展开的树状目录。即使目录没有写权限,也输出目录里的
所有文件。然后用 cpio 命令把这些文件复制到/home 分区的 john/tmp 目录里。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 487

cron——时钟守护程序
cron 可以在指定的时间执行命令。我们通常在/etc/crontab(只有超级用户才有访问权限)
文件中指定计划任务。
crypt——加/解密文件
crypt [ password ]

crypt 可以对文件的内容进行加/解密。是加密解密的关键参数。
cut——从文件中删除每行的指定列或字符
cut {-b byte-list, --bytes=byte-list} [-n][--help][--version] [file...]
cut {-c character-list, --characters=character-list}[--help] [--version]
[file...]
cut {-f field-list, --fields=field-list} [-d delim][-s][--delimiter=delim]
[--only-delimited] [--help][--version] [file...]

cut 命令从文件的每行中摘出指定的列或字符,如果没有指定文件,就从标准输入获得
输入。-d 选项用于指定列分隔符,默认分隔符是 tab。

实例 A.17

1 cut --help
2 cut –d: -f1,3 /etc/passwd
3 cut –d: -f1-5 /etc/passwd
4 cut –c1-3,8-12 /etc/passwd
5 date | cut –c1-3

说明
1 help 选项可以显示关于参数和选项的帮助信息。
2 以冒号:为分隔符,显示文件/etc/passwd 文件的第 1 和第 3 列。
3 以冒号:为分隔符,显示文件/etc/passwd 文件的第 1~第 5 列。
4 显示/etc/passwd 文件每行中的第 1~第 3 个字符和第 8~第 13 个字符。
5 把 date 命令的输出作为 cut 命令的输入。cut 命令显示前 3 个字符。

date——显示/设置日期和时间
date [-u] [-d datestr] [-s datestr] [--utc][--universal]
[--date=datestr] [--set=datestr] [--help][--version]
[+FORMAT] [MMDDhhmm[[CC]YY][.ss]]

如果没有指定任何参数,date 命令显示当前日期和时间。如果命令行参数以加号(+)
开头,那么参数的其余部分用于指定显示的格式。如果用的是百分号%,那么跟在%后面的
就是定制日期的某一部分(如月份和星期)的格式字符。如果要设定日期,只需在命令行参
数给出表达年、月、日、小时及分钟的数字即可。

实例 A.18

1 date +%T
2 date +20%y
3 date "+It is now %m/%d /%y"
4 date --help

PDF created with pdfFactory Pro trial version www.pdffactory.com


488 附录 A

说明
1 显示当前时间:20:25:51。
2 显示 2096。
3 显示当前日期:10/25/99。
4 显示 date 命令的所有选项和时间格式。

dd——复制转换文件
dd [--help] [--version] [if=file] [of=file][ibs=bytes] [obs=bytes]
[bs=bytes] [cbs=bytes] [skip=blocks] [seek=blocks] [count=blocks]
[conv={ascii,ebcdic,ibm,block,unblock,lcase,ucase,swab,noerror,
notrunc,sync}]

dd 命令用于文件迁移,这样的文件迁移通常在磁带或不同的操作系统之间进行。
实例 A.19

1 $ dd --help
2 $ dd if=inputfile of=outputfile conv=ucase

说明
1 显示所有选项或标志的简短帮助。
2 把 input 文件的所有字符转换为大写字符并写到 ouput 文件中。

diff——比较两个文件的不同
diff [-bitw] [-c | -Cn

比较两个文件,逐行显示它们不同的地方。同时显示在 ed 编辑器上做修改时需要用到
的命令(注意:但你的 Linux 版本不一定支持)。
实例 A.20
diff file1 file2
1c1
< hello there
---
> Hello there.
2a3
> I'm fine.

说明
显示文件 1 和文件 2 每行的区别。第一个文件用< 符号来表示,第二个文件用 > 符号来表示。
每一行程序都用 ed 命令来处理, 表示编辑命令将会用来使两个文件相同。

dos、xdos、dosexec、dosebug——Linux 环境下的 dos,可在 Linux 环境下运行 MS-DOS


和 MS-DOS 程序
见 Linux 手册里相关的详细介绍。
df——汇总磁盘空间
df [-aikPv] [-t fstype] [-x fstype] [--all][--inodes][--type=fstype]
[--exclude-type=fstype][--kilobytes] [--portability] [--print-type]

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 489

[--help] [--version] [filename...]

df 命令显示文件系统信息,该文件系统要么就是所有的文件所在的那个文件系统,要么
就是默认的所有文件系统。

实例 A.21

df
Filesystem 1024-blocks Used Auailable Capacity
Mounted on
/dev/hda5 1787100 1115587 579141 66% /

du——显示磁盘使用情况
du [-arskod] [name ...]

du 命令显示在指定目录下的所有文件和指定文件使用的 512-byte 字块的数量。

实例 A.22

1 du --help
2 du –s /desktop
3 du -a

说明
1 显示 du 命令的参数和选项。
2 显示在 desktop 和其子目录下的字块的使用情况。
3 显示该目录和其子目录下每个文件字块的使用情况。

echo——显示变量运算结果
echo [ argument ] ...
echo [ -n ] [ argument ]

echo 用空格将其变量分开写出,并在标准输出时用换行符来终结之。

系统 V 选项:
\b 退格
\c 续行
\f 换页
\n 另开一行
\r 回车
\t 制表符
\v 水平制表符
\\ 反斜杠
\on n 可为 1、2 或 3,八进制的值

egrep——以全正规表达式在文件中匹配指定模式行为
egrep [ -bchilnsv ] [ -e special-expression ][ -f filename ]
[ strings ] [ filename ... ]

PDF created with pdfFactory Pro trial version www.pdffactory.com


490 附录 A

egrep(expression grep)在文件中搜索指定的模式行为,并打印匹配模式行为的行。egrep
使用正规表达式来匹配模式行为(正规表达式含字母数字全集以及特殊字符集。参见第 3 章
关于 grep 和 grep-E 的叙述)。
实例 A.23
1 egrep 'Tom|John' datafile
2 egrep '^ [A-Z]+' file

说明
1 显示所有文件中含模式 Tom 和 John 的行。
2 显示以一个或多个大写字母的行。

expr——计算表达式
expr expression...
expr {--help, --version}

这里,参数被视为表达式。运算后的结果写到标准输出。表达式的各个部分必须以空格
分开,而且某些对于 Shell 有特殊意义的字符必须转义。该命令在 Bourne Shell 脚本中常用
于简单的数学运算。
实例 A.24

1 expr 5 + 4
2 expr 5 \* 3
3 num=0
num=‘expr $num + 1‘

说明
1 显示 5+4 的和。
2 显示算式 5\*3 的结果。此时,*号被转义以防被 Shell 扩展替换。
3 首先 num 被赋值为 0,接下来的 expr 命令计算 1 加上 num 变量的值,并将结果赋给 num 变量。

fgrep——在文件中匹配字符串
fgrep [ -bchilnsvx ] [ -e special string ]
[ -f filename ] [ strings ] [ filename ... ]

fgrep(快捷 grep)在文件中搜寻特定的字符串,并显示匹配字符串的行。fgrep 与 grep


(1)和 egrep(1)的区别在于,它把正规表达式的元字符视为原义字符(参见第 3 章关于
grep 和 grep-F 的阐述)。

实例 A.25

1 fgrep '***' *
2 fgrep '[ ] * ? $' filex

说明
1 显示当前目录下所有文件中含有三个星号(*)的行,所有的字符都被视为原意,也就是说,原

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 491

义字符并非特殊情况。
2 显示文件中所含的由引号括起来的字符串的行。

file——通过文件内容判断文件类型
file [ -vbczL ] [ -f namefile ] [ -m magicfiles ] file ...

file 命令通过一系列的检查来判断文件所含内容。如果文件的内容看起来像是由 ASCII


码来组成的,file 命令就检查前 512 个字节来判定该文件是用哪种语言编写的。

实例 A.26

1 file bin/ls
/bin/ls:sparc pure dynamically linked excutable
2 file go
go: executable shell script
3 file junk
junk: English text

说明
1 ls 是一个二进制的动态链接可执行文件。
2 go 是一个 Shell 脚本。
3 junk 是一个 ASCII 码文本。

find——搜寻文件
find path-name-list expression

find 命令是能展开 pathname 列表参数所指定的路径之下可以是一个或多个树状目录。


并在这些目录中搜索符合这项要求的文件。第一个参数搜索起始路径,其他的参数用于指定
搜寻文件的特征,如姓名、大小、属组和权限等等。关于语法的区别,请参阅 UNIX 手册。

实例 A.27
1 find . –name \*.c -print
2 find .. –type f
3 find . –type d -print
4 find / -size 0 – exec rm "{}" \;
5 find ~ -perm 644 -print
6 find . –type f –size +500c –atime +21 –ok rm –f "{}" \;
7 find . –name core –print 2> /dev/null (Bash/Korn shells)
( find . –name core –print > /dev/tty ) >& /dev/null (C/TC shell)
8 find / -user ellie xdev -print
9 find ~ -atime +31 –exec mv {} /old/{} \; -print

说明

1 以当前目录为起始目录(.)为起始目录,搜寻所有的以.c 结尾的文件,并显示匹配文件的全路
径名。
2 以当前目录的主目录为起始目录,搜寻所有类型的文件(即非目录文件)。在 Linux 中,-print 这
项不再是必需的。
3 以当前目录为起始目录,搜寻所有目录类型的文件。
4 以根目录为起始目录,搜寻所有 0 字节文件并将其删除。{}是用于表示每一个符合条件的文件名。

PDF created with pdfFactory Pro trial version www.pdffactory.com


492 附录 A

5 以用户的主目录为起始目录,搜寻所有具有 644 权限(属主拥有读写权,而其他用户和属组用户


只有读权限)的~文件。
6 以当前目录为起始目录,找出所有 500 字节且在 21 天内未曾被访问的文件,并寻问用户是否将
其删除。
7 以当前目录为起始目录,找出所有名为 core 的文件,并将错误输出到/dev/null(UNIX 的字节桶)
里。
8 打印在根目录中所有属于用户 ellie 的文件。
9 将超过 31 天还未被访问的文件转移到/old 目录中,并列出所有被移动的文件清单。

finger——显示本地或远端用户信息
finger [-lmsp] [user ...] [user@host ...]

默认的 finger 命令显示每一个登录用户的信息,包括登录名、全名、终端名(前面加*


号则表示禁止写)、闲置时间、登录时间和登录地址(如果能得知的话) 。

实例 A.28

% finger
Login Name Tty Idle Login Time Office
Office Phone
ellie Ellie Quigley p0 1:06 Oct 19 11:41 (:0.0)
ellie Ellie Quigley p1 Oct 19 16:37 (:0.0)
ellie Ellie Quigley p2 Oct 19 16:45 (:0.0)

fmt——简单文本格式化器
fmt [ -c ] [ -s ] [ -w width | -width ] [ inputfile... ]

fmt 是一个简单的文本格式化器。它能够补全或拼接字符行,使之达到指定字符长度(指
定宽度用-w 选项来实现),然后再输出这些字符行,默认宽度为 72。fmt 直接在参数中指定
输入文件。如果没有给出输入文件,则从标准输入获得输入。

实例 A.29
fmt –c –w45 letter

说明
letter 格式。-c 开关使得段落的开头两行缩进,若是齐头式就从第二行开始缩进。-w 选项用于
补全输出行,使之有 45 行。

fold——截裁长行
fold [-bs] [-w width] [--bytes] [--spaces][--width=width][--help]
[--version] [file...]

如果没有文件被指定,则截裁(fold)指定文件名的内容,或者直接截裁标准输入。把
长行截裁为几段,每段最大的默认宽度为 80。宽度应该为 8 的倍数,否则制表符应该提前
扩展。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 493

实例 A.30

% fold --help
Usage: fold [OPTION]... [FILE]...
Wrap input lines in each FILE (standard input by default),
writing to
standard output.

-b, --bytes count bytes rather than columns


-s, --spaces break at spaces
-w, --width=WIDTH use WIDTH columns instead of 80

Report bugs to textutils-bugs@gnu.ai.mit.edu

ftp——文件传输程序
ftp [-v] [-d] [-i] [-n] [-g] [host]

ftp 命令是基于文件传输协议(File Tramsfer Protocol)的用户界面,用于远距站点间的


文件传输。文件传输程序并不局限在 UNIX 机上使用。

实例 A.31

1 ftp ftp.uu.net
2 ftp –n 127.150.28.56

free——显示系统内存的使用情况
free [-b | -k | -m] [-o] [-s delay ] [-t] [-v]

free 命令显示系统物理内存、交换内存情况以及内核所使用的共享内存和缓冲区的大小。

实例 A.32

% free
total used free shared buffers cached
Mem: 64148 54528 9620 45632 3460 29056
-/+ buffers/cache: 22012 42136
Swap: 96352 0 96352

fuser——确定使用文件或套接口的进程
fuser [-a|-s] [-n space] [-signal] [-kmuv] name ...[-] [-n space]
[-signal] [-kmuv] name ...
fuser –1
fuser –V

fuser 命令显示使用指定文件和文件系统的进程 PID。在默认显示模式下,文件名跟在一


个表示访问类型的字母后面。

实例 A.33

% fuser --help
usage: fuser [ -a | -q ] [ -n space ] [ -signal ] [ -kmuv ]

PDF created with pdfFactory Pro trial version www.pdffactory.com


494 附录 A

filename ... [ - ] [ -n space ] [ -signal ] [ -kmuv ] name


...
fuser –1
fuser –V
-a 同时也显示未被使用的文件
-k 取消删除访问该文件的进程
-l 列出 signal(信号)名
-m 装载的文件系统
-n 在指定的名字作用域(file、upd 或 tcp)做域搜索
-s 静音操作
-signal 发送 signal 而不是 SIGKILL
-u 显示用户 id
-v verbose 输出
-V 显示版本信息
- 重置选项

udp/tcp name: [local_port][,[rmt_host][,[rmt_port]]]

gawk——模式搜寻处理语言
gawk [ POSIX or GNU style options ] –f program-file [-- ] file ...
gawk [ POSIX or GNU style options ] [ -- ]program-text file ...

gawk 是 AWK 程序语言的 Gnu 实现。它遵循 POSIX 1003.2 标准所定义的命令语言和使


用规范,这个版本的实现是基于 The AWK Programming Language( 《AWK 程序语言》 ,Aho,
Kernighan Weinberger 著)这本书的,还有其他功能/特征则与 UNIX awk System V Release 4
版本相同或相似。gawk 还有具有与最新的 Bell Lab awk 和其他一些 Gnu 特色的特征。
gcc 及 g++——Gnu project C 和 C++的编译器
gcc [ option | filename ] ...
g++ [ option | filename ] ...

getopt(s)——分析命令行选项
getopts 命令是 getopt 的升级。getopts 命令用于在命令行里中断选项,使得 Shell 脚本能
更方便地对选项进行分析和合法测试(见第 9 章“getopts”部分) 。
grep——搜寻含匹配模式的文件
grep [-[AB] NUM] [-CEPGVbchiLlnqsvwxyUu] [-e PATTERN | -f FILE]
[--extended-regexp] [--fixed-strings] [--basic-regexp]
[--regexp=PATTERN] [--file=FILE] [--ignort-case] [--word-regexp]
[--line-regexp] [--line-regexp] [--no-messages] [--revert-match]
[--version] [--help] [--byte-offset] [--line-number]
[--with-filename] [--no-filename] [--quiet] [--silent]
[--files-without-match] [--files-with-matcces] [--count]
[--before-context=NUM] [--after-context=NUM] [--context] [--binary]
[--unix-byte-offsets] files...

grep 在给定的输入文件里(假如没有给定文件或文件名,就在标准输入里)搜寻含给定
匹配模式的文本行,在默认情况下,grep 命令输出匹配行,下面是三个 grep 的变体,可由
以下的选项控制:

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 495

-G, --basic-regexp 模式被视为常规表达式(见下) ,是默认值


-E, --extended-regexp 模式被视为扩展表达式
-F, --fixed-strings 模式被视为一系列固定的字符串,由换行符分隔。其中任
何一个都可能被匹配。此外还有两个变体 egrep 和 fgrep,
egrep 类似于 grep-E,但不完全相同, 而是兼容以前的 UNIX
的 egrep 版本。而 fgrep 则等同于 grep-F
(注:关于 rgrep 见后面)

实例 A.34

1 grep Tom file1 file2 file3


2 grep –in '^tom savage' *

说明
1 grep 显示在 file1、file2 和 file3 中所有的含有 Tom 字符串的行。
2 grep 显示当前目录下所有文件中含有 tom savage 开头的行(忽略大小写) ,并显示相应的行号。

groups——显示用户的属组
groups [ user... ]

groups 命令把当前用户(默认)或指定用户的属组写到标准输出。
gzip、gunzip、zcat——压缩或解压缩文件
gzip [ -acdfhlLnNrtvV19 ] [-S suffix] [ name ... ]
gunzip [ -acfhlLnNrtvV ] [-S suffix] [name ... ]
zcat [ -fhLV ] [ name ... ]

gzip 用来缩小用 Lempel-Ziv(LZ77)编码的文件。无论何时,这样的文件都被扩展名


为.GZ 的文件替代,但替代后文件的属主、访问记录和权限以及修改时间都没有改变。
head——输出文件的前十行
head [-c N[bkm]] [-n N] [-qv] [--bytes=N[bkm]] [--lines=N] [--quiet]
[--silent] [--verbose] [--help] [--version] [file...]
head [-Nbcklmqv] [file...]

head 标准输出每个文件的前十行。假如超过 1 个文件,它就显示各个文件的标题并把这


个标题作为文件名;如果没有文件或文件为“-” ,则从标准输入读入。
host——显示 DNS 中指定主机和指定区域的信息
host [1] [-v] [-w] [-r] [-d] [-t querytype] [a] host [server]

host 命令可用于显示指定 internet 主机的信息。遍布全国的互连服务器提供了这些主机


信息。默认情况下,host 命令对主机名和 IP 地址进行相互转换。指定-a 或-t 选项,可以显
示完整的信息。
id——显示用户名、用户 ID、组名和组 ID
id [-gnurG] [--group] [--name] [--real] [--user] [--group] [--help]
[--version] [username]

PDF created with pdfFactory Pro trial version www.pdffactory.com


496 附录 A

id 命令用于列出用户的 ID、用户名、组 ID 和组名。如果用户的真实 ID 和有效 ID 不一


致,则同时显示之。
jsh——标准作业控制
jsh [ -acefhiknprstuvx ] [ argument... ]

jsh 命令是标准 Bourne Shell 的一个交互式界面。通过该界面,用户可以使用 Bourne Shell


所有的功能,并能对作业进行控制。
kill——发送信号结束向一个或多个进程
kill [

发送信号结束一个或多个进程
killall——杀死指定名字的进程
less——与 more 命令相反
less -?
less –-help
less –V
less –version
less [-[+]aBcCdeEfgGiImMnNqQrsSuUVwX][-b bufs] [-h lines] [-j line]
[-k keyfile][-{oO} logfile] [-p pattern] [-P prompt] [-t tag]
[-T tagsfile] [-x tab] [-y lines] [-[z] lines] [+[+]cmd] [--]
[filename]...

less 命令与 more 命令相似,但它不仅可以向后翻动文件而且还可以向前。less 命令在开


始显示前,无需读入整个文件,因此在开启大文件的情况下,它比 vi 一类的文本编辑器速
度更快。另外,less 命令使用的 termical 技术(在有些系统中也被称为 terminfo)。因此 less
命令可以在各种终端中运行。它甚至能对硬拷贝终端提供一定的支持。
line——读入一行
line 命令从标准输入中读入一行(直到碰到换行符) ,并把该行写到标准输出中。less 命
令遇到 EOF 结束符时即退出并给出返回码,这样,至少会显示一个换行符。在 Shell 脚本编
程中,该命令常用于接收用户的输入。
ln——为文件创建硬链接
ln [options] source [dest]
ln [options] source... directory
Options:
[-bdfinsvF] [-S backup-suffix] [-V {numbered,existing,simple}]
[--version-control=(numbered,existing,simple)] [--backup]
[--directory] [--force][--interactive] [--no-dereference] [--symbolic]
[--verbose] [--suffix=backup-suffix] [--help] [--version]

如果最后一个参数指定的是一个已存在的目录,link 命令就把每一个给定的文件链接指
向该目录下的同名文件。如果只给定一个文件名,该文件将被链接到当前目录。如果只给出
两个文件名,第一个文件将被链接到第二个文件。如果给出两个文件而最后的参数是一个目
录,将导致出错,在 crossing 一个分区的时候,更常用的是符号链接。
选项:

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 497

-b,--backup 备份将被删除的文件
-d,-F,--direction 用于超级用户对目录创建硬链接
-f,--force 删除已存在的文件
-i,--interactive 提示是否删除已存在的目标文件
-n,--no-dereference 当指定的目标文件是一个指向目录的符号链接时,ln 命令替换该链接,而不是
解除原链接的参照关系,并在目录创建一个指向该目录的链接。这个选项常与
-force 选项搭配使用
-s,--symbolic 建立符号链接而不是硬链接
-v,--verbose 在创建链接,显示被链接指向的文件名
--help 在标准输入上显示帮助信息,并正常退出
--version 在标准输入上显示版本信息,并正常退出
-S,--suffix SIMPLE_BACKUP_SUFFIX 环境变量用作备份文件名的前缀。通过这个选项
backup-suffix 可以改变该环境变量的值,如果该环境变量初值,也没有使用这个选项,将像
emacs 一样使用~作为前缀
-V –version-control 可以通过设置 VERSION_CONTROL 环境变量来指定备份类型

实例 A.35
1 ls -1
total 2
drwxrwsr-x 2 ellie root 1024 Jan 19 18:34 dir
-rw-rw-r-- 1 ellie root 16 Jan 19 18:34 filex
2 % ln filex dir
3 % cd dir
4 % ls -1
total 1
-rw-rw-r-- 2 ellie root 16 Jan 19 18:34 filex

说明
1 ls 命令显示了 dir 目录和 filex 文件的完整信息列表。一个目录的链接数至少两个,一个是为指向
自身而创建的,另一个是为父目录而创建的。一个文件的链接数则至少一个,该链接指向文件创
建时所在的目录,当删除一个文件时,其链接数也将降为 0。
2 ln 命令创建了一个名为 filex 的硬链接。现在,filex 文件同时被链接到 dir 目录和当前目录。ln
命令在创建一个链接时,并不创建新文件,它只是简单地为一个已有的文件提供了其他文件或目
录的位置信息。如果删除其中一个链接,还将留下另一个。对被链接的文件的修改都将反映到其
链接文件中,因为它们实际上是同一个文件。
3 切换至 filex 被链接的目录。
4 我们看到,filex 的链接数现在已经变为 2,这样,我们可以在这个目录及其父目录都可以访问到
filex 文件了。

logname——取得用户当前运行的进程名
logname [--help] [--version]
look——显示以给定字符串为开头的行
look [-dfa] [-t termchar] string [file]
look 命令显示文件中那些以给定字符串为开头的行。由于采用二进制查找,因此文件中
的各行必须是有序的。如果不指定文件,则使用/usr/dict/words 文件,而且只比较字母数字,
并忽略大小写区别。

PDF created with pdfFactory Pro trial version www.pdffactory.com


498 附录 A

选项:
-d 字典字母顺序,即只对字母和数字进行比较
-f 忽略大小写区别
-a 使用另一字典/usr/dict/web2
-t 指定终结字符串,即只比较到该字符串(包括该字符串)

如果找到匹配的行,look 命令返回退出状态值 0,如果没有找到,返回退出状态值 1,


如果出错,返回大于 1 的值。
实例 A.36
1 % look sunb
sunbeam
sunbeams
Sunbelt
sunbonnet
sunburn
sunburnt

2 % look karen sorted.datebook


3 % look Karen sorted.datebook
Karen Evich:284-758-2857:23 Edgecliff Place, Lincoln, NB
92086:7/25/53:85100
Karen Evich:284-758-2867:23 Edgecliff Place, Lincoln, NB
92743:11/3/35:58200
Karen Evich:284-758-2867:23 Edgecliff Place, Lincoln, NB
92743:11/3/35:58200
4 % look –f karen sorted.datebook
Karen Evich:284-758-2857:23 Edgecliff Place, Lincoln, NB
92086:7/25/53:85100
Karen Evich:284-758-2857:23 Edgecliff Place, Lincoln, NB
92743:11/3/35:58200
Karen Evich:284-758-2867:23 Edgecliff Place, Lincoln, NB
92743:11/3/35:58200

说明
1 look 命令显示在文件/usr/dict/words 中所有以 sunb 开头的行。这里我们假定/usr/dic/words 为当前
目录。
2 look 命令无法在名为 sorted.datebook 的文件找到以 karen 为开头的行(文件必须是有序的,否则
look 命令将找不到任何东西) 。
3 look 命令显示在文件/usr/dict/words 中所有以 Karen 开头的行。
4 –f 选项用于指定在比较时忽略大小写区别。

lp(ATT,Linux)——输出到打印机
lp [ -cmsw ] [ -ddest ] [ -number ] [ -ooption ] [ -ttitle ] filename ...
cancel [ ids ] [ printers ]

lp/cancel 命令向打印机提交/撤销打印请求
实例 A.37
1 lp –n5 filea fileb
2 lp –dShakespeare filex

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 499

说明
1 向打印机发出打印 5 份文件 filea 和 fileb 拷贝的请求。
2 向名为 Shakespeare 的打印机提交打印 filex 文件的请求。

lpr(UCB,Linux)——输出到打印机
lpr [ -Pprinter ] [ -#copies ] [ -Cclass ] [ -Jjob ] [ -Ttitle ]
[ -i [ indent ] ] [ -1234font ] [ -wcols ] [ -r ] [ -m ] [ -h ]
[ -s ] [ -filter-option ] [ filename ... ]

lpr 为打印作业建立缓冲池,当打印设备可用时,即顺序响应打印请求。每个打印作业
由一个控制作业和一个或多个数据文件组成。
实例 A.38
1 lpr -#5 filea fileb
2 lpr –Pshakespeare filex
说明
1 向打印机发出打印 5 份文件 filea 和 fileb 拷贝的请求。
2 向名为 Shakespeare 的打印机提交打印 filex 文件的请求。

lpstat——显示 LP 打印服务状态信息
lpq——显示打印机状态信息
ls、dir、vdir——列出目录内容
ls [-abcdfgiklmnpqrstuxABCFGLNQRSUX1] [-cols] [-T cols] [-I pattern]
[--all] [--escape] [--directory] [--inode] [--kilobytes]
[--numeric-uid-gid] [--no-group] [--hide-control-chars] [--reverse]
[--size] [--width=cols] [--tab-size=cols] [--almost-all]
[--ignore-backups] [--classify] [--file-type] [--full-time]
[--ignore=pattern] [--dereference] [--literal] [--quote-name]
[--recursive] [--sort={none,time,size,extension}]
[--format={long,verbose,commas,across,vertical,single-column}]
[--time={atime,access,use,ctime,status}][--help] [--vision]
[--color[={yes,no,tty}]] [--colour[={yes,no,tty}]][name...]

对于给定的目录参数,ls 命令列出目录的内容。对于文件参数,则显示其文件名和其他
要求列出的关于该文件的信息。输出将按字母顺序排序,如果没有给出任何参数,则显示当
前目录的内容。

实例 A.39

1 ls -alF
2 ls –d a*
3 ls -i

说明
1 -a 选项要求列出隐藏文件(以.开头的文件)
,-l 选项要求显示文件的完整信息。-F 选项则在每个
目录名前加上反斜杠\,在可执行文件前加上星号*,而在符号链接文件末添上@符号。

PDF created with pdfFactory Pro trial version www.pdffactory.com


500 附录 A

2 如果-d 选项为目录,那么只显示目录名,而不显示其内容。
3 -i 选项要求在显示的文件名前加上该文件所耗用的 I 节点数。

mail——阅览/发送文件
Sending mail
mail [ -tw ] [ -m message_type ] recipient...
rmail [ -tw ] [ -m message_type ] recipient...
Reading mail
mail [ -ehpPqr ] [ -f filename ]
Forwarding mail
mail –F recipient...
Debugging
mail [ -x debug_level ] [ other_mail_options ] recipient...
mail [ -T mailsurr_file ] recipient...

recipent 参数通常是可为 login(l)识别的用户名,当指定 recipents 参数时,mail 即假定邮


件要发送,它从标准输入中接收输入直至遇到文件终止符(Ctrl-D)为止,如果从终端接收
输入,则以只含句点(.)的一行作为结束的标志。只要从上述设备中接收了信息,mail 命
令即将这些内容加入每个收件人的 mailfile 文件中。
mailx——交互式消息处理器
mailx [ -deHiInNUvV ] [ -f [ filename|+folder ]] [ -T filename ]
[ -u user ] [ recipient... ]
mailx [ -dFinUv ] [ -h number ] [ -r address ][ -s subject ]
recipient...

上面列出的这些 mail 工具为发送、接收、管理邮件提供了交互式的界面。这些功能需


要一些基本的网络服务来支持。接收到的邮件被存放在 mailbox 文件中,而阅览过的文件则
被移到 mbox 文件中。
make——管理/更新/重新生成一组相关程序及文件
make [ -f makefile ] ... [ -d ] [ -dd ] [ -D ]
[ -DD ] [ -e ] [ -i ] [ -k ] [ -n ] [ -p ] [ -P ]
[ -q ] [ -r ] [ -s ] [ -S ] [ -t ] [ target ... ]
[ macro=value ... ]

make 命令根据描述文件中的命令更新文件。如果目标文件比同名依赖文件要新,make
命令就会更新这些目标文件。
man——格式化显示在线个人使用手册
man [-acdfhktwW] [-m system] [-p string] [-C config_file] [-M path]
[-P pager] [-S section_list] [section] name...

manpath——指定用户的个人手册搜索路径
man [-acdfhkKtwW] [-m system] [-p string] [-C config_file] [-M path]
[-P pager] [-S section_list] [section] name...

man 命令使在线个人帮助手册以某种格式显示。该版本可以识别 manpath 和(man)pager


环境变量,因此用户可以拥有自己的个人帮助手册和选择任何程序来显示这个已经个人格式

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 501

化过的在线手册。如果指定了要参阅的章节,那么 man 命令只显示该指定章节。


mesg——允许/禁止用 write 命令发送的消息
mesg [ -n ] [ -y ]

-n 参数指示屏蔽由 write(1)命令发送的消息,这是通过设置用户终端上的用户禁止写
权限来实现的。-y 参数则重新打开写权限。不带参数则只显示而不改变当前状态。
mkdir——创建新目录
more——浏览/翻页显示文本文件
more [ -cdflrsuw ] [ -lines ] [ +linenumber ] [ +/pattern ]
[ filename ... ]
page [ -cdflrsuw ] [ -lines ] [ +linenumber ] [ +/pattern ]
[ filename ... ]

more 命令在终端上显示文本文件的内容,但一次只显示一屏。正常情况下,每显示一
屏暂停一次,并在荧屏的底部显示“—More—”的字样。
mtools——在 UNIX 系统中访问 DOS 格式的文件
mtools 是一个公共工具集,这些工具能让 UNIX 系统在 MS-DOS 文件系统系统上(通
常是软盘)读、写及移动文件,这些命令尽可能做得像真正在 MS-DOS 上操做一样。例如,
把一些子目录从一个子目录移动到另一个目录里。我们还可以访问以下站点找到关于 mtool
的工具包:
http://mtools.ltnb.lu/mtools-3.9.1.tar.gz
http://www.tux.org/pub/knatf/mtools-3.9.1.tar.gz
ftp://swnsite.unc.edu/pub/Linux/utils/disk-management/mtools.3.9.1.tar.gz
mv——移动/重新命名文件
mv [options] source dest
mv [options] source... directory
Options:
[-bfiuv] [-S backup-suffix] [-V {numbered,existing,simple}]
[--backup] [--force] [--interactive] [--update] [--verbose]
[--suffix=backup-suffix]
[--version-control={numbered,existing,simple}] [--help][--version]

mv 命令将源文件移到目标文件。源文件与目标文件不应该同名。如果目标文件不是目
录,则在该文件前只能有一个文件;如果是目录,则可以指定多个文件;如果目标文件不存
在,mv 命令就创建该目标文件;如果目标文件存在而且不是一个目录,该文件将被覆盖;
若是目录,源文件就被移到该目录里。

实例 A.40
1 mv file1 newname
2 mv –i test1 test2 train

说明
1 将目标文件 file1 更名为 newname。如果 newname 文件已经存在,该文件就被覆盖。

PDF created with pdfFactory Pro trial version www.pdffactory.com


502 附录 A

2 把文件 test1 和 test2 移到 train 目录里,-i 选项指明采用交互模式,也就是在移动文件前询求用


户确认。

nawk——模式搜索处理语言
nawk [ -F re ] [ -v var=value ] [ ’prog’ ] [ filename ... ]
nawk [ -F re ] [ -v var=value ] [ -f progfile ][ filename ... ]

nawk 在输入文件中搜索所有含有匹配模式的行。命令字符串必须用单引号括起,防止
Shell 误翻译。awk 程序规定了一系列模式/动作语法,这些规则常用于过滤文件、pipe(管
道)以及标准输入中指定的信息。
newgrp——注册新属组
newgrp [-] [ group ]

newgrp 命令通过改变用户真实属组 id,使得用户能注册新的属组。注册后,用户依然


保持登录状态而且当前的目录不改变。newgrp 命令的运行会用一个新的 Shell 替代当前 Shell,
但是这样的结果可能会因错误(未知组)导制一些命令的终止。
news——显示新闻项
news [ -a ] [ -n ] [ -s ] [ items ]

news 命令可以使用户获得最新发生的新闻和事件。按惯例,这些事件将存储在/var/news
中。如果不指定任何参数,news 命令就按最新访问时间排序,打印出/var/news 下的所有文
件内容,并且在文件前加上合适的标题。
nice——以低优先级来运行命令
nice [ -increment ] command [ arguments ]

/user/bin/nice 用较低的 CPU 调度优先级来执行一个命令。调用的程序(通常是用户的


Shell)必须属于分时类型程序。命令将在分时环境中执行。增量 increment 的默认值是 10。
除非是超级用户,增量值 increment 必须在 1~19 之间。nice 也是 tc/csh 的内置命令。
nohup——防止命令挂起或退出
/usr/bin/nohup command [ arguments ]

有三个不同版本的 nohup。适用于 C Shell 的 nohup 是一个在/usr/bin/nohup 中的可执行


文件,当使用 Bourne Shell 时可用。Bourne Shell 版本的 nohup 在执行时能避免 HUP(挂起)
和 TERM(程序终结) 。假如标准输出是一个终端,它将会被重新导向文件 nohup.out。标准
错误被重新导向跟在标准输出之后。优先级增加了 5。nohup 应该由 Shell 激活(或者由下一
个用户从输入来激活)来防止它对中断信号作处反应。

实例 A.41
nohup lookup &

说明
lookup 程序将会在后台运行直到终结,中途即使用户退出注册也不会停止。任何生成的结果都
将会放到在当前目录下一个名为 nohup.out 的文件中。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 503

od——把转储文件设置为八进制格式或其他格式
oodd [-abcdfhiloxv] [-s[bytes]] [-w[bytes]] [-A radix] [-j bytes]
[-N bytes] [-t type] [--skip-bytes=bytes]
[--address-radix=radix] [--read-bytes=bytes] [--format=type]
[--output=duplicates] [--strings[=bytes]] [--width[=bytes]]
[--traditional] [--help] [--version] [file...]

od 以一个或多个格式显示文件名,以什么方式显示是由第一个参数决定的。假如第一
个参数丢失了,-o 就是默认值。例如,此文件就能以八进制、ASCII 码、十进制或者十六进
制等等来显示。
pack(pack、pcat、unpack)——压缩或解压文件
pack [ - ] [ -f ] name ...
pcat name ...
unpack name ...

pack 压缩文件。只要有可能而且对程序有帮助的话,所有的输入文件名都可被替换成一
个压缩文件 name.z,这个文件具有和原先文件相同的访问/存贮方式、访问/修改日期和相同
的属主。一般文本文件能压缩到原大小的 60%~75%。除非 pcat 不能被当成是一个过滤器,
pcat 适用于压缩文件而 cat(1)适用于普通的文件。指定文件解压后写到标准输出里。这样,
浏览一个名为 name.z 的压缩文件可以用“pcat name.z”,而如果这个文件是用 pack 创建的
话就直接可以用“pcat name.unpack”来解压文件。
passwd——修改登录密码和密码属性
passwd [ name ]
passwd [ -d | -1 ] [ -f ] [ -n min ] [ -w warn ][ -x max ] name
passwd –s [ -a ]
passwd –s [ name ]

passwd 命令能修改密码或显示和用户登录密码相关的属性。此外,有特权的用户能使
用该命令设置或修改密码,或修改任何登录用户的密码属性。
paste——合并几个文件的同一行或数行,合并同一个文件中相连的几行为一行
paste filename1 filename2...
paste –d list filename1 filename2...
paste –s [ -d list ] filename1 filename2...

paste 能把指定输入文件 file1、file2……等数个文件的相应行(一行或数行都可)合并


起来。此命令把这些文件当作是“一项/列”或者是一个表格内的好几项/列来看待,并把它
们水平的合并到一起(见前面的 cut 命令) 。

实例 A.42
1 ls | paste - - -
2 paste –s –d"\t\n" testfile1 testfile2
3 paste file1 file2

说明
1 文件分为三项列出并用制表符 Tab 粘贴到一起。

PDF created with pdfFactory Pro trial version www.pdffactory.com


504 附录 A

2 用 Tab 制表符把两行合并成一行,而且这个新的行即起了分隔符的作用。也就是说,头两行用
Tab 制表符粘连在一块,下两行用新行连接,再下两行又用制表符 Tab 来粘连,如此类推。-s 开
关使得 testfile1 中相连的行先粘连到一起,然后就是 testfile2……如此类推。
3 file1 中的一行和 file2 中的一行用制表符相连到一起,这样文件的行就犹如两项/列了。

pcat——见 pack
pine——适用于 internet 新闻和 email 的程序
pine [ options ] [ address, address ]
pinef [ options ] [ address, address ]

pine 是一个面向荧屏的信息处理工具。在默认状态下,pine 对新手设置了有限的功能使


用权限,它还设置更多更高的选项和权限、 “超级用户”设置和个人自定义的设置。pinef 是
pine 的一个变体,它使用功能键来代替单字母命令。pine 的基本功用设置包括:浏览、存盘、
导出、删除、打印、回复和转发信息。
pg——同时在一页显示多个文件
pg [ -number ] [ -p string ] [ -cefnrs ] [ +linenumber ]
[ +/pattern/ ] [ filename ... ]

pg 命令是一种过滤器,它使得你能在一个终端上同时显示多个文件于一屏上。假如没
指定文件名或是 pg“恰好遇到”一个文件,它就从标准输入读入。每一屏最后都是光标。
键入用户键入一个 RETURN,则显示另一屏。此命令允许备份和回顾之前的操作(参阅前
面的“more”
)。
pr——打印文件
pr [[-columns] [-wwidth] [-a]] [-eck] [-ick] [-drtfp]
[+page] [-nck] [-ooffset] [-llength] [-sseparator]
[-hheader] [-F] [filename ... ]
pr [[-m] [-wwidth]] [-eck] [ick] [-drtfp] [+page] [-nck]
[-ooffset] [-llength] [-sseparator] [-hheader] [-F
[filename1 filename2 ...]

pr 命令能通过不同的格式选项来以各种不同的格式来打印文件内容。在默认状态下,列
表被发送到 stdout,并被分成了几页。每一页的开头是页码、文件最后一次修改的日期时间
和文件名。假如不指定选项的话,默认的格式为 66 行,其中前后各有 5 行的开头和结尾。

实例 A.43
pr –2dh "TITLE" file1 file2

说明
一页分成两半,打印两大列,其开头为“TITLE”,代表 file1 和 file2。

ping——报告一个远程系统是否可达和接通
ping [-dfnqrvR] [-c count] [-i wait] [-1 preload] [-p pattern]
[-s packetsize]

ping 发送 ICMP ECHO_REQUEST 数据包给一个主机,等待回应以查看此主机是否可

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 505

达。ping 用于追踪网络的可通性。假如 ping 接收不到任何回应数据包,它就会以数字 1 的


状态退出;数据包出错就以数字 2 状态退出,否则就以状态 0 退出。这样,就可以通过查看
退出状态值来确定主机是否可达。
该程序用于网络的测试、评估和管理。由于此命令会加重网络负载,所以在正常操作或
自动脚本上使用 ping 并不是很好。
ps——报告进程状态
ps [ - ] [ lujsvmaxScewhrnu ] [ txx ] [ 0[H-]k1[[+|-]k2...]]

ps 显示正在使用的进程的信息。没有选项的时候,ps 就输出关于终端的进程信息。输
出仅仅包括进程 id、终端的标识符、总运行时间和命令名。否则,显示的信息是由选项来控
制的。ps 选项与 ATT 和 Berkeley 类型版本的 UNIX 中的选项不是一样的。

实例 A.44

1 % ps 1
FLAGS UID PID PPID PRI NI SIZE RSS WCHAN STA TTY TIME COMMAND
100 501 496 495 12 0 1412 820 sigsuspend S p0 0:00 -tcsh
100000 501 1165 496 13 0 952 492 R p0 0:00 ps 1
100 501 506 505 0 0 1448 856 sigsuspend S p1 0:00 –tcsh
100000 501 842 506 1 0 1300 848 do_select S p1 0:00 vi atfi

2 % ps -u
warning: '-' deprecated; use 'ps u', not 'ps –u'
USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND
ellie 496 0.0 1.2 1416 824 p0 S 15:23 0:00 -tcsh
ellie 506 0.0 1.3 1448 856 p1 S 15:23 0:00 -tcsh
ellie 842 0.0 1.3 1300 848 p1 S 16:07 0:00 vi atfile
ellie 1166 0.0 0.7 856 492 p0 R 16:25 0:00 ps –u
%

3 % ps aux | grep '^linda' ucb


4 % ps –ef | grep '^ *linda' att

说明
1 ps 1 选项显示一长列关于每个进程的信息。
2 ps 不必要在选项前加一个横杠。实际上,假如你在选项前加上横杠的话,会出现警告信息。u 选
项是加上了进程的用户名、开始时间、CPU 使用率和已使用内存率等项(Linux 的) 。
3 显示所有正在使用的进程。而且,当该进程属主为用户 linda(linda 这用户名在每一行的开头)
时,通过管道符将输出重定向为 grep 程序的输入。
4 和第一个例子相同,仅仅是 ATT 版本。

pstree——以树状结构显示进程
pstree [-a] [-c] [-h] [-1] [-n] [-p] [-u] [-G|-U][pid|user]
pstree –V

pstree 以树状结构显示进程。假如 pid 忽略的话,则此树状结构以 pid 或 inti 为根。假如


指定了用户名,则进程的树状结构以当前用户指定的进程为根进程。pstree 通过把相同的树
枝进程放到一个方括号里,并在此括号前加上那些相同的进程的次数,而把它们连接起来。
init-+-getty

PDF created with pdfFactory Pro trial version www.pdffactory.com


506 附录 A

|-getty
|-getty
'-getty
成为
init---4*[getty]
pwd——显示当前使用的目录名
quota——显示用户磁盘使用情况和限制

quota [ -guvv | q ]
quota [ -uvv | q ] user
quota [ -gvv | q ] group

quota 用来显示用户磁盘使用情况和限制。默认状态下,只显示当前用户所分的磁盘配
额的使用情况。
-g 显示当前用户所在属组的盘配额的使用情况
-u 一个选项标志,和默认时的功用一样
-v 当文件系统中储存的内容没有分配时,显示文件系统的配额情况
-q 显示扼要信息,只包括文件系统分配完后的信息
rcp——复制远程文件
rcp [-px] [-k realm] file1 file2
rcp [-px] [-r] [-k realm] file ... directory

rcp 命令能在机器间复制文件,其形式为:
remothostname:path
user@hostname:file
user@hostname.domainname:file

实例 A.45
1 rcp dolphin:filename /tmp/newfilename
2 rcp filename broncos:newfilename

说明
1 从远程主机上复制文件 dolphin 到本地的/tmp 目录,并改名为/newfilename。
2 复制本地文件 filename 到远程主机 broncos 上,并改名为 newfile-name。

rdate——通过网络得到时间和日期
rdate [-p] [-s] [host...]

rdate 通过 TCP 从另外一台机器(该机器认同 RFC 868 规定的协议)上得到当前的时间


和日期。加上-p 选项,rdate 仅显示从另外一台机器上得到的时间。-s 选项,把从远程的机
器上得到的时间设置到本地的机器上。但只有超级用户才能重设时间。在 ctime(3)格式中,
每个系统的时间都会返回。

实例 A.46
1 rdate homebound atlantis
(Output)

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 507

[homebound] Tue Jan 18 20:35:41 2000


[atlantis] Tue Jan 18 20:36:19 2000

rgrep——递归、高亮度的 grep 程序
rgrep [ options ] pattern [file] ......

rgrep 不像 grep 和 egrep,它能递归递降目录。在 UNIX 系统中使用此种搜索,传统的方


法是在有 grep 的连接中使用 find 命令。使用 rgrep 能得到更好的效果。参见 xargs 命令。

命令行选项:
-? 额外的帮助(可用-?避免 Shell 在某些系统内扩展)
-c 计数匹配
-h 高亮度匹配(ANSI 假定可兼容终端)
-H 输出匹配而不是包含匹配的整行
-i 忽略 case
-l 仅仅列出文件名
-n 输出匹配的行数
-F 后继链接
-r 递归扫描目录树
-N 不执行递归扫描
-R pat 除了不检查匹配 pat 的文件外,递归扫描目录树(和-r 命令功用一样)
-v 显示不匹配指定模式的行
-x ext 仅检查由 ext 扩展的文件
-D 输出所有可搜索的目录。该选项仅用于测试。使用该选项的话,就不对任何文
件做文件内的字符串的查找
-W len 显示由 len 规定的字符串长度的行
. 除了新行外,匹配任何字符
\d 匹配任何数字
\e 匹配任何 ESC 字符
* 匹配 0 字符,包括回车前所有的 0 字符
+ 匹配 1 字符,包括回车前所有的 1 字符
? 匹配回车前的 0 和 1 字符
^ 匹配行首
$ 匹配行尾
[…] 匹配括号里的任何单个字符。例如,[-02468]匹配“-”或者任何单个数字;[-0-9a-z]
匹配“-”和任何在 0~9 之间的数字以及 a-z 间的任何字母
\{…\} 用于重复。例如, …x\{9\},匹配 9 个 x 字符
\(…\) 用于倒回参考。在\(…\)里的模式被标记储存起来。常规表达式从左边数起,可
以做 9 个标记。如要重置已储存的模式,则使用\1, \2, …\9
\2 \1,=,…\9 匹配 nth\(…\)指定的。例如,\([\t][a-zA-Z]+\)\1[\t],匹配连续重复的任何字符

PDF created with pdfFactory Pro trial version www.pdffactory.com


508 附录 A

实例 A.47

1 rgrep –n –R '*.c' '^int'


2 rgrep –n –xc '^int'

说明
1 在当前目录(包括其子目录)搜寻所有带有.c 扩展名的文件。搜寻在行首含有 int 匹配的行,并
将该行以及其行数都打印出来。
2 搜寻所有带有.c 扩展名的文件,打印行首含有“int”的行,还有该行的行数(同上)。

rlogin——远程登录
rlogin [ -L ] [ -8 ] [ -ec ] [ -1 username ] hostname

用 rlogin 命令可以从用户终端发起,与名为 hostname 的远端机器建立远程登录会话。


主机名存在于主机数据库中,这些主机名可能由/etc/hosts 文件指定, 或由网络信息服务(NIS)
提供,即域名服务器,或者由它们二者共同提供。每个主机都由对应的一个正式名称(数据
库记录中的首项),可能的话还有一个或多个非正式名称。无论时正式名称还是非正式名称
都可以作为命令中的 hostname 参数。/etc/host.equiv 文件中保存的是一个可信任的远程主机
名的清单。
rm——从目录中删除文件
rm [-f] [-i] filename...
rm –r [-f] [-i] dirname...[filename...]

rm 命令用于删除目录中的文件(用户拥有写权限)。如果删除的文件是一个符号链接文
件,则仅删除指向的文件或目录。如果用户对一个目录有写权限,那么无需对指向该目录的
链接有写权限,也可删除该链接。

实例 A.48

1 rm file1 file2
2 rm –i *
3 rm –rf dir

说明
1 删除当前目录下的 file1 和 file2 文件。
2 删除当前目录下的所有文件,但删除文件前显示确认提示。
3 递归删除 dir 目录下的所有文件和目录,并忽略所有错误信息。

rmdir——删除一个目录
rmdir [-p] [-s] dirname...

rmdir 用于删除一个空目录。如果指定-p 选项,则连带删除其父目录。


rsh——激活/启动一个远程 shell
rrsshh [--KKddnnxx] [--kk _r_e_a_l_m] [--ll _u_s_e_r_n_a_m_e] _h_o_s_t [command]

rsh 用于与指定的主机建立连接,并执行指定的命令。rsh 命令把本地标准输入作为远程

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 509

命令的输入,在本地标准输出上显示远程命令的标准输出,并在本地也同样复制远程命令的
标准错误。本地的中断、退出和终止信号也一样传输给远程命令。当远程命令执行完毕,rsh
即终止运行,若不指定命令,rsh 就用 rlogin 命令将当前用户登录到远程主机。

实例 A.49

1 rsh bluebird ps -ef


2 rsh –1 john owl ls; echo $PATH;cat .profile

说明
1 与远程机器建立连接,并显示该机器正在运行的进程。
2 以 john 登录远程机器,并执行例中的三个命令。

ruptime——显示本地机器的主机状态
ruptime [ -alrtu ]

ruptime 用于显示局域网上的每台机器的状态。这些状态信息是通过接收局域网上的机
器发出的广播包得到的。5 分钟内没有发出状态广播的机器将被视为已关机。通常,状态信
息列表按主机名排序,但也可以通过指定选项来改变显示顺序。
rwho——显示登录本地机器的用户名
rwho [ -a ]

rwho 命令的输出结果类似于 who(l)命令。但它可以列出局域网内所有机器的登录用户,


但是这个命令不能通过网关进行工作,而且这些机器上必须存在/ver/spool/who 目录和运行
rwho 保护程序。如果在 5 分钟内没有接到对方机器的状态回报,who 命令即假定该机器已
经关机,因而也不会显示该机器上登录用户的最新报告。如果用户与系统不发生任何交互动
作超过一分钟,rwho 命令将显示该用户的闲置时间。除非指定-a 选项,否则将不显示闲置
时间达 1 小时以上的登录用户。
script——创建一个终端对话上的直接输入脚本
script [ -a ] [ filename ]

script 把显示在终端上的所有东西创建为一个脚本。脚本将被写入到一个文件里。如果
没有给出文件名,就会储存到一个名为 typescript 的文件里。当 Shell 退出或键入 Ctrl-D 时,
脚本结束。

实例 A.50
1 script
2 script myfile

说明
1 在新 Shell 中启动一次脚本会话。所有在终端上显示的内容都被写入到名为 typescripte 的文件中,
输入^d 或 exit 即可结束本次会话。
2 在新 Shell 中启动一次脚本会话。所有在终端上显示的内容都被写入到名为 myfile 的文件中,输
入^d 或 exit 即可结束本次会话。

PDF created with pdfFactory Pro trial version www.pdffactory.com


510 附录 A

sed——流编辑器(参见第 4 章)
sed [-n] [-V] [--quiet] [--silent] [--version] [--help] [-e script]
[--expresion=script] [-f script-file] [--file=script-file]
[script-if-no-other-script] [file...]

sed 将指定文件(默认为标准输入)根据给出的命令脚本对该文件进行编辑,并将结果
写到标准输出,但不改变源文件(参阅第 4 章关于 sed 的介绍)sed 与其他文本编辑器的区
别在于他可以通过管道对文本进行过滤。

实例 A.51

1 sed 's/Elizabeth/Lizzy/g' file


2 sed '/Dork/d' file
3 sed –n '15,20p' file

说明
1 将所有“Elizabeth”字符串替换为“Lizzy”,并在终端上显示替换过程。
2 删除所有含“Dork”的行,并在屏幕上显示其余文本行。
3 显示 15 行至 20 行。

size——显示目标文件的段字节长度
size [ -f ] [ -F ] [ -n ] [ -o ] [ -V ] [ -x ] filename...

size 命令以字节为单位显示目标文件载入 ELF 或 COFF 段的大小,即显示,文本、数据


和 bss(非初始化数据)的段长度,或是这些段的长度。
sleep——挂起执行程序一段时间
sleep time

sleep 把执行程序挂起若干秒,主要用于在特定的一段时间后执行该程序。

实例 A.52

1 (sleep 105; command)&


2 (In Script)
while true
do
command
sleep 60
done

说明
1 返回提示符状态 105 秒后执行 Command 命令。
2 进入循环并执行 Command 命令,然后挂起 1 分钟,最后再次进入循环。

sort——对文件进行排序、合并
sort [-cmus] [-t separator] [-o output-file] [-T tempdir]
[-bdfiMnr] [+POS1 [-POS2]] [-k POS1[,POS2]][file...]
sort {--help,--version}

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 511

sort 命令对指定文件中的 ASCII 码文本行进行排序,并将结果写到标准输出中。比较是


基于输入文件中一个或多个关键字进行的。默认情况下,只对于一个关键字,即整行进行排
序。排序的次序是按机器默认的标准,逐字按字母排列顺序相比较出来的。

实例 A.53
1 sort filename
2 sort –u filename
3 sort –r filename
4 sort +1 –2 filename
5 sort –2n filename
6 sort –t: +2n –3 filename
7 sort –f filename
8 sort –b +1 filename

说明
1 以字母顺序排列 file 文件中的文本行。
2 排列并合并相同的文本行。
3 反向排序。
4 从第一个域(域间一空格分隔,域从 0 开始记数)起到第 2 个域进行排序,而不是一直比较至行
末。
5 以数字为序对第 3 个域进行排序。
6 以数字为序对第 3 个域~第 4 个域进行排序,且指定冒号:为域分隔符。
7 忽略大小写排序。
8 对第 2 个域进行排序,并且删除行首空格。

spell——查找拼写错误
spell [ -blvx ] [ -d hlist ] [ -s hstop ] [ +local_file ] [ filename]...

spell 命令通过查阅拼写列表文件来指出给定文件中的单词拼写错误。如果某个单词没有出
现在拼写列表中,或是由拼写列表中的单词派生而来(变体、前缀和/或后缀) ,那么这个单词
就会被显示在标准输出上。如果没有给定输入文件,spell 命令将从标准输入中接收单词输入。
split——分割文件
split [ -n ] [ filename [ name ] ]

split 命令从 filename 文件中取得输入,并把文件分割成许多 n 行的文件输出。第一个输


出文件名为源文件名加上 aa,如此类推直到 zz(即最大可达 676 个文件) 。输出文件名应比
系统最大文件名长度少 2[参见 statvfs(2)]。如果没有指定输出文件名,则将默认使用 x 作为
输出文件名(输出文件可能为 xaa、xbb…) 。

实例 A.54

1 split –500 filea


2 split –1000 fileb out

说明
1 split 将文件 filea 分割为数个 500 行大小的文件,输出文件为 xaa、xbb……
2 split t 将文件 filea 分割为数个 500 行大小的文件,输出文件为 outaa、outbb……

PDF created with pdfFactory Pro trial version www.pdffactory.com


512 附录 A

string——在目标文件中搜索可打印字符
strings [ -a|-|--all ] [-f|--pring-file-name] [ -o ] [--help]
[ -v|--version ] [-n min-len| min-len|--bytes=min-len]
[-t {o,x,d} [--target=bfdname] [--radix={o,x,d}|] filename... ]

string 命令在目标文件中搜索 ASCII 字符串。在这里,字符串是指超过 4 个可打印字符


并且以换行符结束的字符序列。string 命令常用于识别随机目标文件和其他字符串。

实例 A.55
strings /bin/nawk | head –2

说明
显示可执行二进制文件/bin/nawk 前两行所含 ASCII 文本。

stty——设置终端
stty [ settings...]
stty { -a, --all, -g, --help, --save, --version} ] [ -g ] [ modes ]
stty 命令用于对当前标准输入的设备进行终端 I/O 配置。如果不指定参数,则输出当前
配置。

实例 A.56

1 stty erase <Press backspace key> or ^h


2 stty –echo; read secretword; stty echo
3 stty –a (ATT,Linux ) or stty –everything (BSD)

说明
1 将退格键(backspace)作为删除键。
2 关闭回显;接收用户输入;打开回显。
3 列出 stty 命令的所有选项。

su——切换为超级用户或其他用户
su [-flmp] [-c command] [-s shell] [--login] [--fast]
[--preserve-environment] [--command=command] [--shell=shell ]
[ - ] [--help] [--version] [ username [ arg ... ] ]

su 命令可以在不注销当前用户的情况下,切换用户角色。默认用户为超级用户。su 命
令要求用户提供相应的密码,如果密码无误,将以实用户名登录新的 Shell 进程。该用户属
组 ID 和其他属组即时生效。并使用用户密码文件中指定的登录 Shell 作为当前的登录 Shell。
如果密码文件中没有指定登录 Shell,默认使用 bsh。如果想回到先前的登录用户,只需键入
Ctrl-D 键即可结束当前登录 Shell。指定-选项可以选择完整的登录过程。
sum——计算文件的检验和
sync——更新 superblock 并把更新内容写入硬盘
tab——设置终端的制表符
tail——显示文件的末尾
tail [-c [+]N[bkm]] [-n [+]N] [--bytes=[+]N[bkm]] [--lines=[+]N]

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 513

[--follow] [--quiet] [--silent] [--verbose] [--help] [--version]


[file...]]

如果在数字 n 前有加号,tail 命令将显示文件 n 行后的内容。如果是减号,则显示末 n


行内容。与 UNIX 上的版本不同,Gnu 版本可以输出不受数据量限制。

实例 A.57
1 tail +50 filex
2 tail –20 filex
3 tail filex

说明
1 显示文件 filex 第 50 行后的内容(包括第 50 行)。
2 显示文件 filex 的末 20 行。
3 显示文件 filex 的末 10 行。

talk——与其他用户交谈
talk username [ ttyname ]

talk 命令是一个可视化的交谈程序。它可以把你从终端输入的文本传送到另一用户终端上。

实例 A.58
talk joe@cowboys

说明
请求与名为 cowboys 机器上的用户 job 建立谈话。

tar——存储和恢复档案文件(通常是磁带)
tar [ - ] c|r|t|u|x [ bBefFhilmopvwX0134778 ] [ tarfile ]
[ blocksize ] [ exclude-file ] [ -I include-file ]
filename1 filename2 … -C directory filenameN …

tar 命令用于生成/解开档案文件(通常称为 tar 文件)


。tar 文件可以是 UNIX/Linux 的普
通文件,也可以是磁带设备。该命令常用于将文件拷贝到软磁盘上。欲获得完整的选项列表
信息,请参见在线帮助手册。
实例 A.59
1 tar cvf /dev/diskette .
2 tar tvf /dev/fd0
3 tar xvf /dev/fd0
4 tar cvf mytarfile .

说明
1 将当前目录下(由.指定)的所有文件拷贝至磁带设备/dec/diskette 软盘中,并显示拷贝的文件。
2 显示磁带设备/dev/fd0 软盘中的内容列表。
3 解开磁带设备/dev/fd0 软盘的内容。
4 将当前目录下的所有文件制成名为 mytarfile 的档案文件。

PDF created with pdfFactory Pro trial version www.pdffactory.com


514 附录 A

tee——重复标准输出
tee [ -ai ] [--append] [--ignore-interrupts] [--help] [--version]
[ filename... ]

tee 命令将其标准输入拷贝到标准输出和一个或多个文件中。如:在 ls|tee outfile 命令中,


输出既显示在屏幕上又被拷贝到 outfile 文件中。
实例 A.60
data | tee nowfile

说明
date 命令的输出结果既显示在屏幕上又被拷贝到 nowfile 文件中。

telnet——与远程主机进行通信

实例 A.61
telnet necom.com

说明
与名为 necom.cn 的主机建立一次会话。

test——计算表达式/检查文件类型
test [expr]
test [--help, --version]

test 命令计算指定表达式的值,并通过返回值指明表达式为真(零)还是为假(非零) 。
现在,bsh 和 ksh 内建的 test 命令版本可以提供字符串、数字和文件检查。csh 和 tcsh 包含大
部分的内建检查功能。

实例 A.62

1 test 5 gt 6
2 echo $? (Bourne and Korn Shells)
(Output is 1, meaning the result of the test is not true.)

说明
1 test 命令以数字大小判断:5 是否大于 6。
2 $变量值即为上一条命令的返回状态,如果状态为非零,则 test 命令的结果为非真。如果为零,
则结果为假。

time——显示当前 Shell 及其子进程采用的时间简报


timex——为执行命令计时;显示进程数据和系统活动进程
timex [ -o ] [ -p [ -fhkmrt ] ] [ -s ] command

timex 命令首先执行指定的命令,然后以秒为单位显示命令执行的总耗时、用户时间和
活动时间。如果指定选项的话,还可以列出命令执行过程的统计数据及其子进程的简要信息。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 515

该命令还能显示执行期间系统的总活动时间。timex 将结果输出到标准错误。
top——显示 CPU 峰值处理能力
top [-] [d delay] [q] [c] [S] [s] [i]

top 命令可以在实时处理和 CPU 高强度任务中对 CPU 进行活动检测。


touch——更改文件的访问时间和/或修改时间
touch [ -amc ] [ mmddhhmm [ yy ] ] filename...

touch 命令按指定参数更新指定文件的访问时间和修改时间。如果指定的文件不存在,
则建立该文件;如果没有指定时间则使用当前时间。

实例 A.63
touch a b c

说明
创建三个文件 a、b 和 c。如果这些文件存在,则更新文件的修改时间戳。

tput——初始化终端或查询终端信息(terminfo)数据库
tput [ - Ttype ] capname [ parms...]
tput [ -Ttype ] init
tput [ -Ttype ] reset
tput [ -Ttype ] longname
tput –S <<

tput 命令通过 terminfo 数据库设置 Shell 的终端依赖特性和信息,初始化/重置终端或返


回请求终端类型的完整名称。

实例 A.64

1 tput longname
2 bold=‘tput smso‘
unbold=‘tput rmso‘
echo "${bold}Enter your id: ${offbold}\c"

说明
1 显示 terminfo 数据库中终端的完整名称。
2 设置 shell 变量 bold,这样可以高亮显示输出文本。然后设置变量 unbold,恢复正常显示。“Enter
your id:”将高亮显示,而接下来显示的文本则正常显示。

tr——转换字符
tr [ -cds ] [ string1 [ string2 ] ]

tr 命令替换或删除标准输入中的选定字符串,然后写到标准输出。输入中的 string1 被映
射为输出中 string2,在八进制数字前加斜杠表示对应的 ASCII 码字符。如果 string2(包括重
写字符)比 string1 要短,那么在 string1 中有,而 string2 中没有对应的字符不会被转换。可
以在八进制数字前加反斜杠表示对应的 ASCII 码字符。

PDF created with pdfFactory Pro trial version www.pdffactory.com


516 附录 A

\11 制表符
\12 换行符
\042 单引号
\047 双引号

实例 A.65

1 tr 'A' 'B' < filex


2 tr '[A-Z]' '[a-z]' < filex
3 tr –d ' ' < filex
4 tr –s '\11' '\11' < filex
5 tr –s ':' ' ' <filex
6 tr '\047' '\042'

说明
1 将文件中的 A 字符转换为 B 字符。
2 将所有大写字母转换为小写字母。
3 删除文件中所有空格。
4 将多个制表符替换(压缩)为单个制表符。
5 将多个冒号:替换(压缩)为单个多个冒号:
6 从标准输入获取输入,并将双引号替换为单引号。

true——提供成功状态
true 命令不作任何事情,它总是返回零值(代表成功)
。在 bsh 和 ksh 的 Shell 脚本中常
用于启动一个无限循环。
while true
do
command
done

tsort——拓扑排序
/usr/ccs/bin/tsort [filename]

tsort 命令对输入文件说明的部分序列进行总排序,并写到标准输出上。如果不指定输入
文件,那么从标准输入中获得输入。输入由项(非空字符串)和对组成(以空格作为分隔符) 。
多个不同的项对表明了顺序,这些可标识的项对只是表示存在,但并没有排序。
tty——显示终端名
tty [ -l ] [ -s ]

tty 命令显示用户终端的路径名
umask——设置文件即创建权限掩码
umask [ ooo ]

umask 命令可以将用户的文件创建权限掩码设置为由 000 指定的值。这三个八进制数字


分别对应属主、属组和其他用户的读/写/执行权限。系统用于创建一个文件时使用的权限值
减去该值中对应的位,即是文件最后创建的最终权限。例如,umask 022 命令可以去掉属组
和其他用户的写权限, (如:用 777 权限创建的文件变成 755,666 创建的文件则变成 644。
如果不指定 000 值,则显示完全的掩码,umask 由 Shell 识别和调用。

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 517

实例 A.66
1 umask
2 umask 027

说明
1 显示当前的文件权限掩码。
2 用目录的权限值(777)。减去掩码(027),就得了 750。文件得权限值为 666,减去掩码 027,
得 640。当创建文件时,文件被赋予由 umask 得出的权限。

uname——显示当前机器名
uname [ -amnprsv ]
uname [ -S system_name ]

uname 命令将当前机器的信息写到标准输出。如果不指定选项,uname 显示当前操作系


统名。通过指定选项可以让 uname(2)或 sysinfo(2)返回选定的信息。

实例 A.67

1 uname -n
2 uname -a

说明
1 显示主机名。
2 显示机器名、网络名、操作系统的 release、操作系统名及其版本,相当于同时指定-m、-n-r 和-s
选项。

uncompress——解压由 compress 命令压缩的文件


uncompress [ -cFv ] [ file . . . ]

实例 A.68
uncompress file.z

说明
恢复文件 file.Z 为其原始状态,即压缩前文件的状态。

uniq——在文件中查找重复行
uniq [ [ -u ] [ -d ] [ -c ] [ +n ] [ -n ] ] [ input [ output ] ]

uniq 读入输入文件,对临近的行进行比较。正常情况下,重复的第二行以及后续行均被
删除,而将其余的行写入输出文件。输入文件和输出文件不能同名。

实例 A.69
1 uniq file1 file2
2 uniq –d –2 file3

PDF created with pdfFactory Pro trial version www.pdffactory.com


518 附录 A

说明
1 删除文件 file1 中相邻的重复行,并将结果输出到文件 file2。
2 显示从第 3 个域起重复的行。

unit——转换数量的度量衡
unit 命令用于转换各式各样的标准单位为其他度量衡相应的单位。该命令与用户的交互
风格如下:
You have: inch
You want: cm
* 2.540000e+00
/ 3.937008e-01

unpack——解开由 pack 命令创建的包


unpack 解开由 pack 命令创建的包。该命令搜索由 name.z
(name 由命令中文件参数指定),
如果指定的文件名以.z 结尾,即搜索该文件,不再添加.z 后缀进行文件匹配搜索。如果该文
件为包文件,那么以解包后的文件替代之,而且将文件的.z 后缀去掉。但仍然保留与源包相
同的访问、修改时间和属主。
uucp——拷贝文件到其他系统,UNIX 系统对拷
uucp [ -c | -C ] [ -d | -f ] [ -ggrade ] [ -j ] [ -m ] [ -nuser ]
[ -r ] [ -sfile ] [ -xdebug_level ] source-file destination-file
uucp 将 source-file 参数指定文件拷贝到由 dentination-file 参数指定的目标中。
uuencode [ source-file ] file-label
uudecode [ encoded-file ]

uuencode-uuencode,uudecode-为通过 E-mail 传送二进制文件,对二进制文件进行编码/


解码,uuencode 命令将二进制文件转换为能通过 mail 传送的 ASCII 编码。label 参数指明编
码转换后的输出文件。如果不指定任何文件,则从标准输入获得输入进行编码。uudecode
读入经编码转换后的文件,去掉 mail 程序添加的首末行,依照编码头部指定的文件名、权
限和属主恢复源二进制文件。编码转换后的文件是一个纯文本文件,我们可以用任何文本编
辑器对它进行编辑,但是最好只改变头部的模式或 file-label(文件标签),以免破坏被编码
转换的二进制代码。

实例 A.70
1 uuencode mybinfile decodedname > uumybinfile.tosend
2 uudecode uumybinfile.tosend

说明
1 第一个参数 mybinfile 为欲进行编码的文件,第 2 个参数则用于指定, 通过邮件发送后经 uudecode,
命令解码时生成的文件名。uumybinfile.tosend 文件将通过邮件系统被发送出去。
2 该命令对编码文件进行编码,并创建由 uucode 命令的第 2 个参数指定名称的文件。

wc——统计行数、单词数和字符数
wc [ -lwc ] [ filename ... ]

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 程序员的实用工具 519

wc 统计文件或标准输入(如果不指定任何输入文件的话)的行数、单词数和字符数,
在这里,单词的定义是:以空格、制表符或换行符分隔的字符串。

实例 A.71

1 wc filex
2 who | wc -1
3 wc –1 filex

说明
1 显示 filex 文件的行数、单词数和字符数。
2 who 命令的输出结果被重定向到 wc,然后由 wc 命令显示行数。
3 显示文件 filex 的行数。

what——从一个文件中找出 SCCS 版本的信息,并打印在@(#)模式后面搜寻到的信息


what [ -s] filename

what 在每个文件里搜寻模式 @ (#), 通过这个模式,SCCS 对 %Z% 关键字进行命令替


换。然后输出跟在 “>、新行、\或空字符/字节后的所有东西。
which(UCB)——定位命令并显示其路径名和别名
which [ filename ]

which 命令查找与 name 参数命令指定的命令匹配的文件。若给定的参数为命令别名,


那么该别名经 Shell 解释转换后在拥护的路径进行查找。别名解释和路径由且只由用户
的.cshrc 文件指定。
whereis(UCB)——定位命令的源文件、二进制文件和帮助手册文件
whereis [ -bmsu ] [ -BMS directory ... –f ] filename

who——显示当前登录用户
write——向其他用户发送消息
write username [ ttyname ]

write 命令用于从一个用户终端将文本行写到另一个终端上。
xargs——构建参数并执行命令
xargs [ flags ] [ command [ initial–arguments ] ]

xarg 命令将文件的内容填充至命令行中,动态生成命令行。

实例 A.72

1 ls $1 | xargs –i –t mv $1/{} $2/{}


2 ls | xargs –p –1 rm -rf
3 find . –type f | xargs grep –1 "ellie"

说明
1 将目录$1 中所有文件移到目录$2 中,在每次移动文件前均显示 mv 命令。
2 -p 选项用于在每次移除文件时提示用户。

PDF created with pdfFactory Pro trial version www.pdffactory.com


520 附录 A

3 find 命令在当前目录中搜索普通文件,并将结果列表写到 xrags 中,xrags 则将文件名传给 grep


命令。这样就可以显示所有含“ellie”的文件了。这也是实现递归 grep 的一种方法。

zcat(对压缩文件进行解压,并将结果写到标准输出。类似于 uncompress)——c
zcat [ file . . . ]

实例 A.73
zcat book.doc.Z | more

说明
解开压缩文件 book.doc.Z 并将输出重新定向到 more 命令的输入。

zipinfo——显示 zip 压缩包的详细信息


zipinfo [-12smlvhMtTz] file[.zip][file(s)...] [-x xfile(s) ...]

zipinfo 命令用于显示压缩包中关于文件的技术信息,这些技术信息常见于 MS-DOS 操


作系统,如文件的访问权限、加密状态、压缩类型、版本以及操作系统或压缩程序的的文件
系统等等。默认情况下(不指定参数),该命令为压缩包中每一个文件显示一行状态信息,
除此之外还在首部和尾部给出整个压缩包的摘要信息。输出格式类似于由 UNIX 的 ls -l 的输
出和 unzip -v 的输出混合而成。
zmore——压缩文本浏览器
zmore [ name ... ]

zmore 是一个过滤程序,用于在软拷贝终端上一次显示一屏大小的压缩文件或普通文件
内容。zmore 命令对由 compress,pack 或 gzip 等命令压缩的文件均有效。如果指定文件不存
在,则查找以.gz,.z 或.Z 为后缀的同名文件。显示形式类似于 more 命令:一次显示一屏。

PDF created with pdfFactory Pro trial version www.pdffactory.com


附录 B

Shell 比较
特征 Bourne C TC Korn bash
别名 no yes yes yes yes
高级模式匹配 no no no yes yes
命令行编辑 no no yes yes yes
目录堆栈(,) no yes yes no yes
文件名自动完成 no yes yes yes yes
函数 yes no no yes yes
历史 no yes yes yes yes
作业控制 no yes yes yes yes
键捆绑 no no yes no yes
个性化提示符 no no yes no yes
a.
拼写检查 no no yes no yesb.
a. 非默认设置,须由用户设定。
b.cdspell 是 shopt 命令的选项,用于 cd 命令参数中目录名拼写错误的自动更正、自动的、周期性的以及定时事件(定时任
务、特殊别名、自动退出登录和终端锁定等)。

B.1 tcsh 与 csh

TC Shell(tcsh)是 Berkeley 的 C Shell(csh)的加强版本。这里列出的是增加的新的特


征。
l 增强的历史机制
l 用于编辑命令行的内建命令行编辑器(emacs 或 vi)
l 个性化提示符
l 拼写检查和专门为拼写检查和循环设计的提示符
l 加强的可编程的命令完成、文件名完成、变量名完成以及用户名完成等等
l 创建和修改键盘捆绑的能力
l 自动的、周期性的和定时事件(定时任务、特殊别名、自动退出登录和终端锁定等)
l 新的内建命令(hup、ls-F、newgrp、printenv、which 以及 where 等)

PDF created with pdfFactory Pro trial version www.pdffactory.com


522 附录 B

l 新的内建变量(gid、loginsh、oid、shlvl、tty、uid、version、HOST、REMOTEHOST、
VENDOR、OSTYPE 和 MACHTYPE)
l 只读变量
l 更好的错误报告机制

B.2 bash 与 sh

Bourne Again(bash) Shell 有传统的 Bourne Shell(sh)所没有的如下的特征:


1.个性化提示符
2.历史(csh 风格)
3.别名
4.用于编辑命令行的内建命令行编辑器(emacs 或 vi)
5.用 pushd 和 popd 直接对目录操作
6.csh 风格的作业控制,运行或停止后台作业,把后台作业推到前台等,使用命令 bg,
fg,Ctrp_Z 等
7.浪线、括号和参数扩展
8.个性化的键盘捆绑
9.高级模式匹配
10.数组
11.选择循环(来自 Korn Shell)
12.新的内建命令

特征 csh/tcsh Bourne Bash Korn


变量:
赋值给本地变量 set x=5 x=5 x=5 x=5
设置变量属性 declare or typeset typeset
赋值给环境变量 setenv NAME Bob NAME=’Bob’; expor expor
export NAME NAME=’Bob’ NAME=’Bob’
只读变量:
访问变量的值 echo $NAME Echo $NAME Echo $NAME Echo $NAME or
set var=net var=net var=net print $NAME
Echo $(var)work Echo $(var)work Echo $(var)work var=net
Network Network Network print $(var)work
Network
字符个数 echo $%var(仅用 N/A ${#var} ${#var}
于 tcsh)
特殊变量:
进程的 PID $$ $$ $$ $$
退出状态 $status, $? $? $? $?
最后的后台作业 $!(仅用于 tcsh) $! $! $!

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 比较 523

续表
特征 csh/tcsh Bourne Bash Korn
数组:
赋值给数组 set x=(a b c) N/A Y[0]=‘a’ Y[0]=‘a’
Y[1]=‘b’ Y[1]=‘b’
Y[2]=‘c’ Y[2]=‘c’
Fruit =(apples Set –A fruit
pears peaches =apples pears
plums) peaches plums
访问数组元素 echo $x[1] $x[2] N/A Echo$(y[0]) Print $(y[0])
$(y[1]) $(y[1])
所有元素 echo $x or x[*] N/A Echo Print
$(y[*]),$(fruit[0]) $(y[*]),$(fruit[0])
元素的下标 echo $#x N/A Echo $y(#[*]) Print $y(#[*])
命令替换:
把命令输出赋值给 set d=’date’ d=’date’ d=$(date) or d=$(date) or
变量 d=’date’ d=’date’
访问变量值 echo $d Echo $d Echo $d Echo $d
echo $d[1],$d[2]
echo $#d
命令行参数(位置参量)

访问 $argv[1], $argv[2] $1,$2…$(10) $1,$2…$(10) $1,$2…$(10)
or $1,$2
设置位置参量 N/A set a b c Set a b c Set a b c
set ‘date’ Set ‘date’ or set Set ‘date’ or set
echo $1 $2 $(date) $(date)
Echo $1 $2 Echo $1 $2
命令行参数个数 $#argv $# $# $#
$#(tcsh)
参数所包含的字符 $%1,$%2(tcsh) N/A N/A N/A
个数
用于文件名扩展的元字符:
匹配单个字符 ? ? ? ?
匹配 0 个或者多个 * * * *
字符
匹配一组字符中的 [abc] [abc] [abc] [abc]
一个字符
匹配一个范围中的 [a-c] [a-c] [a-c] [a-c]
一个字符
匹配单个不在组中 N/A(csh) [!abc] [!abc] [!abc]
的字符 [^abc](tcsh)
匹配 0 个或者 1 个 abc?(2|9)1 abc?(2|9)1
括号中的模式。垂
线表示“或”的意
思,本例的模式可
以匹配 abc21, abc91
或 abc1
文件名不匹配模式 ^pattern(tcsh)

PDF created with pdfFactory Pro trial version www.pdffactory.com


524 附录 B

续表
特征 csh/tcsh Bourne Bash Korn
I/O 重新定向和管道:
命令输出重新定向 cmd > file cmd > file cmd > file cmd > file
到文件
命令输出重新定向 cmd > >file cmd > >file cmd > >file cmd > >file
并追加到文件
命令输入重新定向 cmd <file cmd <file cmd <file cmd <file
来自文件
命令错误重新定向 (cmd>/dev/tty)> cmd 2>errors cmd 2>file cmd 2>errors
到文件 &errors
输出和错误都重新 cmd >&file cmd >file 2>&1 cmd >& file or cmd >file 2>&1
定向到文件 cmd &>file or cmd
>file 2>&1
输出定向到文件并 cmd >|file N/A cmd >| file cmd >| file
忽略 noclobber
here 文档 cmd << EOF cmd << EOF cmd << EOF cmd << EOF
input input input input
EOF EOF EOF EOF
用管道连接一个命 cmd|cmd cmd|cmd cmd|cmd cmd|cmd
令的输出和另一个
命令的输入
用管道连接一个文 cmd|&cmd N/A N/A
件的输出错误到另
外一个命令
协同处理 N/A N/A N/A command |&
条件语句 cmd && cmd cmd && cmd cmd && cmd cmd && cmd
cmd || cmd cmd || cmd cmd || cmd cmd || cmd
读取键盘:
从输入读一行并保 set var = $< read var read var read var
存到变量 set var = ‘line’ read var1,var2… read var1,var2… read var1,var2…
read read
read –p prompt read var?“Enther
read –a arrayname value”
数学计算:
计算器 @var = 5+ 1 var=‘expr 5+ 1’ ((var=5+1)) ((var=5+1))
let var=5+1 let var=5+1
浪线扩展
表示用户主目录 ~username N/A ~username ~username
表示主目录 ~ N/A ~ ~
表示当前工作目录 N/A N/A ~+ ~+
表示前工作目录 N/A N/A ~- ~-
别名:
建立别名 alias m more N/A alias m=more alias m=more
列出别名 alias alias,alias -p alias,alias -t
删除别名 unalias m N/A unalias m unalias m

PDF created with pdfFactory Pro trial version www.pdffactory.com


Shell 比较 525

续表
特征 csh/tcsh Bourne Bash Korn
历史:
设置历史 set history=25 N/A automatic or automatic or
HISTSIZE=25 HISTSIZE=25
显示历史项及其编号 history history , fc -l history , fc -l
显示指定序号的历 history5 history5 history5
史项
再次执行一个历史 !!(last command) !!(last command) r(last command)
命令 !5(5thcommand) !5(5thcommand) r5(5thcommand)
!v(latcommand !v(latcommand rv(latcommand
starting with v) starting with v) starting with v)
设置交互编辑器 N/A(csh) N/A set –o vi set –o vi
bindkey –v or set –o emacs set –o emacs
bindkey –e (tcsh)
信号:
命令 onintr trap trap Trap
初始化文件:
登录执行文件 .login .profile .bash_profile .profile
每次调用 Shell 需要 .chsrc N/A BASH_ENV=.bas ENV+.kshrc
执行文件 hrc
ENV=.bashrc
函数:
定义函数 N/A fun(){command;} fun(){command;} fun(){command;}
调用函数 N/A fun fun fun
fun fun fun
param1,param2.. param1,param2.. param1,param2..
程序结构:
if 条件语句 if (expression) if [expression] if [[string if [[string
then then expression]] expression]]
commands commands then then
endif fi commands commands
if {(command)} if command fi fi
then then if ((numeric if ((numeric
command commands expression)) expression))
endif fi then then
commands commands
fi fi
if/else 条件语句 if (expression) if command then if command then if command then
then commands commands commands
commands else else else
else commands commands commands
commands fi fi fi
endif
if/else/elseif 条件语 if (expression) if command then if command then if command then
句 then commands commands commands
commands elif command then elif command then elif command then
else if (expression) commands commands commands
then else else else
commands
commands commands commands
else
commands fi fi fi
endif

PDF created with pdfFactory Pro trial version www.pdffactory.com


526 附录 B

续表
特征 csh/tcsh Bourne Bash Korn
goto goto label… N/A N/A N/A
label :
switch 和 case swith (“$value”) case “$value” in case “$value” in case “$value” in
case pattern1: pattern1) pattern1) pattern1)
commands commands ;; commands ;; commands ;;
breaksw pattern2) pattern2) pattern2)
case pattern2: commands ;; commands ;; commands ;;
commands *) commands *) commands *) commands
breaksw ;; ;; ;;
default: esac esac esac
commands
breaksw
endsw
循环:
while 循环 while (expression) while command while command while command
commands do do do
end command command command
done done done
for/foreach foreach var for var in wordlist for var in wordlist for var in wordlist
(wordlist) do do do
commands commands commands commands
end done done done
until until command until command until command
do do do
commands commands commands
done done done
repeat repeat 3 “echo N/A N/A N/A
hello”
select N/A N/A ps3=“Please select ps3=“Please select
a menu item” a menu item”
select var in select var in
wordlist wordlist
do do
commands commands
done done

PDF created with pdfFactory Pro trial version www.pdffactory.com


附录 C

正确使用引用
的步骤

C.1 反斜线(参考 表 C.1)

1.放在字符前面保护这个字符不被翻译
2.像单引号一样放在字符的两边

C.2 单引号(参考 表 C.1)

1.必须成对出现
2.保护所有字符不被翻译,除了:
a.本身
b.“!”和“.”(csh)
c.\

C.3 双引号(参考 表 C.2)

1.必须成对出现
2.保护所有字符不被翻译,除了:
a.本身
b.“!”和“.”(bash,csh)
c.变量替换中的$
d.命令替换中的反单引号“``”

PDF created with pdfFactory Pro trial version www.pdffactory.com


528 附录 C

表 C.1 使用单引号和反斜线

C Shell Bourne Shell Korn Shell TC Shell bash Shell


echo'$><%^&*' echo '$*&!><?' echo '$*&!><?' echo '$><%^&*' echo '$*&!><?'
echo 'I need $5.00\!' echo 'I need $5.00!' echo 'I need $5.00!' echo 'I need $5.00!' echo 'I need $5.00!'
echo 'She cried, echo 'She cried, echo 'She cried, echo 'She cried, echo 'She cried,
"Help"' "Help"' "Help"' "Help"' "Help"'
echo '\\\\' echo '\\\\' print '\\\\' echo '\\\\'
echo '\\\\'
\\\\ \\ \\ \\

表 C.2 使用双引号

C Shell Bourne Shell Korn Shell TC Shell bash Shell


echo "Hello echo "Hello print "Hello echo "Hello echo "Hello
$LOGNAME\!" $LOGNAME!" $LOGNAME!" $LOGNAME!" $LOGNAME"\!
echo "I don't care" echo "I don't care" print "I don't care" echo "I don't care" echo "I don't care"
echo "The date is echo "The date is pring "The date is echo "The date is echo "The date is
'daate'" 'date'" $(date)" 'date'" a(date)"
echo "\\\\" echo "\\\\" pring "\\\\" echo "\\\\" echo "\\\\"
\\\\ \ \ \\ \\

C.4 联 合 引 用
目标:
在最后的结果中把 Shell 变量嵌入到 awk 命令行中。使得 Shell 扩展变量的时候不需要
面对 awk 的域分隔符的问题。

设置变量:
name="Jacob Savage" (sh, bash and ksh)
set name="Jacob Savage" (csh and tsh)

数据文件中的一行:
Jacob Savage:408-298-7732:934 La Barbara Dr. ,San Jose,CA :02/27/78:500000

awk 命令行:
awk –F:'$1 ~ /^'"$name"'/{print $2}' datafile
(output)
408-298-7732

第一步:
在插入变量以前测试一下你的 Linux 命令行知识。
awk –F:'$1 ~ /^ Jacob Savage /{print $2}' filename
(output)
408-298-7732

第二步:
只插入变量,不做其他改变,所有的引用都保持原样。

PDF created with pdfFactory Pro trial version www.pdffactory.com


正确使用引用的步骤 529

awk –F:'$1 ~ /^$name/{print $2}' datafile

在变量$name 左边添加一个单引号,与最左边的单引号组成一对,于是两个单引号之间
的部分就被保护起来不会被翻译。在变量$name 右边添加一个单引号,与最右边的单引号组
成一对,于是两个单引号之间的部分就被保护起来不会被翻译。这样变量就暴露出来了。

awk –F:'$1 ~ /^'$name'/{print $2}' datafile

第三步:
在变量左右增加一对双引号,这样可以保证变量内部的值可以被扩展,同时,即使这个
值中包含空格,也会因为双引号的引用,而不至于导致命令行出现语法错误。

awk –F:'$1 ~ /^'"$name"'/{print $2}' datafile

查一下引号的个数,单引号和双引号都应该是偶数。

C.5 例 子
oldname="Ellie Main"
newname="Eleanor Quigley"

1.确保命令行正确工作。
awk –F: '/^Ellie Main/ {$1=" Eleanor Quigley";print $0}' datafile

2.插入变量。
awk –F: '/^$ oldname / {$1=" $newname";print $0}' datafile

3.开始引用。

awk –F: '/^'$ oldname' / {$1="' $newname'";print $0}' datafile

在变量$oldname 左边添加一个单引号,与最左边的单引号组成一对,于是两个单引号
之间的部分就被保护起来不会被翻译。在变量$oldname 右边添加一个单引号。在变量
$newname 左边添加一个单引号,与变量$oldname 右边的单引号组成一对。于是两个单引号
之间的部分就被保护起来不会被翻译。在变量$newname 右边添加一个单引号。与最右边的
单引号组成一对,于是两个单引号之间的部分就被保护起来不会被翻译。
4.计算单引号的个数。如果是偶数那么单引号就全部成对出现了。如果不是,那么你
一定是忘记某个步骤。
5.把所有的变量都用双引号引用起来。

awk –F: '/^'"$ oldname"' / {$1="'" $newname"'";print $0}' datafile

PDF created with pdfFactory Pro trial version www.pdffactory.com

You might also like