Professional Documents
Culture Documents
Ellie Quigley 著
中国电力出版社
书 名:Linux Shell实例精解
作 者:Ellie Quigley
译 者:吴雨浓
出版社:中国电力出版社
出版日期: 2003
字数:766千字 开本:16
ISBN 7-5083-1306-2/TP.420
定 价: 59.00
2 Linux Shell 循序渐进
前 言
前 言
第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
第 6 章 gawk 功能:给表达式赋值.........................................................................................123
6.1 比较表达式....................................................................................................................123
6.2 复习 ...............................................................................................................................127
第 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
附录 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
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 强大得像一个工作站。
Shell
实例 1.1
$ cat /etc/shell
/bin/bash
/bin/sh
/bin/ash
/bin/bsn
/bin/tcsh
/bin/csh
/bin/ksh
/bin/zsh
说明
1 /bin/shell 文件包含了在你的 Linux 版本下可以运行的 Shell 程序的列表。最常用的版本是 bash
(Bourne Again Shell)、tcsh(TC Shell)和 ksh(Korn Shell)
2.Quigley, Ellie. UNIX Shells by Example, 2nd Edition. Upper Saddle River, NJ: Prentice Hall, 1999.
1.3.1 解析命令行
当你在提示符下输入一条命令后,Shell 读取行输入并解析命令行,把命令行拆成单
个的字,称为令牌。令牌之间依靠空格和制表符分隔,以换行符作为命令行结束的标志。3Shell
首先判断命令行的第一个字是内建命令还是存储在磁盘上的。如果是内建命令,Shell 就
执行该命令。否则,Shell 就在 PATH 变量指定的目录列表中查找这个程序。如果找到,
就启动一个新的进程来执行这个程序,而 Shell 就休眠(或者等待)直到程序执行完毕,
如果必要,还将报告程序的退出状态。跟着出现提示符,而整个进程都将被重新启动。命
令行的处理顺序如下所示:
(1)历史记录替换(需要设置) 。
(2)命令行拆分为 token(单词) 。
(3)历史更新。
(4)处理引用。
(5)定义别名替换和函数(如果需要) 。
(6)建立重新定向、后台和管道。
(7)变量替换($user、$name 等等)。
3.把命令行拆分为单词的过程称为“词汇分析”(lexical analysis)。
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.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 有启动(创建)其他进程的能力。事实上,当你在提示符下或者通过脚本输入命令
的时候,Shell 就以寻找内建命令或者外部可执行程序来响应,接着安排命令运行。这是依靠
呼叫系统(system call)来完成的,称为系统调用。系统调用需要核心服务,这是进程访问硬
件的惟一方法。有很多系统调用允许建立、执行和终止进程(当 Shell 提供重新定向和管道、
命令替换、用户命令的时候,它还可以通过 kernel 提供其他的服务) 。在后面的章节中我们还
将讨论 Shell 利用系统调用产生新进程的问题。参见图 1.2。
显示提示符并读
入下一条命令
Shell 搜索
一条命令
判断该
命令是否是 是 执行该命令
内建命令
不是
派生一
个子进程 父 Shell
等待
判断该命令
是否是一个已编译的 是
可执行语句
kernel 把一个新的
程序装入内存,并
在子进程中执行它
不是
运行及终止新进程
判断该命令是 退出
是
否是脚本程序
的结尾
唤醒父 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
实例 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
|-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 标准错误端
7.只有重新启动系统才能删除僵进程。
实例 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 中。
环境 环境 环境
0 标准输入 执行
0 标准输入 0 标准输入
(子 Shell
1 标准输出 1 标准输出 上的新程 1 标准输出
2 标准错误 2 标准错误 序) 2 标准错误
退出
说明
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
文件的第四个域内。
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—
1.5.3 改变所有权和权限
表 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 第一个参数是十进制的 755,赋予用户(所有者)rwx 的权限,赋予组 r 和 x 的权限,赋予其他人
x 的权限。
2 在 chmod 的符号方式下,w 的权限被加给了组。
3 在 chmod 的符号方式下,rx 的权限被从组和其他人的权限中除去。
4 在 chmod 的符号方式下,所有者、组和其他人的权限都被置为 r,=符使得所有的权限都被重置。
实例 1.6
实例 1.7
说明
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 启动的子进程可以改变它自己的工作目录,
但是不能影响父进程的工作目录。
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 处理保存了一个文件说明符检索的表格,
父 Shell 子 Shell
125
标准输入 0 键盘
标准输出 1 temp 文件
标准错误 2 监视器
标准输入 键盘
标准输出 监视器
标准错误 监视器
标准输入 键盘
标准输出 temp 文件
标准错误
监视器
图 1.5 标准输出的重新定向
父 Shell 子 Shell
233
0 memo文件
1 监视器
标准输入 键盘 2 监视器
标准输出 监视器
标准错误 监视器
memo 文件
监视器
监视器
图 1.6 标准输出的重新定向
父 Shell 子 Shell
235
0 键盘
1 监视器
标准输入 键盘 2 错误
标准输出 监视器
标准错误 监视器
键盘
监视器
错误
237
0 键盘
1 监视器
键盘 键盘 2 错误
监视器 错误
监视器 错误
键盘
监视器
错误
图 1.8 (C 和 TC Shell)的标准错误的重新定向
实例 1.9
说明
1 把 who 命令的输出由终端重新定向给文件(所有的 Shell 都以这样的方式重新定向输出) 。
2 把 cat 命令的输出(合并 file1 和 file2)追加到 file3 后面(所有的 Shell 都以这样的方式重新定向
并追加输出)。
3 把文件的输入重新定向给 mail 程序,File 文件的内容将被发送给用户 tom(所有的 Shell 都以这样
的方式重新定向输入) 。
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
父进程 父进程
环境 环境
0 标准输入端 0 标准输入端
1 标准输出端 1 标准输出端
2 标准错误端 2 标准错误端
3 管道读取(pipereader)管道
4 管道写入(pipewriter)管道
环境 环境 环境
图 1.10 父进程为管道两端的命令分别派生两个子进程
第一个 第一个
子进程 子进程
环境 环境
dup 系统
0 标准输入端 0 标准输入端
调用后
1 关闭 1 关闭
2 标准错误端 2 标准错误端
3 管道读取(pipereader)管道 3 关闭
4 管道写入(pipewriter)管道 4 关闭
图 1.11 第一个子进程准备向管道中写
第二个 第二个
子进程 子进程
环境 环境
0 关闭 0 管道读取管道 dup 系统
调用后
1 标准输出端 1 标准输出端
2 标准错误端 2 标准错误端
3 管道读取(pipereader)管道 3 关闭
4 管道写入(pipewriter)管道 4 关闭
图 1.12 第二个子进程准备从管道中读
子进程 子进程
环境 环境
4 关闭 4 关闭
当信号发送一个信息(message)给进程时通常会导致进程终止。这种信号一般都是些
意外事件,例如挂起、电源掉电或者是程序错误,例如 0 引起的非法除数和无效的内存地
址。信号也可以通过特定的键传递给进程。例如,你可以通过 Break、Delete、Quit 和 Stop
发送信号给进程,共用同一个终端的所有进程都受到发送信号的影响。你可以 kill 命令取
消一个进程。默认情况下,大多数的信号都会导致程序的终止。每一个进程都以特定的行
为响应特定的信号:
(1)忽略信号。
(2)停止进程。
(3)继续进程。
(4)由专门函数处理相应的信号。
Bourne、bash 和 Korn Shell 允许你按照以下方式处理进入程序的信号、忽略信号或者为
特定的信号指定特定的行为,以及重置信号的默认行为。C 和 TC Shell 被限制只能处理中断
字符^C(Control-C)。
表 1.2 显示的是进程可以使用的标准信号。
表 1.2 标准信号
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 程序通常执行得比较
慢,但是它们更容易写也更适合简单的任务。Shell 编程可以在命令行下以交互方式写入脚
本,这种快捷的方式比较适合简单的任务。然而对于复杂的任务来说,文本编辑器则更为
适合(除非你的输入水平很高)。下面的脚本可以被任何 Shell 执行并输出相同的结果。图
1.14 说明了称为“doit”的脚本是如何建立的,以及它是如何适应已经存在的 Linux 程序/
实用工具/命令的。
Shell
说明
1 打开一个你喜欢的文本编辑器,输入一系列命令。每个命令一行。在第一行#!后面输入你希望使
用的 Shell 和路径。这个叫作 doit 的脚本将被 TC Shell 执行。
2 保存文件并打开执行权限,你就可以执行它了。
3 像其他 Linux 命令一样执行这个文件。
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 在一条信息发送给客人以后,食品清单信息就会改变(减少一项) ,以保证下一位客人拿到是新的
可供其选择的食品清单。如果食品清单上的项目数量比客人的数量少,食品清单就要重置,以保
证每一个客人都有食品。
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 #!/bin/bash
# Gnu bash versions 2.x
说明
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 循环结束。
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 忽略,但是对于想要理解你的程序的人来说却是很重要的。
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 ))
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 循环结束。
Linux 工具箱
就像木匠需要合手的工具一样,Shell 程序员也需要一些合手的工具(也称为实用程序)
来编写实用及高效的脚本。有成百的 Linux 工具可供我们使用,其中很多是日常必备的,例
如 ls、pwd、who 和 vi。这些实用程序是由自由软件基金会提供的,并置于 GPL 协议保护下
的,它们通常都是 UNIX 同名软件的加强版本。本章将主要讨论的三个实用程序是 grep、sed
和 gawk。1在了解这三个程序的强大功能以前,首先要了解它们的正则表达式和正则表达式
元字符集(metacharacters)。
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 的字样,就说明程序中使用的正则表达式存在错误。
Thanks.
Your pal ,
guy@phantom
~
~
~
~
---------------------------------------------------------------------
--> :1,$s/tom/David/g
---------------------------------------------------------------------
这行命令的意思是“从文件的第一行到文件的最后一行(1,$)替换(s)单词 Tom 或者
tom 为 David”。g(global)标志表示这个命令对于全文有效。正则表达式元字符\<和>\分别
界定单词的开始和结束。一对括号[Tt]表示只要匹配括号中的一个字母,在这个例子中,不
论是 T 还是 t。这里出现的五个 Linux/UNIX 模板匹配实用功能可以识别的元字符,它们扩
展了程序编写中可用的元字符集。
2.1.2 正则表达式的元字符集
有两套正则表达式字符集,一套是基本元字符集,另一套是扩展元字符集。另外,
元字符 功能 例子 匹配什么
^ 锚定行的开始 /^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
3.POSIX 是 Portable Operating System Interface for Computer Eaviron ment 的缩写,意思是计算机环境的可移植操作系统界面。
在下面的例子中,被加重的字符就是 vi 将找到的匹配字符。
实例 2.1
说明
正则表达式是 love。模板 Love 找到 love 本身外,还搜索到了一些部分匹配的单词,例如 lovely、
gloves 和 clover。
实例 2.2
说明
^符号用来锚定行的开始。vi 将寻找这样的行,行的最左边匹配正则表达式 love。也就是说,Love
必须是这行最开始的字符,即使在前面多一个空格也不行。
实例 2.3
说明
美元符号用来锚定行的末尾。vi 将只找到这样的行,行的末尾匹配正则表达式 Love。也就是说
love 是这行最后的字符,在 Love 后面就是新的一行。
实例 2.4
说明
除了不能匹配整个新行外,点可以匹配任何单个字符。Vi 将只找到这样的行,行包含如下的字
符:第一个字符是 l,紧跟着一个单个字符,然后紧跟的是 ve。在本例子中,找到的结果是由 live 和
love 组合成的。
实例 2.5
说明
星号匹配 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
说明
在括号内的两端有字符的破折号匹配的是,破折号两端字符界定范围内的任何一个字符。vi 将
搜索一个正则表达式,这个表达式是 ove 后面紧跟着一个字符,这个字符的 ASCII 码必须在 a 和 z
的 ASCII 码的范围之间。因为这是一个 ASCII 码范围,所以不能把这个范围倒置为[z-a]。
实例 2.8
说明
括号内的^表示的是“非”的含义。vi 将搜索一个正则表达式,这个表达式包括 ove,且后面紧
跟一个字符,该字符的 ASCII 码既不能在 a 到 z 的范围内,也不能在 A 到 Z 的范围内,此外,还不
能够在 0 到 9 的范围内。例如,在 ove 后面跟一个逗号、空格或者点等等都是符合要求的,因为它
们不在上述 ASCII 码范围内。
2.2 正则表达式元字符的组合
我们已经介绍了基本正则表达式的元字符的使用方法,这些元字符可以组合成更为复杂
的表达式。正则表达式里斜杠中的字符都是需要搜索的字符串,搜索的范围都是文本文件的
每一行。
实例 2.9
注意:行号不属于文本内容,各行的竖线标志着文本的左右边界。
------------------------------------------------------------------
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
/\<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
~
~
说明
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
event.
~
~
~
说明
编辑器搜索正则表达式 square and fair。将 square 标记为#1,并将 fair 标记为#2。在需要替换的
一侧,将#2 的内容替换为\2,将#1 的内容替换为\1。请参考图 2.2 所示。
标签 1 标签 2
寄存器 1 寄存器 2
fair square
图 2.2 使用多个标签
实例 2.14
说明
1 在每一行中寻找这样的字符串:首先是 2 个 5,紧跟着 3 个 2,最后是一个点(.)
。
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 命 令
g 命令的意思是“文件中所有的行”或者“运行一个全文替代”
。因为搜索模板被称为
正则表达式,所以我们可以用 RE 来替换模板,命令读为:
: g/RE/p
格式
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,\>
2.单词也称为 token。
3.任何版本的 grep 都可以通过引用反斜杠关闭正则表达式元字符集的特殊性。
?,+,{},|,()
格式 含义
grep 'pattern' filename(s) 基本正则表达式元字符集(默认)
grep –G 'pattern' filename(s) 同上(默认)
grep –E 'pattern' filebname(s) 扩展正则表达式元字符集
grep –F 'pattern' filename(s) 无正则表达式元字符集
元字符 功能 例子 匹配什么
^ 锚定行的开始 ^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
续表
元字符 功能 例子 匹配什么
\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 下工作。
元字符 功能 例子 匹配什么
+ 匹配一个或者多个先前字符 [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 下工作。
被括弧括起来的类(Bracketed Class) 含义
[:alnum:] 文字数字字符
[:alpha:] 文字字符
续表
被括弧括起来的类(Bracketed Class) 含义
[:digit:] 数字字符
[:graph:] 非空字符(非空格、控制字符)
[:lower:] 小写字符
[:cntrl:] 控制字符
[:print:] 跟非空字符一样,但是包括空格
[:punct:] 标点符号
[:space:] 所有白空格字符(新行、空格、制表符)
[:upper:] 大写字符
[:xdigit:] 十六进制数字(0-9、a-f、A-F)
实例 3.3
说明
1,2,3 对于 Linux 下的各种 grep(除了 fgrep)都支持 POSIX 括号括起来的类集。在上面的各
个例子中,grep 都将搜索一个空格、逗号、数字[0-9]和另外一个空格字符。
实例 3.4
说明
1 grep 在文件/etc/passwd 中搜索字符串 john,如果成功,grep 的退出状态值就是 0;如果在文件中
没有找到 john,退出状态值就是 1;如果文件没有找到,退出状态值就是 2。
2 在 TC 及 C Shell 中的变量 status 和 Bourne 或者 Korn Shell 中的变量 ?都表示最后执行的命令的
状态。在 Bourne Again(bash)Shell 中,你可以使用以上任何一种变量检测退出状态。
% 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
说明
打印文件 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
说明
打印所有以 n 开头的行。^表示锚定行的开头。
实例 3.8
说明
打印所有以 4 结束的行。$表示锚定行的结尾。
实例 3.9
说明
因为第一个参数是模板, 其他的参数是文件名,
所以 grep 将在文件 Savage 和 datafile 中搜索 TB。
要搜索 TB Savage 参考下面的例子。
实例 3.10
说明
打印所有包含模板 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
实例 3.11
说明
打印包含第一个字符是 5,紧跟着一个点,再后面是任意一个字符的字符串的行。除非在点的
前面有一个反斜杠,否则它就表示一个任意字符。若前面有反斜杠,则点就不再特殊,而是仅仅表
示一个点。
实例 3.12
说明
打印所有包含字符串“.5”的行。
实例 3.13
说明
打印所有以 w 或者 e 开头的行。^表示锚定行的开头,括号里的一个字符将被匹配。
实例 3.14
说明
打印所有包含非数字字符的行。括号内的^表示任意一个不在括号范围内的字符。因为与行都包
含至少一个非数字字符,所以所有的行都将被打印(参考-v 选项)
。
实例 3.15
说明
打印所有包含前两个字符是大写字母,后面紧跟着一个空格及一个大写字母的字符串的行。例
如,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
说明
打印所有包含这样的字符串的行,第一个字符是 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
说明
打印所有包含单词 north 的行,\b 是单词分界符。在所有 Gnu 版本的 grep 中,它都可以用来代
替单词锚。
实例 3.22
说明
打印所有包含这样的字符串的行,第一个字符是 n,紧跟着是任意个字母或者数字字符,然后
是一个非字母数字字符。在各种 Gnu 版本的 grep 中,\w 和\W 都是标准的单词匹配符。
实例 3.23
说明
打印所有包含这样的字符串的行,第一个字符是一个小写字母,紧跟着是任意个字符,然后以
字符 n 结束。注意.*,它表示任意字符,包括空格。
元字符 功能 例子 匹配什么
^ 锚定行的开始 ^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
续表
元字符 功能 例子 匹配什么
\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。
% 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 打印所有的包含 NW 或者 EA 的行。在这个例子中,egrep 被使用。如果你没有 Gnu 版本的 grep,
就请使用 egrep。
2 在这个例子中,Gnu 版本的 grep 打开- E 选项以支持扩展元字符,就像 egrep。
3 正规的 grep 不支持扩展的正则表达式。竖线是用于表示“或”的扩展正则表达式元字符。正规
grep 无法识别,若搜索“NW|EA”,则会因为找不到匹配而没有任何输出。
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
说明
打印所有包含一个或者多个 3 的行。
实例 3.26
说明
打印所有这样的行,该行包含这样的字符串,首先是字符 2,紧跟着零个或者一个点,然后是
一个 0 和 9 之间的数字。
实例 3.27
说明
打印包含一个或者多个连续的 no 的行。
实例 3.28
说明
打印所有这样的行,该行包含这样的字符串,首先是一个或者多个字母数字字符(\w+),紧跟
着一个或者多个非字母数字字符(\w+),最后是 A、B 或者 C 中的一个。
实例 3.29
说明
打印所有这样的行,该行包含这样的字符串,首先是字母 S,紧跟着是 h 或者 u。
实例 3.30
说明
打印所有的包含 Sh 或者 u 的行。
% 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
(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
(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
(Solaris egrep)
4 % egrep ‘\.[0-9]{2}[^0-9]' datafile
<no output; not recognized with or without backslashes>
说明
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.4 递归 grep(rgrep)
跟其他 grep 家族成员不同,rgrep 可以递归访问目录树。rgrep 有一些命令行选项支持跟
标准的 grep 一样的选项。参考附录 A 或者在命令行输入 rgrep?以获得帮助(标准的 UNIX
下的 grep 不支持这个特性)。
实例 3.34
说明
找到包含字符串“[A-Z]****[0-9]..$5.00”的行,这里的每个字符都表示它们自己,没有特殊字
符。
实例 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'
说明
命令 ls 的输出通过管道传递给 grep,输出中所有以 d 开头的行都被打印,也就是所有的目录都
被打印。
选项 功能
-b 在搜索到的行的前面打印该行所在的块号码。该功能在通过内容定位块的
号码时非常有用
-c 只显示有多少行匹配,而不具体显示匹配的行
-h 不显示文件名
-i 在字符串比较的时候忽略大小写
-l 只显示包含匹配模板的行的文件名清单,不同项目之间用换行符分隔
-n 在每一行前面打印该行在文件中的行数
-s 静默工作,除非出现错误信息,否则不打印任何信息,这个功能在检测退
出状态的时候有用
-v 反检索,只显示不匹配的行
-w 如果被\<和\>引用,就把表达式做为一个单词搜索只在 grep 中有效(不是
所有版本的 grep 都支持该特性,SCO UNIX 的 grep 就不支持)
选项 功能
-# 表示同时显示匹配行上下的#行,例如:grep -2 pattern filename 表示同时
显示匹配行上下各两行
-A#,--after-context=# 在匹配指定的内容的行打印完毕后,再打印一行#号
-B#,--before-context=# 在匹配模板的行的前面打印 1 行#号
-C,--context 相当于选项-2,在匹配行的前后各打印 2 行空行
-V,--version 显示包括 bug 报告在内的版本信息
-b,--byte-offset 在每一行前面打印字符偏移量
续表
选项 功能
-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 在每一个匹配行的前面打印行号。
实例 3.37
说明
选项-i 关闭了大小写敏感。模板中可以包含任意的大小写形式。
实例 3.38
说明
打印所有不包含 Suan Chin 的行。当需要从文本中删除特定的行时采用这个选项。删除特定行的
方法是,首先把输出重新定向到一个临时文件,然后再把临时文件重新命名为原文件的名字。
Grep -v 'Suan Chin' datafile > temp
Mv temp datafile
% 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 只打印匹配的文件名,而不打印匹配的行。
实例 3.40
说明
选项-c 使得 grep 只打印有多少匹配模板的行。注意:打印的不是模板出现了多少次,而是包含
模板的行有多少。
实例 3.41
说明
选项-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。
% 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 时发送报告给自由软件
基金会。
% 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 打印匹配 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 的行以及该行的上两行。
实例 3.47
说明
选项-b 使得 grep 在打印每一行前打印字符偏移量。
% cat negative
-40 is cold.
This is line 1.
This is line 2.5
-alF are options to the ls command
实例 3.48
说明
1 选项-e 使得 grep 平等的对待模板中所有的字符,这样开头的短横线才不会被误认为是选项。
2 当模板是正则表达式的时候,另一个选择是写成--regrxp=pattern 的形式;在这个例子中,正则表
达式是-40。
实例 3.49
说明
选项-x 使得 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.50
1 % cat repatterns
western
north
说明
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 就在每一行的前面追加文件名,在这个例子中是 datafile 和 db。
2 选项-h 使得 grep 不打印头信息,例如在这个例子中,就不打印文件名。
实例 3.52
说明
选项 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 个字符,并以一个大写字符开头,及一个数字结束的字符串的行
续表
命令 功能
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 个或者多个空格也可以)的行
命令 功能
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 以相同的方式处理该行命令中的模板。
Linux 工具实验室 1
grep 和 egrep 练习
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 中搜索匹配的行。
流线式编辑器
——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
1.记住,当一个命令从命令行输入时,Shell 将试图识别每一个元字符甚空白,而任何命令行都无法识别的字符必须被引用
起来。
4.4 定 址
可以通过定址来判断那一行你所希望编辑的。可以通过数字、正则表达式或者二者结合
的方式定址。在没有指定地址的情况下,sed 处理输入文件的所有行。
当该地址是由数字构成时,数字表示的是行数,美元符号($)表示输入文件的最后一
行。用逗号分隔的两个行数表示以这两行为起止的行的范围(包括行数表示的那两行)。可
以通过数字、正则表达式或者二者结合的方式确定一个范围。
sed 命令告诉 sed 对行做什么样的处理:打印、删除或修改等等。
FORMAT
sed 'command' filename(s)
实例 4.2
说明
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 选项可以显示一个命令行选项列表以及简单
的用法说明。
实例 4.3
% sed –h
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...
说明
如果没有-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 命令,将导致分支到
带有标号的命令处,或者到脚本的末尾
续表
命令 功能
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
4.6 错误信息和退出状态
当 sed 发现语法错误时,就会直接将错误信息发送到标准错误输出。如果无法确定错误
实例 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。
元字符 功能 例子 匹配什么
^ 锚定行的开始 /^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
续表
元字符 功能 例子 匹配什么
& 保存搜索字符用来替换其他 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.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 除了打印所有的行以外,还要打印
匹配行。
实例 4.6
说明
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
说明
删除第三行。其他的行默认打印到屏幕。
实例 4.8
说明
从第三行到最后一行都被删除。其他的行被打印。美元符号($)表示最后一行,逗号(.)被
称为范围操作符。在这个例子中,范围是从第三行开始到最后一行结束的。
实例 4.9
说明
删除最后一行。美元符号($)表示最后一行。默认打印除了那些被 d 命令删除的行以外的所有
行。
实例 4.10
说明
删除所有包含模板 north 的行,并打印其他行。
4.7.3 替换:s 命令
实例 4.11
说明
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
说明
s 命令表示替换。-n 选项和行末尾的 p 标志一起使用告诉 sed 只打印那些发生替换的行。也就是
说,如果某一行开头的 west 被替换为 north,那么就打印这行。
实例 4.13
说明
&符号表示替换字符串中被找到的部分。所有以两个数字结束的行,最后的数字都将被它们自
己替换,同时追加.5。
实例 4.14
说明
所有的 Hemenway 都被 jose 替换,只有发生变化的行才被打印。-n 选项与 p 命令一同使用,取
消默认输出。g 表示对该行进行全局替换。
实例 4.15
说明
模板 Mar 被包含在一对括号内,并在特殊的寄存器中保存为标签 1(tag1),它将在后面作为\1
替换字符串。Margot 替换所有的 Marianne。
实例 4.16
说明
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
说明
所有在模板 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
说明
对于模板 east 和 west 之间的行,每行的末尾用字符串**VACA**替换,下一行都被移动到该字
符串的后面。箭头所表示的就是被选定的范围。
4.7.5 多点编辑:e 命令
实例 4.20
说明
-e 选项允许多点编辑。第一个编辑命令是删除第一到第三行。第二个编辑命令是用 Hemenway
替换 Jones。因为两个命令是在同一行执行(也就是说,两个命令都是在当前行的固定空间中执行) ,
所以命令的执行顺序将影响命令的结果。例如,如果两个命令都是替换命令,那么第一个替换命令
将影响第二个替换命令的结果。
实例 4.21
说明
-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
说明
命令 r 表示从文件中读取指定的行。newfile 文件的内容被读入输入文件 datafile 中,显示在与
suzan 相匹配的行的后面。如果 suzan 在不止一行中出现,newfile 的内容就将显示在所有匹配行的下
面。
4.7.7 写入文件:w 命令
实例 4.23
说明
命令 w 表示把一个指定的行写入文件。所有包含 north 的行都将被写入名为 newfile2 的文件中。
4.7.8 追加:a 命令
实例 4.24
说明
命令 a 表示追加。字符串--->THE NORTH SALES DISTRICT HAS MOVED <---将被追加到以
north 开头,且 north 后面有空格的行的后面。需要被追加的文本必须写在命令行中追加命令的后面。
sed 要求在命令 a 后面有一个反斜杠。第二个反斜杠表示在 TC Shell 下转义换行符,这样才能在
下一行完成引用。a 如果被追加的不止一行,除了最后一行,每一行也都要用反斜杠结束。
% cat datafile
northwest NW Charles Main 3.0 .98 3 34
western WE Sharon Gray 5.3 .97 5 23
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
说明
如果模板 eastern 匹配了某一行,n 命令就移动到输入文件中该行的下一行,替换这一行的模式
空间。即用 Archie 替换 AM,打印该行,然后再继续。
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
说明
当模板 Lewis 在某一行被匹配,替换命令首先将 Lewis 替换为 Joseph,
然后再用 q 命令退出 sed 程序。
实例 4.30
说明
% 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
说明
如果模板 WE 在某一行中被匹配,h 命令将使得该行被从模式空间中复制并放入保持缓冲区中。
当存储到保持缓冲区中以后,该行还可以被重新利用(通过 G 命令或者 g 命令) 。在这个例子中,模
板 WE 被匹配以后,该行被放入保持缓冲区内。命令 d 在模式空间中删除该行。第二个命令搜索 CT,
一旦被找到, (通过 G 命令)sed 就从保持缓冲区中取回行,并追加到当前模式空间中行的末尾。简单
地说:包含 WE 是行被移动并追加到包含 CT 的行的后面 (参考“保持和互换:h 命令和 x 命令”
)。
删除
模式空间
保持缓冲区
g 以前 n
替换模式空间
g 以后
新模式空间
实例 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 的行
被复制并覆盖了文件的末尾。
% 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
说明
如果模板 WE 被匹配,h 命令就把该行拷贝到保持缓冲区中,d 命令在模式空间中删除该行。当
模板 CT 被匹配,g 取得保持缓冲区中的行,并覆盖当前存储在模式空间中的行。简单地说,任何包
含模板 northeast 的行都将被复制,并覆盖包含 CT 的行。
说明
命令 x 表示互换模式空间和保持缓冲区的内容。当模板 patricia 被匹配,该行将被保存到保持缓
冲区中。当包含 Margot 的行被找到,模式空间将与保持缓冲区交换彼此的行。简单地说,就是包含
Margot 的行被包含 patricia 的行替换了。
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
******************
MARGOT HAS RETIRED
********************
说明
1 这行是注释。注释必须是由#引导的单独的行(即不能跨行) 。
2 如果某一行包含模板 Lewis,那么下三行就追加到这行的后面。
3 每一个被追加的行,除了最后一行,都用反斜杠结束。反斜杠的后面必须紧跟着换行符。如果出
现文本,哪怕只是一个空格,sed 也会出现错误。
4 需要被追加的最后一行不需要反斜杠结束。这就告诉 sed:这是需要处理的最后一行了,下一行
是其他命令。
5 任何包含模板 Margot 的行的内容都将被下三行的文本替换(c 命令)
。
6 下两行将被插入到第一行的前面(i 命令) 。
7 最后一行被删除。
实例 4.36
1 /western/, /southeast/{
/^ *$/d
/Suan/{ h; d; }
}
2 /Ann/g
3 s/TB \(Savage\)/Thomas \1/
说明
1 对于以 western 开始并以 southeast 结束的范围内的行,空白行将被删除,匹配模板 Suan 的行将
被从模式空间中复制到保持缓冲区中,然后再从模式空间删除。
实例 4.37
说明
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 命令和功能清单。
表 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
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 的最新扩展。
1.在 Red Hat Linux 中,gawk 是连接到 awk 的。这个版本主要是新 awk,nawk 带上 Gnu 扩展。
实例 5.1
% awk --version
GNU Awk 3.0.3
Copyright (C) 1989, 1991-1997 Free Software Foundation.
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.
实例 5.2
% ls –l /bin/awk
lrwxrwxrwx 1 root root 4 May 12 04:47 /bin/awk -> gawk
5.2.1 从文件输入
在下面的例子中,%表示 C Shell 的提示符。
格式
% awk 'pattern' filename
实例 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 的行。
实例 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 打印文件 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 打印文件 employees 的第一个和第二个域中包含模板 sally 的行,记住,域是以空白分隔的。
5.2.2 从命令输入
Linux 命令的输出可以通过管道由 awk 处理。Shell 程序通常用 awk 处理命令。
格式
实例 5.6
说明
1 df 报告文件系统中的剩余空间。df 命令输出通过管道传送给 awk。如果第四个域中的块多于
75000,则打印该行。
2 rusers 命令打印那些通过网络在远程机器上的登录。ruers 命令的输出通过管道传送给 awk 作为
awk 的输入。如果正则表达式 root 匹配行的末尾,该行的第一域就被打印。也就是 root 登录过的
所有机器的名字都将被打印。
实例 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
-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
选项 含义
-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
-- 选项处理的结束信号
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
说明
Linux 的 date 命令的输出通过管道给 awk。字符串 Month:被打印,后面跟着包含换行符“\n”
的秒域和 Year:最后是第六域($6) 。
换码序列。换码序列表现为一个反斜杠后面紧跟着一个字母或者数字。它们被用在字符
串中表示表格、新行及换页等等(参见表 5.2)。
表 5.2 换码序列
换码序列 含义
\b Backspace
\f 换页
\n 新行
\r 回车
\t 制表符
\047 十进制 47,表示单引号
\c c 表示其他任何字符
实例 5.9
说明
如果某行包含模板 Sally,print 函数就打印两个制表符、字符串 Have a nice day、第一个域(用
$1 表示)和第二个域(用$2 表示)后面紧跟着一个包含两个感叹号的字符串。
5.3.2 OFMT 变量
在打印数字的时候你也许想控制数字的格式,我们通常用 printf 来完成这个功能。awk
的特殊变量 OFMT 也可以在使用 print 函数的时候,控制数字的打印格式。它的默认值是
“%.6g”——小数点后面 6 位将被打印(下面的内容将讲述如何改变该变量) 。
实例 5.10
说明
OFMT 变量被设置为浮点数(f)的小数点后面两位被打印。百分号(%)用来说明指定的格式。
5.3.3 printf 函数
在打印输出的时候,你可能想指定一些空格用来分隔各个域,以便使得列显得更简洁清
晰。因为带有制表符的函数不能总是保证希望的输出,于是 printf 函数就用来格式化格式复
杂的输出。
printf 函数返回一个打印到屏幕的格式化好的字符串,就像 C 语言中关于 printf 的说明。
printf 函数由被引用的控制字符串组成,这个控制字符串可以嵌入指定格式和改变量。控制
字符串紧跟着一个逗号和一串由逗号分隔的表达式,这些表达式将按照控制字符串指定的形
式被格式化。跟 print 函数不同,printf 函数不提供换行符,如果需要换行就必须写换码序列,
\n。
每一个百分号和格式符号都必须有相应的参数,打印文字百分数则必须使用 2 个百分
号。参考表 5.3——转换字符清单和表 5.4——printf 函数的编辑符。格式符号一般放在百分号
的前面,参考表 5.5——格式符号清单。
当一个参数被打印,打印输出的地方被称为域,域的宽度是指域能容纳多少个字符。
下面例子中的管道符号(垂线) ,作为需要打印字符的一部分,表示格式开始和结束的位
置。
实例 5.11
说明
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 十六进制数
续表
转换字符 定义
lx 长十六进制数
o 八进制数
lo 长八进制数
e 用科学记数法记录的浮点数
f 浮点数
g 使用 e 或者 f 函数处理过的浮点数,占用最少的空间
字符 定义
- 左对齐
# 显示八进制数的时候以 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.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}
说明
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 的值可以在有限的范围内修改。
实例 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 变量$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
说明
打印保存在文件中的每个记录,$0,并在记录前加记录号,NR。
5.5.2 域
记录中的每一个单词称做“域” ,默认情况下以空格或者 tab 分隔。每一个单词叫作一
个域,awk 跟踪域的数量,并在内建变量 NF 中保存这个数字。每一行的域个数是不一样的,
典型限制为每行 100 个。可以建立新的域。下面的例子有 4 个记录和 5 个域,每一个记录都
从第一个域$1 开始,然后是第二个域,$2,如此继续。
实例 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 将打印记录的个数 NR 及文件记录中每一行的第一个、第二个和第五个域。
实例 5.17
说明
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
说明
-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 用括号进行
文件名扩展)。
实例 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
说明
由于没有使用逗号分隔域,所有的域都挤在一起。
实例 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 如果模式在动作前面,模式决定什么时候
发出动作。动作可以是一个语句也可以是一组语句。语句之间用分号分隔,也可以用换行符。
格式
{ action }
实例 5.24
{ print $1 $2 }
说明
动作是打印第一个和第二个域。
模式可以与动作捆绑在一起。记住,动作是花括号内的语句。模式控制的动作是从第一个左花
括号开始到第一个右花括号结束。如果动作跟在模式后面,第一个左花括号必须与模式在同一行。
模式不需要放在花括号中间。
格式
实例 5.25
说明
如果记录包含模式 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 的行。
实例 5.27
说明
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 显示 employee 文件中所有以正则表达式 Mary 开头的行。
实例 5.29
说明
awk 显示 employee 文件中所有以一个大写字母开头,然后是一个或者多个小写字母,紧跟着是
一个空格的行。
命令 含义
[:alnum:] 字母数字字符
[:alpha:] 字母字符
[:cntrl:] 控制字符
[:digit:] 数字字符
[:graph:] 非空字符(非空格和控制字符)
[:lower:] 小写字母
[:print:] 类似[:graph:],但是包括空格
[:punct:] 标点字符
[:space:] 所有白空格(包括换行符、空格和制表符)
[:upper:] 大写字母
[:xdigit:] 允许十六进制格式的数字
实例 5.30
说明
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 显示所有在第一个域匹配 Bill 或 bill 的行。
实例 5.32
说明
awk 显示所有第一个域的末尾不匹配 ly 的行。
实例 5.33
% cat employees
Tom Jones:4424:5/12/66:54335
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
说明
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
实例 5.34
说明
打印所有包含模式 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
说明
打印所有以模式 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 的值。打印第三个域和
一个空格,后面是第二个域。
% 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
说明
第三个域跟在第二个域的后面。因为没有逗号分隔$2 和$3,所以输出的域之间也没有空格。
实例 5.39
说明
1 这是一个 awk(旧版 awk)的错误信息。旧的 awk 程序很难除错,因为几乎所有错误显示出来的
信息差不多都是这个。在动作语句中缺少一个花括号。
2 这是一个 nawk 的错误信息。nawk 的错误信息比旧的 awk 错误信息要详细一些。在这个例子中,
动作语句缺少花括号。
3 Gnu awk,也就是 gawk 按照与传统的 awk 风格有一点不同的格式显示其诊断。
实例 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
说明
如果记录包含模式 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
说明
如果记录以 n 或者 s 开头,就打印第一个域。
实例 5.45
说明
如果第五个域包含一个文本逗号,且后面是一个或者多个在 7 和 9 之间的数字,就打印该记录。
实例 5.46
说明
如果第二个域不包含模式 E,就打印第一个域和它后面紧跟着的第二个域($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
实例 5.47
说明
如果第三个域以模式 Joel 开头,打印第三个域和字符串“is a nice guy.”。注意,如果打印字符
串则将包含空格。
实例 5.48
说明
如果第八个域以两个数字结束则打印这个记录。
实例 5.49
说明
如果第四个域以 Chin 结束,就打印双引号内的字符串“The price is”,第八个域和一个句号。
实例 5.50
说明
如果记录包含模式 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
说明
默认的域分隔符是空格。打印第一个域($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
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)
。
% 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."}
说明
1 如果记录以模式 north 开头,就打印第一、第二和第三个域($1,$2,$3)。
2 如果记录以模式 south 开头,就打印字符串 The、第一个域的值和字符串 district。
3 -f 选项引导 awk 脚本文件名,后面跟着的是需要处理的输入文件名。
Linux 工具实验室 3
(File: lab3.data)
上面的数据库记录了在过去三个月中为党派运动捐款的人的名字、电话和捐款数额。
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 美元的人的资料
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
说明
1 如果第三个域等于 5346,则条件为真,gawk 运行默认动作——打印这一行。当 if 条件被隐含,
它就成为一个条件模板测试。
2 如果第三个域大于 5000,awk 就打印该行的第一个域。
3 如果第二个域匹配正则表达式 Adam,就打印这个记录。
4 如果第二个域不匹配正则表达式 Adam,就打印这个记录。如果在一个数值跟一个字符串进行比
较时,使用数值比较符号,那么字符串就会自动被转换为数字。如果这个关系符号是字符串比较
符号,那么数字就会转换为字符串。
6.1.2 条件表达式
条件表达式使用两个符号——问号和冒号给表达式赋值。这里有一个更简洁的方法,它
能实现 if/else 语句一样的效果。标准格式如下:
格式
conditional expression1 ? expression2 : expression3
实例 6.2
% awk '{max={$1 > $2} ? $1 : $2: print max}' filename
说明
如果第一个域大于第二个域,那么问号后面的表达式的值就赋给 max,否则冒号后面表达式的
值就赋给 max。
完整的形式如下所示:
if ($1 > $2)
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 为包含输入的文件)。
实例 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 数据验证程序
实例 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
(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
实例 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
说明
如果第二个域等于字符串 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
说明
如果第七个域不等于数字 5,就打印该行。
实例 6.12
Chin 4
Johnson 4
Nichols 3
说明
如果第七个域小于 4,就打印第四和第七个域。
实例 6.13
说明
如果第六个域大于 9,就打印第一和第六个域。
实例 6.14
说明
如果第八个域小于或者等于 17,就打印它。
实例 6.15
说明
如果第八个域大于或者等于 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
6.2.3 逻辑运算符
实例 6.16
说明
如果第八个域大于 10 并且小于 17,就打印记录。只有在两个条件都为真时记录才被打印。
实例 6.17
说明
如果第二个域等于字符串“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
说明
如果记录包含正则表达式 southern,第五个域就加 10 并打印。注意,数字以浮点格式打印。
实例 6.20
说明
如果记录包含正则表达式 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
说明
如果记录包含正则表达式 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,然后再打印。
实例 6.24
说明
如果记录包含正则表达式 northeast,第八个域就除 3,然后打印。精确到小数点后面 6 位。
实例 6.25
说明
如果记录包含正则表达式 southern,第八个域就乘 2,然后打印。
实例 6.26
说明
如果记录包含正则表达式 northeast,第八个域就除 3,然后打印余数。
实例 6.27
说明
如果第三个域以正则表达式 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
6.2.6 范围运算符
实例 6.28
说明
打印从以正则表达式 western 开头的记录到以正则表达式 eastern 开头的记录这个范围内的所有
记录。如果找到一个新的以正则表达式 western 开头的记录,则继续打印直到下一个以正则表达式
eastern 开头的记录出现或者到达文件末尾。
6.2.7 条件运算符
实例 6.29
说明
如果第七个域大于 4,print 函数就打印问号后面的表达式的值(字符串 high 和第七个域的值)
;
否则 print 函数就打印冒号后面的表达式的值(字符串 low 和第七个域的值)
。
6.2.8 赋值符号
实例 6.30
说明
如果第三个域等于 Chris,就把 Christian 赋值给第三个域并打印该记录。双等号表示等于关系的
判断,而单等号则表示赋值。
实例 6.31
说明
如果找到正则表达式 Derek,则第八个域就加上 12(+=)并打印结果。另外一种写法是:$8=$8+12。
实例 6.32
说明
将所有记录的第七个域都除以 3,并将余数赋值给第七个域,最后打印出来。
Linux 工具实验室 4
(File lab4.data)
上面的数据库包含姓名、电话号码和在过去三个月中为党派运动捐献的金钱的数额。
1.打印在第二个月中捐献数额在 100 以上的人的姓名。
2.打印在最后一个月内捐献数额少于 85 的人的电话和姓名。
3.打印在第一个月捐献数额在 75 和 150 之间的人的姓名。
4.打印三个月捐献总额少于 800 的人的姓名。
5.打印月平均捐献数额大于 200 的人的姓名和地址。
6.打印地址所在地区编码不是 916 的人的名字。
7.打印所有以行号开头的行。
8.打印每一个人的名字和捐款总额。
9.将 Chet 第二次捐款数额加上 10。
10.把 Tom Savage 改名为 Steve Hanson。
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 能把数字变量转换为字符串
变量,或者相反。变量通过赋值符号被赋值,参见表 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 是输入文件。
)
实例 7.2
% awk –F: -f awkscript month=4 year=2000 filename
说明
变量 month 和 year 都是自定义变量,且分别被赋值为 4 和 2000。在 awk 脚本中,这些变量使用
起来就像它们是在脚本中建立的一样。注意,如果参数前面出现 filename,那么在 BEGIN 语句中的
变量就不能被使用(参考“BEGIN 模板” )。
实例 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 当前的输入文件名
续表
变量名 变量内容
FNR 当前文件的记录号
FS 输入域分隔符,默认是空格
IGNORECASE 在正则表达式和字符串操作中关闭大小写敏感(仅在 gawk 中有效)
NF 当前文件域的数量
NR 当前文件的记录数
OFMT 数字输出格式
OFS 输出域分隔符
ORS 输出记录分隔符
RLENGTH 通过 match 函数匹配的字符串的长度
RS 输入记录分隔符
RSTART 通过 match 函数匹配的字符串的偏移量
RT 记录结束符在输入文本的时候,gawk 把它设为与 RS 相同
SUBSEP 下标分隔符
实例 7.5
% 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)
(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 等等。
初始化自定义变量的值;以及打印输出标题。
实例 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 模块
实例 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 重新定向符,文件名必须被双
引号括起来。当使用>的时候,文件就被打开并截断;一旦文件被打开,直到文件被明确地
关闭或者 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
说明
执行 UNIX 的 data 命令,并通过管道输出给 getline,然后再把输出赋值给自定义变量 d 并打印它。
实例 7.12
说明
执行 data 命令并通过管道输出给 getline,然后 getline 函数从管道中读取并将输入赋值给自定
义变量 d。split 函数把变量 d 转化为数组 mon,然后打印数组 mon 的第二个元素。
实例 7.13
% awk 'BEGIN{while("ls" | getline) print}'
a.out
db
dbook
getdir
file
sortedf
说明
命令 ls 的输出传递给 getline 作为输入,循环的每一个反复,getline 都从 ls 读取一行输入,并把
它打印到屏幕。输入文件并不是必要的,因为 BEGIN 模块在 awk 打开输入以前就执行了。
实例 7.14
(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 Output)
16
说明
awk 将逐行读取文件/etc/passwd 的内容,在到达文件末尾前,计数器 lc 一直增加。当到了末尾
后,打印 lc 的值,这时候 lc 的值是 passwd 文件的行数。
注意:如果文件不存在,getline 返回-1。如果到达文件的末尾就返回 0。如果读到一行,就返回
1,所以命令:
while (getline < "/etc/junk")
在文件/etc/junk 不存在的情况下将陷入无限循环,因为返回值-1 意味着逻辑真。
7.3 管 道
如果你在 awk 程序中打开一个管道,那么在打开下一个管道之前必须关闭它。管道符号
右边可以通过双引号关闭管道。在同一时刻只能有一个管道存在。
实例 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 模块后,管道被关闭。双引号引用的字符串必须确认管道是从那里被初始化的。
格式
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 命令来刷新屏幕。
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
% 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
说明
自动递增操作符使自定义变量 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
说明
如果记录以正则表达式 north 开头,就打印字符串“The record is”和 NR(记录号)的值。
实例 7.23
说明
打印 NR 和第 0 域。也就是打印记录号和整条记录。
实例 7.24
说明
如果 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
7 northeast NE 13
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
说明
用选项-F 设置域分隔符为冒号。如果 NR 是 5,就打印 NF(域的个数)
。
实例 7.27
说明
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
实例 7.28
说明
第六个域($6)与第七个域($7)相乘的结果存储在一个新的域——第九个域($9)中,打
印第九个域。原来的文件有 8 个域,现在是 9 个域。
实例 7.29
说明
每个记录的第十个域($10)的值都被赋值为 100。这是一个新的域,第九个域($9)不存在,
所以被看作一个空域。打印 NF(域的个数)、第九个域的值、空域及第 0 个域,也就是整条记录。
实例 7.30
说明
如果 NR 等于 1,也就是第一条记录,就打印环境变量 USER 和 HOME 的值。环境变量的值由
父进程传递给 awk 程序,通常这个父进程是 Shell,环境变量存储在叫作 ENVIRON 的特殊数组内。
7.5.3 BEGIN 模块
实例 7.31
说明
BEGIN 模块后面紧跟着就是动作块,这个例子中的动作是在打开输入文件以前打印字符串
“---------EMPLOYEE---------”
。注意,输入文件还没有打开,awk 也没有报错。
实例 7.32
说明
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
说明
BEGIN 被用做初始化变量,变量 FS(域分隔符)被赋值为冒号,OFS(输出域分隔符)被设置
为制表符。如果一个记录以正则表达式 Sharon 开头,就打印这个记录的第一、第二和第八域。每一
个输出域都以制表符分隔。
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 块。打印字符串“The total number of records is”和 NR(最
后一个记录的记录号)。
实例 7.35
说明
如果记录以正则表达式 north 开头,自定义变量 count 就加 1。awk 处理完输入文件后,打印变
量 count 的值。
实例 7.36
% cat awk.sc2
# Second awk script-- awk.sc2
1 BEGIN{ FS=":"; OFS="\t"
print " NAME\t\tDISTRICT\tQUANTITY"
print " \n"
}
3 END{
print "------------------------------------------"
% 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
$ 84.00
$ 94.00
$ 89.00
$ 94.00
说明
printf 函数默认把浮点数右对齐并格式化为 6 位数字,一位是小数点,小数点后面是 2 位。首先
对数字四舍五入,然后再打印。
实例 7.38
说明
这是一个左对齐的例子,打印包含 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
实例 7.40
说明
如果记录包含模板 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 模块结束。
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
----------------------------------------------------------------------
7.6 条 件 语 句
awk 的条件语句是从 C 语言那里借鉴来的,它们通过得到的结果控制程序流。
7.6.1 if 语句
以 if 开头的语句结构是动作语句。通过条件模块,if 可以被隐含。通过条件动作语句,
可以清楚地说明 if 用法,if 后面紧跟的语句必须放在括号内。如果括号内的表达式的值是真
(非 0 或者非空),就执行紧跟着的语句或者语句块。如果这些语句多于一条,那么每条语
句都需要以分号表示结束,且所有语句需要放在一对花括号中间,以便作为一个整体执行。
格式
if(expression){
statement; statement; ...
}
实例 7.42
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{
statement; statement;…
}
}
实例 7.43
说明
1 如果表达式是真,也就是第六个域大于 50,print 函数就打印第一个域和“Too high”
。否则执行
else 后面的语句,打印“Range is OK”
。
2 如果表达式是真,也就是第六个域大于 50,就执行语句块。否则就执行 else 后面的语句块。注
意,语句块需要用花括号括起来。
格式
{if ( expression ){
statement; statement;...
}
else if (expression){
statement; statement;...
}
else if (expression){
statement; statement;...
}
else{
statement;
}
}
实例 7.44
}
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 循环
实例 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 中,括号里
面第一个初始化语句只能初始化一个变量(在 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}
}
说明
如果第一个域包含 Peter,awk 就略过这一行,然后从输入文件读取下一行,脚本从头开始执行。
7.8.2 exit 语句
exit 语句用于结束 awk 程序。它终止对记录的处理,但是不会略过 END 模块,如果 exit
语句被赋予一个 0~255 之间的参数(例如 exit 1),这个参数就被打印到命令行,以判断退出
成功还是失败。
实例 7.49
(In Script)
{exit(1) }
说明
退出状态值为 0,表示退出成功,否则表示失败(这是 Linux 和 UNIX 中的惯例)程序退出状
态值完全是由具体程序的程序员提供。在这个例子中,程序退出返回值就是 1。
7.9 数 组
因为 awk 中数组的下标可以是数字和字母,所以称为关联数组(associative array)。数
组的下标(subscript)通常称为关键字(key)并且跟相应元素的值有关系。值和关键字都存
储在内部的一张对问题中的关键字和值应用散列法运算法则的表格里(也就是哈希表) 。由
于在哈希表中使用的技术,数组元素不是按照顺序存储的。如果显示数组的内容就会发现,
它们并不是按照你预料的顺序显示出来的。
数组跟变量一样,都是在使用的时候自动创建,awk 可以自己判断其存储的是数字还是
字符串。根据内容的不同,awk 的数组初始值是数字 0 或空字符串。你不需要声明 awk 数组
的大小。awk 数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹
配的次数等等。
7.9.1 下标与关联数组
用变量作为数组索引。变量可以作为数组下标值的索引,该变量的值可以是数字或者字
符串。
实例 7.50
% 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
说明
1 数组 name 中的下标是一个自定义变量 x。awk 初始化 x 的值为 0,在每次使用后增加 1。第二个
域的值被赋给 name 数组的各个元素。在 END 模块中,for 循环被用于循环整个数组,从下标为
0 的元素开始,打印那些存储在数组的值。因为下标是关键字,所以它不一定从 0 开始,可以从
任何值开始(数字或者字符串)。
2 awk 变量 NR 包含当前的记录号,通过 NR 作为下标,各个记录的第三个域的值被赋给数组中相
应的元素。最后,在 END 模块中,for 循环被用于循环整个数组,打印那些存储在数组的值。
格式
{for(item in arrayname){
print arrayname[item]
}
}
实例 7.51
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
说明
1 count 数组由两个元素组成,count["tom"]和 count["mary']。每一个元素的初始值都是 0,每一次
tom 被匹配,相应的元素值就增加 1。
2 相同的过程也被应用到 count['mary"]中。注意,即使每行多次出现 tom,也只被计算一次。
3 END 模块中打印存储在数组中的各个元素的值。
2 1 1
值
Name [“Tom”] Name [“Eliza”] Name [“Mary”]
下标
用域值作为数组的下标。任何表达式都可以用作数组的下标,域当然也可以。实例 7.52
中的程序计算所有在第二个域中的名字出现的次数并介绍了一种新的 for 循环方式。
for (index_value in array) statement
实例 7.53
说明
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
4622 Tom 53
2345 Mary 24
(The Output)
Tom 2
Eliza 2
说明
数组 dup 的下标是第二个域的值——每一个人的名字。储存在这里的初始值都是 0,每处理一
个新的记录就增加 1,如果名字出现重复,那么以该名字作为下标的元素值就是 2,以此类推。如果
数组 dup 的值大于 1,那么一个新的叫作 name 的数组就被使用,它也以第二个域作为下标,该数组
用来跟踪出现次数多于 1 的名字。
格式
实例 7.55
(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 函数将按顺序删除每一个元素。
实例 7.57
说明
1 把 NF 的值赋给变量 nf,即域的个数(该程序每个记录的域的个数是固定值 5) 。
2 进入 for 循环,在变量 x 中保存每行域的个数。
3 数组 matrix 是一个二维数组,一维是 NR,一维是 x。将每个域的值赋给 NR 及 X。
4 在 END 模块中,使用两个 for 循环,打印存储在数组 matrix 中的值。这个例子仅有的作用是说
明二维数组是可以模拟的。
实例 7.58
(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
说明
在上一个例子中,每一个参数都被打印。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 }
说明
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 }
说明
awk 把 ARGV 数组的元素看作输入文件。处理完一个参数后就向左切换到下一个需要处理的参
数,直到 ASRGV 为空。如果参数在使用完后立刻删除,就无法作为下一个输入文件来处理。
7.10.1 字符串函数
sub 和 gsub 函数。sub 函数匹配记录中最大、最靠左边的子字符串的正则表达式,并用
替换字符串替换这些字符串。如果目标字符串已经指定,正则表达式匹配目标字符串中最大
的、最靠左的子字符串,并有替换字符串替换它。如果没有指定目标字符串就默认使用整个
记录。
格式
实例 7.62
说明
1 第一次正则表达式 Mac 在整个记录中得到匹配,它被字符串“MacIntosh”替换。替换只发生在
这行中第一次匹配发生的时候。(关于在同一行中多次匹配的情况,请参考 gsub。 )
格式
实例 7.63
说明
1 正则表达式 CA 无论在记录中哪里被匹配,都将被替换为“California”
。
2 正则表达式 Tom 或 tom 无论在记录中哪里被匹配,都将被替换为“Thomas”。
格式
index(string, substring)
实例 7.64
说明
返回子字符串 low 在 hollow 中的位置,偏移量从 1 开始。
格式
length ( string )
length
实例 7.65
说明
length 函数返回字符串 hello 的字符数。
给定,就返回字符串的一部分。如果指定的长度超过实际长度,就返回整个字符串。
格式
substr(string, starting position)
substr(string, starting position, length of string)
实例 7.66
说明
在字符串“Santa Claus”中打印从位置 7 开始长度为 6 的子字符串。
格式
实例 7.67
说明
正则表达式/[A-Z]+$/表示在字符串的末尾搜索连续的大写字母。在字符串“Good ole USA”的
第 10 个位置找到字符串“USA” 。如果没有字符串匹配就返回 0。
实例 7.68
说明
1 match 函数设置变量 RSTART 为正则表达式第一次被匹配的位置。 RLENGTH 设置为子字符串长度。
2 substr 函数被用来在变量 line 中寻找子字符串,并把变量 RSTART 和 RLENGTH(由 match 函数
设置)作为子字符串开始位置和长度。
格式
toupper (string)
tolower (string)
实例 7.69
格式
split (string, array, field separator)
split (string, array)
实例 7.70
说明
split 函数使用“/”作为域分隔符把字符串“12/25/99”分割到一个叫作 data 的数组中。数组的
下标从 1 开始。打印数组 data 的第二个元素。
格式
variable=sprintf("string with format specifiers ", expr1, expr2, ...
, expr2)
实例 7.71
说明
把第一个和第三个域按照 printf 的规格格式化了(左对齐 15 个字符和右对齐 6 位浮点数)
,并
将结果赋值给自定义变量 line。参考“printf 函数”
。
7.10.2 时间函数
gawk 提供了两个分别用来获取时间及格式化时间戳的函数。它们是 systime 和 strftime
函数。
systime 函数。systime 函数返回从 1970 年 1 月 1 日(称为元年)开始到当前时间(不
计闰年)的整秒数。
格式
systime()
实例 7.72
说明
systime 函数的返回值保存在自定义变量中。Systime 函数返回从 1970 年 1 月 1 日开始到当前时
间的不计闰年的整秒数。
定义
数据格式
假设当前时间是 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)
续表
定义
数据格式
假设当前时间是 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
说明
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) 正弦
续表
名称 返回值
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 在 END 模块中打印除法结果的浮点数。
2 END 模块中 int 函数对除法的结果取整,然后打印这个整数。
7.10.5 随机数发生器
rand 函数。rand 函数用来产生一个大于等于 0 而小于 1 的随机数。
实例 7.75
说明
每次打印的值都一样。srand 函数能使 rand 函数从一个新的初始值开始生成随机数。否则每次使
用 rand 函数都会按照相同的顺序重复这些数。
实例 7.76
0.639485
0.657277
说明
srand 函数为 rand 函数设置了新的种子,开始点是当时的时间。每次调用 rand 都打印不同的值。
实例 7.77
说明
srand 函数为 rand 函数设置了新的种子。开始点是当时的时间。srand 选择了一个在 0~25 之间
的随机数,并把它取整。
7.11 自定义函数
自定义函数可以放在脚本中任何可以放置模板和动作的地方。
格式
给函数中本地变量传递值。只使用变量的拷贝。数组通过地址或者指针传递,所以可以
在函数内部直接改变数组元素的值。函数内部使用的任何没有作为参数传递的变量都被看作
是全局变量,也就是这些变量对于整个程序都是可见的。如果变量在函数中发生了改变,那
么就是在整个程序中发生了改变。惟一向函数提供本地变量的办法就是把它们放在参数列表
中,这些参数通常被放在列表的最后。如果函数调用的时候没有提供正式的参数,那么参数
就初始化为空。return 语句通常就返回程序控制并向函数调用者返回一个值。
实例 7.78
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
实例 7.79
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
说明
给自定义变量 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
实例 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
说明
数组 list 用 NR 作为索引。每次都将所处理行的第一个域赋给数组。在 END 模块中,for 循环循
环数组中的每一个元素。
实例 7.83
说明
每次正则表达式 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
END{for(item in region){
Print region[item], item
}
}
1 central
1 northwest
1 western
1 southeast
1 north
1 southern
1 northeast
1 southwest
1 eastern
说明
数组 region 用第一个域作为索引,这个值保存的是 region 被找到的次数。END 模块中用特殊的
awk for 循环循环称为 region 的数组。
Linux 工具实验室 6
(File lab6.data)
数据库中包含名字、电话和为党派运动捐款的数额。写一个awk脚本产生一个如下的
报告:
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
说明
从第一个字符开始的 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 的值,保存空域。
实例 7.87
% cat fixedfile
abc1245556
xxxyyyzzzz
说明
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
说明
第一个 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 程序将打印文件的每一行,且每一行前面都有文件
的名称。
实例 7.89
% awk '{ print FILENAME,$0 }' filel file2 file3 > bundled
说明
打印当前输入文件的文件名 FILENAME,然后是 file1 中的每一行记录。到达 file1 的末尾以后,
awk 将打开下一个文件 file2,做相同的事情,如此继续。输入文件被重新定向到一个叫作 bundled 的
文件。
解捆绑。下面的例子解释如何把两个文件分开。
实例 7.90
说明
第一个域是文件的名字,如果文件名与用户自定义的变量 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
1/1/99
#126
-56.89
PG&E
1/2/99
#127
-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
说明
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
% 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
Sincerely,
Sincerely,
说明
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
说明
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。
实例 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 字符数。
实例 7.99
说明
字符串 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
说明
输入文件分隔符设置为冒号(-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
实例 7.101
说明
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>
}
说明
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
说明
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
说明
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"};'
说明
从终端/dev/tty 读取输入,保存在数组 name 中。
实例 7.107
说明
while 循环用来每次处理/etc/passwd 文件中的一行。每次进入循环,getline 就读取一行且 lc 的值
增加 1。当退出循环时,打印 lc 的值,具体说只要 getline 的返回值不是 0,就打印/etc/passwd 文件
的行号,也就是一行被读取以后,继续循环。
7.14.4 控制函数
实例 7.108
说明
如果第五个域大于 4.5,就从输入文件 datafile 中读取下一行并从 awk 脚本开始处理这行(在
BEGIN 块后面)
。否则打印第一个域。
实例 7.109
说明
如果第二个域包含一个 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
7.14.5 自定义函数
实例 7.110
说明
1 在 BEGIN 块中,自定义变量 largest 被初始化为 0。
2 文件中的每一行,函数 max 的返回值都保存在变量 maxium 中,$5 作为函数 max 的参数。
3 自定义函数 max,函数语句包含在花括号中。每次从输入文件 datafile 中读取新的记录时,都要
调用 max 函数。
4 比较 num 和 largest 的值,返回其中比较大的值,并保存在变量 largest 中。
5 函数定义结束。
6 END 块打印变量 maxium 的最终值。
Linux 工具实验室 7
上面的数据库包含名字、电话和最近三个月给党派运动捐献的金钱的数额。
1.写一个自定义函数,用来返回指定月份的平均捐赠数额。月份通过命令行传递。
2.写另外一个自定义函数打印这个报告生成的数据。
交互使用
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
$ bash –version
GNU bash, version 2.03.0(1)-release (i686-pc-linux-gnu)
copyright 1998 Free Software Foundation, Inc.
$ echo $BASH_VERSION
2.03.0(1)-release
8.1.2 开始
如果 bash Shell 是你的登录 Shell,那么在你看到 Shell 提示符以前,它要按照处理链条
完成一系列处理。1
表 8.1 chsh 命令
选项 功能
-l,--list-shells 打印/etc/shells 中合法 Shell 的清单并退出
-s,--shell 指定登录 Shell
续表
选项 功能
-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,
图 8.2 文件初始化的顺序
实例 8.3
(Sample /etc/profile)
# /etc/profile
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 #
说明
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 循环里获得的,假
如该循环顺利地为所有变量赋值的话。
实例 8.4
(Sample .bash_profile)
# .bash_profile
# The file is sourced by bash only when the user logs on.
3 PATH=$PATH:$HOME/bin
4 ENV=$HOME/.bashrc # or BASH_ENV=$HOME/.bashrc
5 USERNAME="root"
说明
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 窗口对话最好的环境。
实例 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
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'
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
说明
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 命令前,
以防止函数陷入无限递归,即,无限的自我调用。
实例 8.6
(Sample /etc/bashrc)
说明
1 这里设置系统函数和别名,bash 的主提示符被设置为用户名(\v),@符号,主机名(\h),当前
目录名和一个美元符号(参见表 8.3)。这个提示符将出现在所有交互 Shell 中。
2 命令的别名和昵称通常在用户的.bashrc 文件中设置。别名是预先设置的并在 bash 启动以后可以
使用。可以使用它寻找文件在磁盘上的存储位置,即文件所在的目录,例如 which ls 命令的结果
是/bin/ls。
实例 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 被带–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) 。
选项名 开关缩写 功能
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 读入并执行完一个命令后退出
续表
选项名 开关缩写 功能
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 打开“输出显示”模式以进行调试
格式
实例 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
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 则关闭。
实例 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
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 中)里转换/
定义。
实例 8.11
说明
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
实例 8.12
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
$
说明
1 在"Hello 字符串后必须匹配双引号。
2 键入换行符后即出现次提示符。直到输入闭合的双引号才会显示次提示符。
3 显示 echo 命令的输出结果。
4 显示主提示符。
5 重置次提示符。
6 在'Hi 后必须匹配单引号。
7 键入换行符后即出现次提示符。直到输入闭合的单引号才会显示次提示符。
8 将 PS2 提示符设置为 shell 的名字(\s)跟着由冒号、PS2、>和一个由空格组成的字符串。
实例 8.14
说明
1 PATH 变量的值。路径由冒号分隔的元素列表组成,搜索时从左到右。路径末尾的点符号代表当
前工作路径。
2 PATH 变量的值是一组由冒号分隔的目录,需要注意的是,变量的末尾没有点号,这大概是出于
安全的考虑。
3 通过导出路径,子进程也能对路径进行访问。无需再单独导出,只要在同一行写为:“export
PATH=$HOME=/usr/acb:/bin:.”即可。
4 由于搜索路径中没有点符号,所以在当前工作路径执行程序 runit 时,Shell 无法找到该命令。
5 如果程序在当前工作目录下,则可以在程序名前加点符号和斜杠(./) ,以使 Shell 能够找到并执
行之。
现。尽管可以关闭它,但是如果没有什么特别的原因,最好别那样做。
实例 8.15
(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 命中次数,是因为该命令还没有使用过。
实例 8.16
$ source .bash_profile
$ . .bash_profile
说明
source 或点命令在当前 Shell 上下文中执行初始化文件.bash_profile。于是在 Shell 中定义了局部
和全局变量。用点命令可以在文件被修改后免于注销并再次登录 a。
a. 如果.bash_profile 直接作为一个脚本执行,就会启动一个子 Shell。这样就会在子 Shell 中设置变量,当退出子 Shell 时,
父 Shell 未受到这些变量设置的影响。
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!";
}
实例 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
......
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
说明
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 程序在/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
说明
如果第一个命令失败了(返回值为非零),就执行||后的命令,即如果 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)
实例 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
[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 将后台程序送到前台执行
续表
命令 含义
stop 挂起一个后台作业
stty tostop 如果后台作业将结果输出到终端,将其挂起
kill 向指定的作业发送 kill 信号
wait [n] 等待指定的作业及其返回值,n 为 PID 或作业号
jobs 命令的参数 含义
%n n 为作业号
%string 以 string 开始的作业名
%?string 含 string 的作业名
%% 当前作业
%+ 当前作业
%- 上一个作业,即当前作业的前一个作业
-r 列出所有正在运行的作业
-s 列出所有挂起的作业
8.2 命令行快捷键
8.2.1 命令和文件名自动填充
为了减少键入,bash 实现了命令和文件名的自动填充,该机制可以让你输入命令和文件
名的部分,然后按 Tab 键即可由系统自动完成余下的部分。
如果你键入了命令的第一个字符,然后键入 Tab 键,bash 将试图填完该命令并执行之。
如果由于文件名和命令都不存在,bash 无法完成自动填充,那么终端将继续保持原状态,光
标也将停在命令的末尾。如果有一个以上的命令符合开头键入字符,然后你接着又键入了
Tab 键,那么所有以这些字符开头的命令都会被列出来。
如果还有多个以相同字符开头的文件,bash 将填入最短的匹配文件名,直到遇上不同的
字符才将文件名完全填入,最后刷新光标。
实例 8.27
1 $ ls
file1 file2 foo foobarckle fumble
说明
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 编辑器的路径名
续表
变量 含义
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 箭头键
箭头 含义
↑ 上移历史列表
↓ 下移历史列表
→ 在历史命令中向右移动光标
← 在历史命令中向左移动光标
实例 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
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 参数 含义
-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
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
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 行。
实例 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
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。
表 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)从历史列表中获得该命令,并重新执行之。
实例 8.32
$ vi !:1
vi file1
$ ls !:2
ls file2
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 开始) ,并把它传
给 ls 命令作为参数。
(file3 是第四个单词)。
4 感叹号(!)加上美元符号($)指的是命令列表中最近执行命令的最后一个参数。最后的参数是
c。
5 脱字符(^)代表命令后的第一个参数。感叹号(!)加上插入记漏字符号(^)指的是历史命令
列表中最近执行命令的第一个参数。最近执行命令的第一个参数是 a。
6 星号(*)代表命令后的所有参数。感叹号(!)加上星号(*)指的是历史命令列表中最近执行
命令的全部参数。
7 显示但不执行历史列表中最近执行命令,历史列表被更新。现在你可以在该行中进行脱字符(^)
替换了。
实例 8.33
set –o vi
说明
将 vi 设置为内置编辑器,用于对命令列表进行编辑。
如果要换成 emacs,则要键入:
实例 8.34
set –o emacs
说明
将 emacs 设置为内置编辑器,用于对命令列表进行编辑。
表 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 替换行文本中的一个字母或任意文本
表 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)然后粘贴出来
实例 8.35
1 $ FCEDIT=/bin/vi
6.无论是保存退出还是直接退出编辑器,这些命令都将被执行,除非它们被注释掉或者删除掉。
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 命令用于启动编辑器并从历史列表中调入最后三条命令到编辑器的缓冲区。
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
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 转义序列
格式
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 列出)
实例 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'
表 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 文件中读入键绑定
实例 8.40
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
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 以全路径名的格式显示目录栈。如果没有选
实例 8.44
1 > pwd
/home/ellie
> pushd ..
/home ~
> pwd
/home
> pwd
/home/ellie
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.
说明
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 元字符
元字符 含义
\ 按文本含义解释后面接着的字符
& 在后台运行进程
; 命令分隔符
$ 变量替换
? 匹配一个字符
* 匹配零或多个字符
8.2.8 文件名替换
当对命令行求值的时候,Shell 用元字符来缩写与某些字符集匹配的文件名或路径名。
在表 8.15 中列出的文件名替换元字符被扩展为文件名字母列表。将元字符扩展为文件名的
过程也称为文件名替换或 globbing。如果使用了元字符而又没有任何文件名可匹配,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 和一个字符的文件名。
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}
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
说明
1 列出当前目录的文件(注意文件 youx) 。
2 Shell 会对?进行文件名扩展。任何当前目录下以 y-o-u 开头并跟着一个字符的文件名将被替换到
字符串中。youx 文件名被替换到字符串中,成为“How are youx”(这可能不是你所期望的结果)
。
3 通过在问号前面添加一个反斜杠,问号将被转义,这意味着 Shell 不会把它视为通配符进行解释
扩展。
4 通过添加前导反斜杠来转义换行符。次提示符将一直显示直至字符串被换行符所断开。问号(?)
被转义以防止进行文件名扩展。
符。波浪号本身代表用户主目录的全路径名 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 命令切换到并显示先前的工作目录。
实例 8.51
2 $ print * ?? [] ~ $LOGNAME
* ?? [] /home/jody/ellie ellie
10.不论是用单引号还是双引号引用,波浪符号都不会被扩展。
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 选项,所以当使用通配符*进行文件名扩展时,那些匹配模式且以点
字符开头的文件名也被扩展出来。
表 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])
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
11.bash 参考手册,http://www,delorie.com/gnu/docs/bash/bashref_56.html。
格式
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
2 $ name="Peter Piper"
$ echo $name
Peter Piper
3 $ x=
$ 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
说明
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
选项 值
-- 选项段的结束标志,余下的都是参数
-f name-value 形式被视为函数而不是变量
-n 将全局变量(已导出的)转换为局部变量,该变量将不会导出到子进程中
-p 显示所有的全局变量
实例 8.59
说明
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 后保持原来的值。子进程无法改变父进
程变量的值。
变量名 含义
_(下划线) 上一条命令的最后一个参数
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* 决定文件名扩展的整理顺序、范围表达式和等式的动作,以及匹配路径名的整理
顺序
续表
变量名 含义
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
命令清空。
实例 8.60
unset name; unset TERM
说明
unset 命令把变量从 shell 内存中删除
选项 解释
-e 允许翻译如下所有的转义序列
-n 把换行符压缩到输出行的末尾
-Ea 关闭对转义符号的翻译,包括关闭对那些默认情况下翻译的转义符号的
翻译
转义序列
\aa 警告(铃声)
\b 退格
\c 不换行打印
\f 填表
\n 换行
\r 返回
\t 制表符
\v 垂直制表符
\\ 反斜杠
\nnn ASCII 代码为 nnn 的符号
a. 在 bash 2.x 以前的版本中不能运行。
在使用转义序列的时候,必须使用-e 开关。
实例 8.61
说明
1 echo 命令将其参数打印到屏幕上。在 echo 命令执行以前 Shell 先做了变量替换。
格式
Printf format [argument...]
实例 8.62
printf "%10.2f%5d\n" 10.5 25
格式说明符 解 释
\" 双引号
\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
说明
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
1 $ fruit=peach
2 $ echo ${fruit:-plum}
peach
3 $ echo ${newfruit:-apple}
apple
4 $ echo $newfruit
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
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。这个设置是永久的。
实例 8.66
2 $ echo ${foo:+pears}
pears
3 $ echo $foo
grapes
$
说明
1 变量 foo 被赋值为 grapes。
2 特定修改符:+将检验变量是否被设置。如果被设置,那么 pears 将临时替换变量中的值;否则就
返回 null。
3 变量 foo 保持原来的值。
实例 8.67
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
说明
1 变量被赋值为 notebook。
2 变量 var 的子字符串,offset 是 0,也就是从 n 开始,字符串长度是 4 也就是以 e 结束。
3 变量 var 的子字符串,offset 是 4,也就是从 b 开始,字符串长度是 4 也就是以 k 结束。
4 变量 var 的子字符串,offset 是 0,也就是从 n 开始,字符串长度是 2 也就是以 o 结束。
子字符串的变量扩展。模式匹配参数用来从字符串的前边或者后边,去掉特定的部分字
符串。最常用的方法就是从路径中去点路径名。参考表 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
说明
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
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.
6 $ echo $#
13
7 $ echo $*
a b c d e f g h i j k l m
说明
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。
表 8.25 特殊变量
变量 含义
$ Shell 的 PID
- 当前 sh 的选项
? 最后一个命令的退出状态值
! 最后一个放入后台作业的 PID 值
实例 8.75
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
| 管道
< 输入重新定向
续表
元字符 含义
> 输出重新定向
Newline 命令终止
Space/tab 单词分隔符
$ 变量替换
*[]? 用于文件名扩展的 Shell 的通配符
单引号或者双引号必须成对使用。单引号可以保护一些特殊的元字符不被翻译,例如$、
*、?、>和<。双引号也可以保护一些元字符不被翻译,但是允许变量和命令替换。单引号可
以保护双引号,双引号也可以保护单引号。
跟 Bourne Shell 不同,bash 尽量让你知道是不是丢失了引号。在交互状态下,如果丢失
了引号就会出现次要提示符,进而在脚本中检查整个文件,看看是不是缺少引号。如果出现
引号不匹配的情况,Shell 就会尝试用下一个合法的引号来匹配它。如果下一个合法的引号
不能匹配该引号,脚本就会终止。引用可能是程序员最大的敌人之一,请参考附录 C 中的
Shell 引用规则。
反斜线。反斜线用来保护一个字母不被翻译。如果把反斜线放在一对引号中,它就不被
翻译。反斜线可以保护美元符号、反引号和双引号中的反斜线。
实例 8.76
3 $ echo \\
\
4 $ echo '\\'
\\
5 $ echo '\$5.00'
\$5.00
6 $ echo "\$5.00"
$5.00
说明
1 反斜线防止 Shell 对问号做文件名替换。
2 反斜线使得换行符被忽略,下一行可以作为这一行的一部分来理解。
3 因为反斜线是一个图书字符。因此它可以防止自己后面的反斜线被翻译。
4 单引号中的反斜线不被翻译。
5 所有在单引号中的字母都被看成没有特殊意义的字母本身。反斜线在这里没有任何特殊的含义。
6 在双引号中,反斜线可以防止美元符号被用做变量替换。
7 由于单引号中的反斜线不被翻译,所以 Shell 看到的是三个单引号。次要提示符出现。它停止所
有引用并把这些字符放在 echo 命令中。因为前两个单引号匹配,所以其他的部分就没有被引用。
Shell 就尝试给$5 赋值。又因为它是空的,最终打印.00
单引号。单引号必须成对使用,它可以保护所有的字符不被翻译。要打印一个单引号就
必须使用双引号或者反斜线来引用它。
实例 8.77
说明
1 在本行中,单引号没有成对出现,于是出现了次要提示符,等待引号匹配。
2 单引号可以保护所有的字符不被翻译。“Don’t”中的省略符号借助反斜线忽略。否则它就会与
美元符号前面的那个单引号匹配,这样,字符串最后的那个单引号就会无法匹配。问号和美元符
号在一对单引号中,因此 Shell 不会尝试去翻译它们。
3 在这个字符串中,单引号保护双引号。
双引号。双引号必须成对出现,它们允许变量和命令替换,但是保护其他符号不被翻译。
实例 8.78
1 $ name=Jody
说明
1 变量 name 被赋值为 Joby。
8.3.4 命令替换
在需要把命令的输出结果赋值给一个变量或者需要用字符串替换变量的输出结果时,我
们可以使用变量替换。所有的 Shell 都使用反引用的方法进行命令替换。bash 有两种命令替
换的方法:一种是老的方法把命令放在反引号中,另外一种是新的 Korn 风格,把命令放在
一对圆括号中见,并在前面缀上一个美元符号。
bash 做命令替换的方法是执行命令,并把其执行结果返回到标准输出。在传统的命令替
换方式下,反斜线除了在美元符号、单引号和斜线后面的情况以外,还都保持着自己的字面
意义。在 Korn 风格的方式下,括号中间的所有字符都被当作命令来看待。
命令替换是可以嵌套的,在使用的嵌套命令替换时,如果使用的是旧的风格,在内部反
引用前必须使用反斜线转义。
格式
实例 8.79
(Old Way)
1 $ echo "The hour is ‘data +%H’"
The hour is 09
3 $ ls ‘ls /etc’
shutdown
4 $ set ‘date’
5 $ echo $*
Web Jul 14 09:35:21 PDT 1999
6 $ echo $2 $6
Jul 1999
说明
1 data 命令的输出被替换到字符串中。
2 awk 命令的输出被赋值给变量 name,并显示出来。
3 在反引号中的,ls 命令的输出是/etc 目录下的文件清单。这些文件名将做为 ls 命令的参数。在/etc
目录下有同样名字的文件被列出来。
4 set 命令把 date 命令的输出结果赋值给位置参量。空格把相应的参量分隔开来。
5 $*表示所有的位置参量。date 命令的输出被保存在变量$*中。参量之间以空格分隔。
6 打印第二个和第六个参量。
7 把变量 dirname 设置为当前工作目录,使用命令替换嵌套。首先执行 pwd 命令,把当前目录的完
整路径传递给 Linux 命令 basename 作为参数。basename 命令把参数中除了最后一部分以外的所
有部分都剥离出去。在使用嵌套命令替换时,如果使用的是旧的风格,在内部反引用前必须使用
反斜线逃逸。
实例 8.80
4 $ machine=$(uname –n)
$ echo $machine
jody
5 $ pwd
/usr/local/bin
$ dirname="$(basename $ (pwd))" Nesting commands
$ echo $dirname
bin
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 利用替换
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.路径扩展
格式
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
实例 8.83
1 $ declare –a friends
3 $ echo ${friends[0]}
shery1
4 $ echo ${friends[1]}
Peter
5 $ echo ${friends[2]}
Louise
说明
1 内建命令 declare 创建一个数组,但是这个步骤并不是必须的。任何变量如果使用脚标的话都可
以自动成为数组。
2 数组 friend 被赋值为一串值:Sheryl、Peter 和 Louise。
3 通过数组名和脚标,访问数组 friends 的第一个元素,打印第一个元素到屏幕。
4 用索引 1 访问第二个元素,打印第二个元素到屏幕。
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
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 的方式,函数名后面是
一对空括号,然后是函数的定义。新的方式是使用关键字 function,后面是函数名和函数定
义。如果使用新的方式,那么括号就变成可选的部分了。函数定义被放在一对大括号中间。
它包括一些用分号分隔的命令。最后一个命令后面也需要分号。大括号前后需要保留一些空
间。所有需要向函数传递的参数都被当作位置参量来处理。位置参量在函数中是本地变量。
内建的 local 命令允许你在函数内部创建本地变量。函数是可以递归的,例如不断地引用自
己直到一定的次数。
格式
实例 8.85
说明
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 调用,因为这个函数已经被输出过。
实例 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.
8 $ echo $1 $2
johan joe
说明
1 定义函数 fun。关键字 function 后是函数的名字,然后是括号,括号中是函数的定义。括号中的
命令写在不同的行中,括号中的命令如果在同一行就需要分号分隔。左半边括号后面需要至少一
个空格。函数在使用前必须定义。
2 函数被调用时的动作就像脚本。函数中的每一个命令都被按照顺序执行。
3 在函数 welcome 中有两个位置参量。当参数传递给函数的时候,位置参量就被赋值。
4 作为参数 tom 和 joe 被赋值给$1,$2。这些位置参量只在函数内部使用,不能供函数外部使用。
5 在命令行设置一些位置参量,它们对于函数内部的位置参量没有影响。
6 $*显示当前被设置的位置参量的值。
7 调用函数 welcomn,Johan 和 joe 被赋值相应的位置参量。
8 函数中的变量使位置参量无效。
9 unset 命令使用-f 选项清空函数,这个函数不再被定义。
表 8.27 重新定向
重新定向操作符 作用
< 重新定向输入
> 重新定向输出
>> 追加输出
2> 重新定向错误
&> 重新定向错误和输出
>& 重新定向错误和输出
2>&1 重新定向错误到标准输出
1>&2 重新定向标准输出到错误
>| 重新定向输出的时候覆盖 noclobber
<>filename 如果是一个设备文件,就把这个文件作为标准输入和标准输出
实例 8.87
说明
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 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 >
说明
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.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
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.3.10 管道
管道就是把管道符号左边命令的输出发送给管道符号右边的命令作为输入。一个管道线
可以由不止一个管道组成。
下面这个例子就是计算登录人数的,把 tmp 文件中的命令的输出保存起来,使用 wc –l
命令统计 tmp 文件有多少行,然后再删除这个文件(即得到了登录人数)。
实例 8.91
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 多重管道
给命令的文本。当用户定义的结束符单独出现在行的最左边时,输入就结束了。结束符用来
代替 Control-D 来结束程序的输入。
在命令行上,结束符前有<<、制表符或者只有制表符。下面的例子用来说明 here 文档
的在命令行的用法,但实际上它更广泛地应用在脚本中。
实例 8.92
说明
1 Linux cat 程序等待用户的输入,直到用户定义的结束符 FINISH 单独出现在一行的最左边。
2 次要提示符出现,下面的文本都是 cat 的输入,变量替换开始替换 here 文档。
3 命令替换 $(date+%T)与 here 文档一同使用。
4 用户定义的 FINISH 出现,输入结束。在 FINISH 前和后都不能有空格。
5 显示 cat 的输出。
6 出现 Shell 提示符。
实例 8.93
说明
1 cat 等待用户的输入,直到用户定义的结束符 DONE 单独出现在一行的最左边。由于使用了<<-
符号,因此在结束符前面可以有一个或者几个制表符。这种情况在命令行下会有问题,但是在脚
本中没有任何问题。
2 结束符 DONE 被匹配,它前面有一个制表符。输入结束,输出被打印到屏幕上。
使用-i(交互模式)、-s(从标准输入读)和-m(允许作业控制)选项。见表 8.29。
选项 含义
-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.94
1 $ set -f
2 $ echo *
*
3 $ echo ??
??
4 $ set +f
说明
1 打开 f 选项,禁止文件名扩展。
2 星号没有被扩展。
3 问号没有被扩展。
4 关闭 f 选项,允许文件名扩展。
选项名称 快捷开关 作用
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 打开调试回响模式
选项 含义
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.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] 中止进程
续表
命令 作用
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?
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 个命令?如何把历史命令清单按逆顺序打印出来?
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。
7.取得三个文件的输出并把这些输出重新定向到文件 gottemall。
8.通过对 ps 命令和 wc 命令使用管道找出当前系统正在运行的进程有多少?
练习 6——变量
1.什么是位置参量?在命令行输入:
a.如何显示所有位置参量的清单?
b.哪个位置参量被赋值为 birds?
c.如何打印位置参量的个数?
d.如何从 Shell 内存中删除所有位置参量?
2.什么是环境变量?用什么命令可以显示它们的清单?建立一个称为 CITY 的变量,
并把它赋值为你的家乡。如何输出它?
3.什么是本地变量?设置一个本地变量,把它赋值为你的名字,最后清空它。
4.declare-i 的作用是什么?
5.$$变量将显示什么?$! 呢?
bash Shell 编程
9.1 介 绍
当命令不是从命令行开始执行而是从一个文件开始,这个文件就叫作 Shell 脚本,这种
模式叫作非交互模式。当 bash 开始非交互模式的时候,它就开始寻找环境变量,BASH_ENV
(ENV)和启动文件(通常是.bashrc)并给它们赋值。在读取环境变量文件后,bash 开始执
行脚本。 1
实例 9.1
1 $ chmod +x myscript
2 $ ls –1F 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 使用,
实例 9.3
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
---------------------------------------------------------------
(输出)
$ 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 开始。
实例 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
说明
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
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
说明
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。
实例 9.9
2 $ let i=i+1
$ echo $i
6
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 浮点数学计算
将十分有用。
实例 9.10
说明
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"
说明
1 在脚本 grettings2 中,位置参量$0 表示脚本名称。$1 表示第一个命令行参数,$2 表示第二个命
令行参数。
2 脚本 grettings2 没有任何参数传递,输出说明脚本的名称叫作 grettings2,因为没有参数,所以变
量$1 和 $2 为空,什么也不打印。
3 这次,有一个参数 Tommy 被传递给脚本,Tommy 被赋值给位置参量 1。
4 输入两个参数 Tommy 和 Kimberly。Tommy 被赋值给$1,Kimberly 被赋值给$2.
实例 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 控制
选项。
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。继续运行程序。
$*与$@的分别。只有在用双引号引用时二者才有区别。双引号中的$*使得变量变为一
个字符串;而双引号中的$@,相当于其中的每一个变量都被双引号引用,每一个单词都被
看作分开的字符串。
实例 9.14
1 $ set 'apple pie' pears peaches
2 $ for i in $*
> 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,
这是希望得到的结果。
9.5 条件结构和流控制
9.5.1 退出状态
条件命令允许你基于是或否,真或假来处理一些任务。if 是最简单的判断形式;if/else
允许你执行两路判断;if/elif/else 允许你执行多路判断。
bash 允许两种类型的条件控制:命令的成功或者失败,表达式的真或者假。在两种情况下,
都需要使用退出状态。退出状态值为 0 表示成功或者为真,否则为失败或者为假。?状态变量
保存了一个与退出状态值相互对应的数值。让我们通过看下面的实例 9.15,回顾一下退出状态
值的工作原理。
实例 9.15
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) 。
实例 9.16
The test Command
(At the command Line)
1 $ name=Tom
说明
实例 9.17
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
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。
判断操作符 判断是否为真
字符串判断
[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 必须包含在引号中。
操作符 含义
-+ 一元减和加
!~ 逻辑否
*、/、% 乘、除、余数
+- 加、减
在 bash2.x 版本以前没有的符号:
<<、>> 位逻辑左移位、右移位
续表
操作符 含义
<= >= <> 比较运算符
= =、!= 等、不等
& 位逻辑与
^ 位逻辑非
| 位逻辑或
&& 逻辑和
|| 逻辑或
=、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|= 快捷方式
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
-----------------------------------
if test expression
then
command
fi
or
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"
说明
1 问用户问题并要求回答,read 命令等待用户的回答。
2 方括号替代 test 命令用在判断表达式中。如果表达式为真,就返回 0,否则就返回非 0。如果变
量 answer 的值是 Y 或者 y,就执行 then 后面的命令。$answer 被双引号引用作为一个字符串。
否则判断命令就会失败。
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。
实例 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
(命令行)
$ bigfiles
Usage: bigfiles mdays size
$ echo $?
1
$ bigfiles 400 80
mdays is out of range
$ 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))。
实例 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,导致程序再次从第三行开始执行,如果变量包含一个值,
格式
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
then
3 echo "you are superuser."
4 else
echo "you are not superuser."
5 fi
说明
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。
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
(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 用户要求输入,输入将被赋值给变量 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 文件可执行
实例 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 配合使用。
9.5.6 null 命令
实例 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
说明
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
#
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??)
说明
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 值被匹配后,就从这里开始继续执行。
实例 9.31
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 后面的语句。
格式
实例 9.32
(The Script)
#!/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 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 等其他单词替代。
实例 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
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
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。
9.6.2 while 命令
while 命令判断它后面的命令,如果退出状态值是 0,就执行循环体内的命令,
直到 done,
控制返回循环体的顶部,while 命令将再次检验命令的退出状态,直到退出状态值为非 0,
程序继续执行 done 后面的语句。
格式
while command
do
command(s)
done
实例 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 命令打印换行符和字符串。
实例 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,
实例 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 关键字以后就自动返回循环体的
格式
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.
...
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 标志着循环的结束。
格式
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
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.
$ 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
$ 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)
#!/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
说明
3 位置参量列表向左位移一次。
4 循环体在这里结束。控制返回到循环的顶部。每次进入循环体,shift 命令都使参量列表中的值减
少 1。在第一次位移以后,$#的值为 4。当$#的值为 0,循环就结束。
实例 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[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] ]]
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 命令以后从此处开始执行。
实例 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
说明
1 命令替换以后$或者‘cat mail_list’,for 循环将按照单词重复文件 mail_list 中的名字清单。
2 如果名字匹配 richard,就执行 continue 命令,控制返回循环的顶部,对表达式求值的位置。因为
richard 已经被位移出列表,下一个用户 melanie 将被赋值给变量 name,旧的风格,if [ “$name” =
richard]; then。
3 continue 命令控制返回到循环顶部,忽略其他命令。
4 除了 richard 以外,所有的用户名都被打印,并被 mail 一份 memo 文件的副本。
5 循环体结束。
实例 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
说明
1 启动外层循环,第一次循环时 month 被赋值为 Jan。
2 内层循环启动。这层循环第一次变量 week 被赋值为 1。在内层循环单词列表被完全重复一遍以
后,才返回外层循环。
3 如果用户输入 n 或者回车,就执行第 4 行。
4 continue 命令带参数 2,控制流从外层第二层循环的顶部开始执行,若 continue 命令没有参数就
从最内层的循环顶部开始执行。
5 控制流返回到最内层的 for 循环。
6 done 终止最内层的循环。
7 done 终止最外层循环。
实例 9.51
(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
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 的内容,每一行前面都被加了行号在前面。
(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 中运行循环。
在后台运行循环。循环可以在后台运行,程序可以不等待循环的结束而连续运行。
实例 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.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
说明
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}'; }
说明
关键字 function 后面跟着的函数名称是 dir(有的时候函数名称后面有一对空的括号,它们并不
是必须的)。当输入 dir 以后,就执行花括号内的命令。这个函数的作用是只显示当前工作目录下的
子目录。第一个花括号两边的空格是必须的。
格式
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
2 if (( $# != 2 ))
then
3 Usage "$0: requires two arguments"
fi
4 if [[ ! ( -r $1 && -w $1 ) ]]
then
5 Usage "$1: not readable and writeable"
fi
6 echo The arguments are: $*
< Program continues here >
(Output)
$ checker
error: checker: requires two arguments
说明
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
(The Output)
$ do_increment
4,6 The sum is 6
7
说明
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
(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.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
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 中增加信息。
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,通常称为“必杀” 。
实例 9.62
trap 2 or trap INT
说明
为信号 2,SIGINT 设置默认动作。该动作是当终止键(Control-c)按下就杀死当前进程。
实例 9.63
trap " " 1 2 or trap "" HUP INT
说明
信号 1(SIGHUP)和信号 2(SIGINT)将被 shell 进程忽略。
实例 9.64
说明
1 trap 命令设置收到信号 2(Cotrol-c)时的动作是退出。
2 没有参数的 trap 命令显示陷阱清单。
3 如果参数是一个横线,则所有的信号都被恢复为原始值,而不论在 Shell 启动时它们是什么。
实例 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 标志循环结束。
复位信号。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
说明
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
(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.70
说明
1 程序 runit 有四个参数:x 是一个选项,n 是一个需要后面有一些选项的选项,filex 是一个独立参
数。
2 程序 runit 组合一些选项 x、n 和 200。filex 也是一个参数。
3 用选项组合 x 和 y 启动 runit。
4 用选项组合 x 和 y 启动 runit;选项 n 单独传递,30 作为它的参数。
5 用选项 n 和数字参数组合启动 runit。选项 x 和 y 组合在一起,而 filex 是单独的。
实例 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 就赋值给问号。
实例 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
说明
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,显示错误信息。
实例 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
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
# 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 ))"
$ 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.76
1 $ set a b c d
2 $ echo The last argument is \$$#
3 The last argument is $4
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
说明
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 所示。
选项 含义
-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 显示版本信息然后退出
续表
-quiet 在默认情况下启动不显示任何信息
-rcfile file 如果启动交互模式,就用这个文件替代~/.bashrc
-verbose 打开长选项,同-v
-version 显示版本信息并退出
实例 9.78
1 $ set -f
2 $ echo *
*
3 $ echo ??
??
4 $ set +f
说明
1 打开 f 选项,禁止文件名扩展。
2 星号不能扩展。
3 问号不能扩展。
4 f 选项关闭,文件名可以扩展。
选项名 快捷键 功能
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 允许作业控制
续表
选项名 快捷键 功能
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 版本中有效。
选项 含义
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 中,而不是覆盖覆盖这个文件
续表
选项 含义
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.12 内建命令
命令 功能
: 什么也不做,返回退出状态值 0
.file 从文件中读并执行命令
break[n] 参考循环命令
. 执行当前进程中的程序
alias 列出已经存在的命令并为这些命令建立昵称
bg 把作业放入后台
bind* 显示当前函数与键的绑定,或者把键绑定到函数 readline
break 打破无限循环
续表
命令 功能
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 的进程
续表
命令 功能
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——第一个脚本
3.你的脚本的第一行是什么?这一行对于你的作用是什么?
练习 2——命令行参数
1.写一个名为 rename 的脚本,包含两个参数,第一个参数是原始文件的名字,第二个
参数是新文件的名字。
如果发现用户提供的参数少于两个,就在屏幕上显示使用方法并退出脚本。下面是这个
脚本工作的例子:
$ rename
Usage: rename oldfilename newfilename
$
否则显示:
“No such user on our system.”
你还记得如何通过命令行检测退出状态吗?
练习 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
练习 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
c.保存一个收到邮件的人的清单。建立一个日志文件来完策划能够这个工作。在所
有邮件发送完毕以后,打印被发送邮件人的个数和他们的清单
练习 8——函数
写一个函数来处理菜单中的每一个项目。在用户选择了一个合法项目以后函数结束,再
次要求用户选择菜单中的项目。如果用户选择了非法的选项,就打印:
“invalid entry , try again.”
然后再次显示菜单。
6.在 lookup 脚本中的 View entry 项目下建立子菜单,向用户询问,他或者她是否愿意
查看特定的信息:
a.phone
b.address
c.birthday
d.salary
7.在程序运行中如果发出中断或者挂起的信号,trap 命令就执行清除操作。
交互式 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 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
10.1.2 启动
在 TC Shell 启动以前需要处理一系列进程,参考图 10.1。
启动
图 10.1 启动 TC Shell
图 10.2 如果以上的任何一个初始化文件存在,它们将按照这样的顺序被访问
10.2 TC Shell 环境
10.2.1 初始化文件
在 tcsh 启动以后,首先执行系统启动文件/etc/csh.cshrc 和用户目录下的两个文件(.tcshrc
和.login)。这些文件使用户能够初始化其环境。
实例 10.2
# /etc/csh.cshrc
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
14 umask 022
else
15 umask 022
endif
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 的文件名是规则文件名,就继续到下一行。
23 如果返回状态值是 0,就在当前环境中运行这个文件。
24 关键字 end 表示循环体结束。
实例 10.3
说明
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 表示从第一行开始的判断结束了。
~/.login 文件。它只在用户第一次登录的时候运行,其中包含环境变量的设置和终端设
置。图形应用程序通常从这里启动。因为环境变量具有继承的能力,因此只需要设置一次,
终端环境也不必要每次都重新设置,这些设置都放在~/.login 文件中。
实例 10.4
说明
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
2 echo $path
/usr/bin /bin /usr/bsd /usr/local/bin .
3 echo $PATH
/usr/bin:/bin:/usr/bsd:/usr/local/bin:.
说明
1 为 tcsh 设置 path。它包含一个以空白分割目录清单,当一个命令从命令行输入,Shell 就从左到
右的搜索这些目录。
2 显示 path 变量的值。
3 环境变量 PATH 显示出来的清单,跟 path 变量显示的清单是一样的,只是用冒号分割,它被传
递给从当前 Shell 启动的程序(其他的 Shell 用冒号分割路径)
。
5.在没有 vfork(2)的机器上,打印哈希存储区的号码和尺寸。
在 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 变量或者环境变量
%# >给普通用户使用
#给超级用户使用
续表
%{string%} 包含一个换码序列的字符串,只在改变终端属性时候使用,并不移动指针的位
置。这一项不能作为提示符的最后一项
%? 在提示符前显示命令执行的返回码
%R 在辅助提示符中,表示语法分析程序的状态。在第三提示符中,表示拼写检查;
在历史中,表示历史字符串
主提示符。在交互模式下,用户在主提示符下输入命令并回车。如果你不想使用默认提
示符,则可以在.tcshrc 文件中重新设置它。如果你只想在这个登录会话中改变主提示符,可
以在提示符下设置它。
实例 10.6
说明
1 主提示符被设置为主机名,它跟在用户名的后面,然后是空格和当前目录。这些字符串包含在一
对方括号中,以#结束。
2 显示新提示符,~表示用户的主目录,cd 命令改变目录到上一层目录。
3 新提示符包括当前工作目录,/home。这种方式的好处是用户可以方便地知道自己的位置。
辅助提示符。当编写在线脚本时,就会出现辅助提示符。辅助提示符是可以改变的。无
论 Shell 的程序的结构怎样,只要这个结构没有完成,还需要新行,辅助提示符就会出现。
在命令行下练习写脚本,一旦回车就不能退回更改了,历史中也不保存辅助提示符下输入的
命令。
实例 10.7
说明
1 这是一个在线脚本的例子,foreah 循环没有结束,还需要继续输入因此出现辅助提示符。循环体
对于括号内的每个单词都响应一次。
2 在第一次循环中,joe 被赋值给变量 pal。用户 joe 把 memo 中的内容通过 mail 发送了出去。第二
次 tom 被赋值给变量 pal,如此这样循环下去。
3 end 语句结束了循环体。在括号内的每一个单词都被处理以后,循环结束并显示主提示符。
4 显示主提示符。
实例 10.8
说明
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 程序在/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 每一个命令的输出都重新定向给文件 outputfile 了。如果没有括号,那么前两个命令输出就打印
到屏幕,最后一个命令的输出定向到输出文件。
2 pwd 显示当前路径。括号内的两个命令被子 Shell 处理。CD 是内建命令。在子进程中,目录被修
改为根,并显示路径。当退出子 Shell 以后,该路径恢复为原始路径。
命令的条件执行。条件执行的两个命令用两个&或者双竖线分隔。右边的命令是否执行
取决于左边的命令。
实例 10.12
> grep '^tom:' /etc/passwd && mail tom < letter
说明
如果第一个命令成功(退出状态值为 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 程序的内容传递给打印机。最后的&符号使得该程序在后台运行。
2 屏幕上出现两个数字:方括号中的数字表示第一个作业将被放在后台执行。第二个数字是这个作
业的 PID。
3 提示符立刻出现了。当程序在后台运行时,提示符就在前台提示你输入新的命令。
10.3 命令行快捷方式
10.3.1 历史
TC Shell 内建的历史机制。它在内存中按照顺序保存一定数量的历史命令的记录,称为
事件,而这些事件就是你在命令行输入的命令。当 Shell 从终端读取命令时,它先把命令分
割为一些单词,接着再把这行命令保存在历史列表中,然后再分析语法,最后执行它。前一
个输入的命令永远被保存。你可以重新执行以前输入的而现在保存在历史中的命令,而无需
重新输入它。从登录会话开始,你输入的命令就被追加到历史列表中,直到退出,历史列表
被保存为一个文件,称为.history。7 历史文件和历史列表有时候容易混淆。历史列表表示当
前内存中保存的命令行的清单。历史文件指的就是.history 文件,它是一个文本文件,用来
保存历史命令以供将来使用。设置内建变量 savehist 使得在退出时把历史保存到历史文件中,
当再次登录的时候,Shell 会把历史文件读到内存中来。内建命令 history 显示历史清单,它
支持多个参数以控制如何显示清单。见表 10.2。
选项 含义
-h 打印没有序列号的历史清单
-T 在说明表格中打印时间戳
-r 打印清单自动换行
-S[filename] 保存清单,如果给出 filename 就保存在 filename 中
-L[filename] 追加清单,如果给出 filename 就追加在 filename 后面
-M[filename] 跟-L 选项类似,只是用现在的清单替代 filename 中的清单
-c 清除历史清单,但是不清除历史文件
n N 是一个数字,表示希望看到的第几行的历史命令
实例 10.15
说明
历史清单显示出最后输入的命令。每一个事件前都有一个数字(事件号)和它们被输入的时间。
实例 10.16
1 set history=1000
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 后,格式就显示出来。例子中显示的只是一部分。
实例 10.17
1 set savehist
2 set savehist = 1000
3 set savehist = 1000 merge
说明
1 从历史清单中保存到历史文件中的命令将在下一次登录时显示在历史清单的顶端。
2 最后 1000 个历史清单替换历史文件并保存,在下次登录时将显示出来。
3 尽量替换已经存在的文件,当前的历史清单会替换已经存在的历史文件,并在下一次登录时导入
内存。
实例 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
说明
1 由于 history 变量被设置为 10,所以不论存储了多少行,都只显示最后的 10 行。
2 显示最后 10 个命令,且每个命令都带有编号。
实例 10.19
2 > history -c
说明
1 选项 h 使得显示没有编号。
2 选项 c 清除了历史记录。
实例 10.20
说明
历史列表按照逆序显示。
实例 10.21
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
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
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
> vi !:l
vi filel
> ls !:2
ls file2
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 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 的命令
续表
事件指示符 含义
!?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
查看编辑命令清单以及简短说明,则输入:
bindkey –l
查看按键与键操作联编的详细情况,则输入:
bindkey
要详细了解按键与命令的键操作联编情况,请参考“键操作联编按键部分” 。
内建 vi 编辑器。要编辑历史清单,则在命令行按 ESC 键。像标准的 i 编辑器一样,按 K
键向上滚动,按 J 键向下滚动。当找到你希望编辑的行后,使用跟 vi 一样的标准键编辑左右
移动、删除插入以及修改文本。参考表 10.5。当编辑结束后按回车键确定。命令就被执行并
追加到历史清单的最后一行中。如果你想增加或者插入文本,可以用任何插入命令(I、e、
o 及 O 等等) 。记住,Vi 有两中模式:命令模式和插入模式,在输入文本的时候,你可以一
直处在插入模式下,按 ESC 回到命令模式。
表 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 替换一行内一个字母或者任何数量的文本
表 10.6 emacs 命令
命令 功能
Ctrl-P 向上移动历史文件
Ctrl-N 向下移动历史文件
ESC < 移动到历史文件的第一行
ESC > 移动到历史文件的最后行
Ctrl-B 向后移动一个字母
Ctrl-R 向后搜索字符串
续表
命令 功能
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)
实例 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
实例 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
..... ...
字符 含义
^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
说明
1 bindkey 与一个控制键作为参数,显示这个控制键所绑定的命令。在这里,^L 的作用是清除屏幕。
2 Control-C 与终止符号捆绑在一起,通常用来终止一个进程。
3 小写的 j 是 emacs 下的一个自插入命令,只把一个字母放入缓冲区内。
4 为了查看可选择的 vi 键操作联编,需要用命令 bindkey –v 设置命令行 vi 编辑器。
5 通过选项 –a 显示可选的 j 的键映射。例如,vi 可以向下移动历史清单。
实例 10.27
说明
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 命令。
绑定键 与该键绑定的功能
bindkey –a 允许可选键映射
bindkey –d 恢复默认绑定
bindkey –e 使用 emacs 绑定
bindkey –l 显示所有编辑命令及其用法
bindkey –u 显示使用信息
bindkey –v 使用 vi 键操作联编
续表
绑定键 与该键绑定的功能
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.28
1 > ls
filel file2 foo foobarckle fumble
说明
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 键尝试自动完成的时候,所有可能的变量都
显示出来。
实例 10.29
1 > ls
baby box.gif file2 prog.c
baby.gif filel file3 prog.o
说明
1 显示当前目录下的文件名,注意,其中的一些文件名也扩展名。
2 设置变量 fignore,使得一些扩展名的文件在文件名自动完成时不出现,以.o 和.gif 扩展名结束的
文件名在文件名自动完成中将被忽略。
3 按下 Tab 键,只有文件 body 显示出来,文件 body.gif 被忽略,因为其扩展名是 gif。
4 当没有其他文件名可以匹配时,变量 fignore 也无效了,虽然文件的扩展名是.gif,但还是显示出
来了。
5 当 vi 编辑器被启动,prog 就被扩展为 prog.c。
实例 10.30
说明
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'
实例 10.31
1 > complete
alias 'p/1/a/'
cd 'p/1/d/'
ftp 'p/1/( owl ftp.funet.fi prep.ai.mit.edu )'
man 'p/*/c/'
3 > complete vi
vi 'p/*/t/'
7 > vi na[tab]mes
8 > cd sh[tab]ellsolutions/
说明
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 中的一个主机名。
表 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)/'
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
1 > complete
rm 'n/-r/d/'
find 'n/-exec/c/'
3 > rm –r te[tab]sting
说明
1 这些例子用来说明 n 类型完成。如果输入第一对斜线内的单词并被匹配,就根据单词列表完成下
一个单词。complete 命令列出两个 n 类型完成,一个是为 rm 命令,另一个是 find 命令。当 rm
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 堆栈目录变量
实例 10.34
1 > pwd
/home/ellie
> pushd ..
/home ~
> pwd
/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
3 > dite
4 > dite
CORRECT>date (y|n|e|a)? no
dite: Command not found.
>
5 > dite
6 > dite
说明
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,终止拼写纠正。
参数 功能
cmd 拼写检查命令
complete 完成命令
all 拼写检查整个命令行
10.3.6 别名
别名是 TC Shell 中用户自定义的命令缩写。如果命令有很多的参数和选项或者语法复杂,
别名就显得十分有用了。别名在命令行设置并不能被子 Shell 所继承。它通常在文件.tcshrc 文
件中设置。因为每次新 Shell 启动时它都要被执行一次,于是文件中所有别名设置在这时候都
将重置。别名也可以被传递到脚本中,但这会导致保护可移植性问题,除非它们在脚本中被
直接设置。
TC Shell 有一些预先设置好的别名,直到定义它们前都不会被定义。它们是:beepcmd、
cwdcmd、periodic 和 precomd。这些别名的定义请参考“特殊别名”部分。
实例 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 设置 more 命令的别名(昵称)为 m。
2 设置 more 命令的昵称为 m,这有助于你避免拼写错误。
3 lf 是 tcsh 的内建命令 ls –F 的别名,它的作用同 ls –F 一样,但是速度要快得多。
删除别名。unalias 命令用来删除别名。在别名前面加反斜线可以临时关闭别名。
实例 10.38
说明
1 用 unalias 命令从别名列表中删除别名 more。
2 要执行命令本身,临时关闭别名 cd。
别名环。当一个别名指向另一个别名,这个别名又指向原来的别名的时候,就发生了别
名环(Alias Loop)。
实例 10.39
说明
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。
表 10.12 作业控制命令
命令 含义
jobs 显示所有正在运行的作业
^z 停止作业,显示提示符
bg 在后台运行被停止作业
fg 把一个后台作业放在前台
kill 给执行的作业发送 kill 信号
作业命令的参数 含义
%n 第 n 个作业
%string 名字以字符串开头的作业
%?string 名字包含字符串的作业
%% 当前作业
%+ 当前作业
%- 当前作业的前一个作业
10.4.1 后台作业
&符号。如果命令需要运行很长的时间才能完成,则可以在命令后追加一个&符号,使
得程序在后台运行。tcsh 的提示符立刻出现在屏幕上,你可以继续输入新命令。这样,这两
个命令就在同一时刻运行了,一个在前台一个在后台。它们都把自己的输入写到标准输出。
如果你在后台运行一个程序,把程序的输出重新定向到一个文件,或者通过管道到打印机也
是一个好主意。
实例 10.40
说明
1 。a
find 命令在后台运行(如果没有-print 参数,find 不会打印任何信息到监视器)
2 方括号中的数字表示这是后台运行的第一个作业,这个作业的 PID 是 543。
3 立刻返回提示符等待新的命令。
挂起键序列。挂起键^Z 用来挂起程序。作业现在被挂起,就显示提示符,直到输入 fg
或者 bg 命令,否则程序不会继续。你如果在程序挂起时尝试退出登录,屏幕上将显示信息
“There are suspended jobs”。
jobs 命令和 listjobs 变量。tcsh 的内建命令 jobs 用于显示当前活动的、在后台运行或者
挂起的程序。上面提到的“运行”是指正在后台运行。当一个作业被挂起,它就被暂停了,
不再被执行。在这种情况下,终端仍可以接受新命令。如果你想在作业被挂起过程中退出
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
说明
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
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
10.4.3 作业调度
内建命令 sched 允许你创建一个作业列表来调度作业在指定的时间运行。没有参数的
sched 显示所有被调度的事件。其设置时间的格式是 hh:mm,小时可以是 24 小时或者 12
小时 AM/PM 的格式。也可以用+指定相对时间,而-可以把时间从列表中删除。
格式
sched
sched [+]hh:mm command
sched -n
实例 10.43
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
> 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 号作业被删除。
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 并显示当前目录
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”。
问号。问号匹配文件名中的一个字符。
实例 10.45
1 > ls
a.c b.c abc ab3 filel file2 file3 file4 file5
2 > ls ???
abc ab3
说明
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
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
说明
1 显示当前目录中的所有文件。
2 显示以 f 开头,后面是 oo、aa 或 umble 的文件名。花括号中的空格会导致错误信息“Missing}”
。
3 显示以 a 开头,后面是.c、c、b3、b4 或 b5 的文件名。
4 不必在 mkdir 命令后面输入参数 prog1、prog2 和 prog3,你可以用花括号来生成这些目录。
5 用括号中的字符串作为前缀或者后缀来扩展单词。括号中没有空格是十分重要的。
实例 10.48
说明
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
2 > cd ~/desktop/perlstuff
> pwd
/home/jody/ellie/desktop/perlstuff
3 > cd ~joe
> pwd
/home/bambi/joe
说明
1 ~表示用户主目录。
2 在~后面跟一个路径表示用户主目录。
3 ~跟在用户名后则表示用户的主目录,在这个例子中改变目录到用户主目录。
实例 10.50
说明
1 设置 noglob 变量,关闭通配符的特殊含义。
2 没有任何解释,显示元字符本身。
10.6 重新定向和管道
通常标准输出(stdout)到屏幕,标准输入来自键盘,标准错误送到屏幕。Shell 允许你
使用特殊的元字符重新定向输入和输出到文件。重新定向符号(<、>、>>和>&)跟在文件
名后面。在符号左边的命令执行以前,符号右边的文件名就已经被打开了。
管道用竖线表示。允许把一个命令的输出作为另外一个命令的输入。管道左边的命令叫
作“写者”,因为它写入管道。管道右边的命令叫作”读者“,因为它从管道读取。关于重
新定向和管道的元字符参见表 10.15。
表 10.15 重新定向的元字符
元字符 含义
command < file 重新定向从文件取得输入
command > file 重新定向输入到文件
command >&file 重新定向输出和错误到文件
command >>file 重新定向输出追加的文件末尾
command >>&file 重新定向输出和错误追加到文件末尾
续表
元字符 含义
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。
格式
实例 10.52
(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)
说明
1 cat 程序从第一个 DONE 开始读取输入,直到作为终结符的 DONE 出现。
2 问号作为辅助提示符,一直显示到用户自定义终结符 DONE 出现。 将这些行作为输入,
出现 DONE
后就不再接受输入。
3 终结符标志着输入的结束,在终结符的前后不能有任何空格。
4 在第一个 DONE 与最后一个 DONE 之间的文本作为 cat 程序的输出显示在屏幕上。最后一个
DONE 必须在其所在行的最左边,并且它的右边不能有空格和字符。
实例 10.54
(The Command Line)
1 > set name = steve
说明
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 的末尾。
重新定向输出和错误。符号>&用来重新定向标准输出和错误。通常,如果命令执行成
实例 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 文件中。因为语法
正确,因此什么也不显示。
2 这次目标文件不存在因而导致 cp 命令出错,发送错误信息到 stderr 也就是终端。
3 >&符号用来重新定向输出和错误到文件 errorfile,因为命令的输出只有错误信息,所以文件名叫
作 errorfile。
4 显示文件内容,显示 cp 命令产生的错误信息。
实例 10.59
说明
1 find 命令从当前目录开始搜索所有以.c 结尾的文件,并把结果打印到文件 outputfile 中。如果产
生错误,则该错误也将被输出到 outputfile 中。
2 find 命令被放在括号中。Shell 将建立一个子 Shell 来处理这个命令。在建立子 Shell 以前,括号
外面的单词首先被处理。badstuff 文件将被打开,准备接收输出和错误信息。当子 Shell 启动后,
它从父进程继承了输入、输出和错误。所以子 Shell 有来自键盘的标准输入,而标准错误被定向
到 badstuff 文件。现在,子 Shell 将处理 >符号。标准输出将分配给文件 goodstuff,而将错误分
配给 badstuff。见图 10.4。
表 10.16 变量 noclobber
续表
不设置 noclobber 文件存在 文件不存在
command > file 错误信息 创建文件
command >>file 追加在文件末尾 错误信息
覆盖 noclobber
command >!file 如果变量被设置,命令的输出将覆盖文件
command >>!file 覆盖变量:如果文件不存在就创建文件并追加到文件末尾。见实例
10.60
实例 10.60
9 > date >>! XXX Override noclobber for this command only
说明
1 在屏幕上显示 filex 的内容。
2 data 命令的输出被定向到文件 filex,文件的原始数据被覆盖。
3 显示 filex 文件的内容。
4 设置 noclobber 变量。
5 因为 filex 已经存在并且变量 noclobber 被设置,所以 Shell 报告文件存在且不能覆盖。
6 ls 的输出重新定向给文件 filex,因为操作符>!覆盖了变量 noclobber 的效果。
7 操作符>!的效果是临时的。它并不关闭变量 noclobber 的作用,只是在其被执行时覆盖文件。
10.7 变 量
tcsh 的变量只能包含字符串或者一组字符串。有些变量是内建的,可以通过设置把它们
打开或者关闭。例如变量 noclobber 和 filec。其他变量可以分配字符串值,如 path 变量。你
可以建立自己的变量,并把字符串或者命令的输出赋给它们。变量名是大小写敏感的,最多
可以由 20 个字符(数字、字母和下划线)组成。
有两种类型的变量:本地变量和环境变量。变量的作用范围就是其可见范围。本地变量
只在定义它的 Shell 中是可见的,而环境变量通常被称做“全局”的,其范围是该 Shell 以及
其所有的子 Shell。如果用 set –r 建立本地变量,将表示这个变量是只读的,也就是既不能被
改变也不能被释放。
在这里,美元符号($)是一个特殊的元字符,当把它放在变量名前面就是告诉 Shell
提取变量的值。当 echo 命令后面是一个变量作为参数时,在 Shell 处理完命令行和命令替换
以后,该命令的效果就显示变量的值。
特殊符号$?追加在变量名前面可以告诉你这个变量是否已经被设置。如果返回 1 就表示
真,也就是变量已经被设置。如果返回 0 就表示假,即变量还没有被设置。
实例 10.61
说明
1 设置内建变量 autologout 使得经过指定的交互时间后自动退出。
2 通过设置内建变量 histoey 为 50 来控制显示事件的数量。
3 设置用户自定义变量 name 为 Geoge。
4 设置用户自定义变量 machine 为 UNIX 命令的输出。
5 符号$?测试变量是否已经被设置。因为返回值是 1,所以可以认定该变量已经被设置。
6 由于$?测试返回值是 0,所以可以认定变量没有被设置。
10.7.1 打印变量值
echo 命令。内建 echo 命令打印参数到标准输出。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 命令打印参数到屏幕。变量替换以后执行 echo 命令。
2 echo 命令默认支持换码序列,>符号是 Shell 提示符。
3 带有-n 选项的 echo 命令打印不带换行符的字符串。
格式
printf format [argument...]
实例 10.63
printf "%10.2f%5d\n" 10.5 25
符号 含义
\” 双引号
\0NNN 八进制数,N 在 0 和 3 之间
\\ 反斜线
\a “哔”声音
\b 退格符
\c 终止输出
\f 表格馈送
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\xNNN 十六进制数,N 在 0 和 3 之间
%% 百分号
%b 字符串参数中带有\逃逸符号
实例 10.64
说明
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 变量旁边的花括号把变量名字从周围的字符中间隔离开来。
2 由于变量 varwork 没有定义,所以打印错误信息。
3 花括号把变量从追加在后面字符中分隔开来。展开变量$var,并把 work 追加在它的后面。
10.7.2 本地变量(可见性及命名)
本地变量只能在创建它的 Shell 中可见。如果本地变量在.cshrc 文件中被设置,那么每次
启动新 Shell 时变量都被重新设置。为了方便,本地变量名字通常采用小写字母命名。
设置本地变量。如果字符串中包含有不止一个单词,就需要引号引用。否则只有第一个
单词被赋值给变量。等号旁边有空格没有关系,但是需要注意的是如果等号的一边有空格那
么另外一边也一定要有空格。
实例 10.66
说明
1 本地变量 round 被赋值为 world。
2 本地变量 name 被赋值为 Santa Claus。双引号保证了 Santa Claus 是一个字符串而不是两个。
3 美元符号($)表示进行变量替换,也就是从变量中提取值。
4 变量替换。
5 启动一个新 C Shell(称为子 Shell)进程。
6 在子 Shell 中,变量 name 并没有定义,它只是在父进程中定义为本地变量。
只读变量。只读变量是本地变量的一种,一旦设置就不能改变或者释放,否则就会显示
错误信息。环境变量不能是只读变量。
实例 10.67
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 则是预先设置的内建变量。
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.
说明
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 变量包含原始值。
实例 10.70
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 单词列表放在括号内,且每个单词间用空格分隔。该数组叫作 fruit。
2 打印数组 fruit 中的单词。
3 打印 fruit 数组中的第一个元素。脚标是 1。
4 打印第二、第三和第四个元素,斜线使你能够指定范围。
5 由于数组没有第六个元素,所以脚标超出范围。
6 打印数组 fruit 中的所有元素。
7 $#加数组名字用于获取数组元素的个数。数组 fruit 包含四个元素。
8 $%加变量或者数组的名字显示单词中字符的个数。
9 因为脚标$#表示数组中所有元素的个数,因此如果使用它作为索引,例如[$#fruit],就可以打印
最后一个元素。
10 第二个元素被赋值为新值,打印这个值为 banana。
11 C Shell 中的 path 变量用来保存目录数组,以便搜索命令。一旦创建了这个数组,其中的单个元
素可以被访问和修改。
12 打印 path 数组中的第一个元素。
表 10.20 变量修改操作符
特殊操作符 举例 含义
$? $? Var 如果变量被设置返回 1,否则返回 0
$# $#var 返回 var 中的单词数
$% $%var 返回 var 中的字符数
实例 10.72
说明
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 变量 name 被赋值为字符串“Thomas Ben Savage”。
2 若把它看做数组,则该数组只有一个元素,就是整个字符串。
3 把变量放在括号内,创建一个单词数组,叫作 name。
4 显示数组的三个元素。
10.9 特殊变量和操作符
TC Shell 中有一些仅由一个字母构成的变量。$表示可以做变量替换。参见表 10.21。
表 10.21 变量及其含义
变量 举例 含义
$?var echo $?name 如果变量已经被设置就返回 1,否则返回 0
$#var echo $#fruit 打印数组中元素的个数
$%var echo $%name 打印变量或者数组的字符个数
$$ echo $$ 打印当前 Shell 的 PID
$< set name=$< 从用户输入直到换行符为止读取一行输入
$? echo $? 与$STATUS 相同,包含最后一个命令的退出状态值
$! kill $! 包含最后一个放入后台的进程 ID 号
实例 10.74
3 > echo $$
245
说明
1 设置变量 num 为空。变量前的 $?使得如果变量已经被设置,无论是否为空,就返回 1,否则返
回 0。
2 打印 path 变量。它是一个有三个元素的数组。变量前的$#变量提取并打印数组元素。
3 在这个例子中,$$表示当前进程的 PID。
4 $<从用户以空格或者换行符为结束标志的命令行输入中提取第一个单词,然后保存在变量 name
中。最后显示变量 name。
5 当$<变量被双引号引用时,接收整个用户命令行输入并保存到变量 name,但是不包括换行符。
最后显示变量 name 的值。
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 变量 pathvar 设置为/home/danny/program.c。
2 当:r 追加在变量后面,在显示的时候,扩展就被删除。
3 当:h 追加在变量后面,就显示路径的 head;这就是说,路径的最后一个元素被删除。
4 当:t 追加在变量后面,显示路径的 tail。
5 当:e 追加在变量后面扩展就显示出来。
6 当变量被设置为/home/*,星号表示当前目录下从/home/开始所有的路径。
7 当:gt 追加在变量后面,每一个路径元素的 tail 都显示出来。
10.9.2 大写和小写操作符
特定的历史操作符可以用来修改变量中字符的大小写。
表 10.23 大小写操作符(tcsh)
:u 把单词中第一个小写字母改为大写字母
:l 把单词中第一个大写字母改为小写字母
:g 对每个单词修改符只发挥作用一次
:a 对每个单词修改符发生作用的次数不受限制
实例 10.76
说明
1 当 :u 追加在变量后面,变量值的第一个字母变成大写。
2 当 :gu 追加在变量后面,变量值的每一个单词的第一个字母变成大写。
3 当 :agu 追加在变量后面,变量值的每一个字母变成大写。
4 当 :agl 追加在变量后面,变量值的每一个字母变成小写。
5 变量被重置,所有字母都变成小写。
10.10 命 令 替 换
10.10.1 反引号
我们可以通过将命令放在反引号中把 Linux 命令的输出赋值给变量,这叫作变量替换。
如果命令的输出被赋值给一个变量,它就以单词列表或者数组的形式被保存。
实例 10.77
说明
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
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 显示数组的前三个元素。
3 显示数组的第六个元素。
4 由于数组没有第七个元素,所以显示错误信息。
5 因为输出多于一行,所以换行符被替换为空格。这也许不是你所期望的输出。
实例 10.79
说明
1 rusers 命令被管道传给 awk。如果找到正则表达式 tom,awk 就打印第一个域。在这个例子中,
就是用户 tom 登录的机器名。
2 用户 tom 登录了三台机器,显示它们的名字。
3 使用$#得到数组元素的个数,3。
4 用元素的个数作为脚标显示最后一个元素。
5 显示数组。
6 shift 命令将数组向左移动。第一个元素被删除,脚标减少 1。
7 shift 后显示第一个元素。
8 shift 后显示数组长度,发现减少了 1。
10.11 引 用
TC Shell 有其自身具有特定含义的整套元字符。事实上,键盘上的所有按键几乎都是元
字符,它们都含有其自身以外的意义。下面是其中的一部分:
*?[]$~!^&{}()><|:;%
行内使用。
2.单引号保护双引号,双引号保护单引号。
3.除了历史字符(!)以外,单引号保护所有元字符。
4.除了历史字符(!)、变量替换符号($)和反引号以外,双引号保护所有元字符。
10.11.1 反斜线
反斜线用于引用单个元字符且只有反斜线才能转义历史字符序列(如!!、 !和!5)。反斜
线通常用来转义换行符。特殊的 tcsh 变量 back_quote 可以引用引号和反斜线,但是不能在 C
Shell 脚本中使用。见实例 10.80。
实例 10.80
说明
1 问号用于文件名扩展,它只匹配单个字符。Shell 将在当前目录下寻找拼写为 y-o-u,后面跟一个
单个字符的文件名。因为没有这样的文件,所以 Shell 将报告“No match”。
2 Shell 不会尝试解释问号,因为有反斜线在问号前面。
3 反斜线转义了换行符,使得字符串在下一行继续。
4 反斜线被单引号或者双引号引用,打印反斜线。在没有包含引号的情况下,可以用反斜线转义其
本身。
5 因为包含在引号中,反斜线被忽略,仅仅作为一个被引用的字符。
6 如果 tcsh 的变量 backslash_quote 被设置,则反斜线总是引用反斜线、单引号和双引号。
10.11.2 单引号
单引号必须在同一行成对的出现,且除了!,单引号可以转义所有的元字符。
实例 10.81
说明
1 字符串被单引号引用。除了! ,其他符号都不能当作元字符解释。
2 执行 cp 命令以后执行 echo 命令。因为!!可以当作元字符解释,通过使用反斜线可以再次执行
最后一个命令,而命令 cp 则变成字符串的一部分。
3 通过反斜线引用!! ,它们不必做历史替换。
4 引号必须出现在同一行,否则将报告“Unmatched”。
5 如果需要续行,反斜线可以用来转义换行符,引号可以在下一行的末尾才被匹配。即使 Shell 忽
略了换行符,echo 命令也不会忽略换行符。
10.11.3 双引号
双引号必须成对出现,在双引号内可以进行变量和命令替换,但是不能进行历史符号替
换。双引号中的美元符号($)无法转义。
实例 10.82
说明
1 本地变量 name 被赋值为 Bob,双引号中的美元符号($)可以用来做变量替换。
2 单引号在双引号内被保护。
3 无论是双引号还是单引号都无法避免 Shell 对叹号的解释。内建命令 history 用来查找最后一个以
引号开头的命令但是没有找到。
4 反斜线用来保护叹号不被解释成其他意思。
5 在双引号内使用反斜线无法使得美元符号($)逃避解释。
10.11.4 单引号与双引号的组合
伴随引用规则的融合在同一个命令中单引号与双引号有各种组合。
实例 10.83
说明
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
3.引号的运用法则如下:从第一个单引号的左边开始,在变量的美元符号($)以前插
入相应的单引号,如下所示:
> awk –F: '/^'$name'/{print "Phone is " $2}' datafile
在单词的右边,也就是$name 中 e 的右边,放上另外一个单引号。这个引号匹配花括号
外的引号。
10.11.6 引用变量
当必须引用变量时可以用:x 和:q 操作符来完成。
用:q 引用变量
:q 用来替换双引号。
实例 10.84
说明
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。
实例 10.85
说明
1 变量 things 被赋值为一个字符串。每个字符串包含一个通配符。变量中的元素个数是 1。
2 当尝试把字符串 things 创建为数组中的一个值。C Shell 把 things 做为通配符做文件名替换,并打
印替换结果 No match。
3 :x 保护变量 things 中的通配符不被 Shell 解释。
4 数组 newthings 包含三个元素。
5 打印数组元素,它们必须被引用否则其中的通配符就会被 Shell 解释做文件名替换。
6 :q 引用变量的作用同于用双引号直接引用变量。程序 grep 将打印文件 filex 中包含有模式 a??
的行。
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 中有效)
续表
内建命令 含义
-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 选项,用于清空目录堆栈
续表
内建命令 含义
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 中使用)
续表
内建命令 含义
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 命令可以按照不同的文件类型和扩展名,以不同的颜色显示文
件名
续表
内建命令 含义
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 可以嵌套
续表
内建命令 含义
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) 参考“循环命令”
beepcmd 发出声响
cwdcmd 改变目录后运行
periodic 每 tperiod 分钟运行一次,e.g., > set tperiod=30
> alias periodic date
precmd 在打印提示符前运行,例如 alias precmd date
当 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.如果变量被设置为只读、同步将不能工作。
续表
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
续表
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* 显示版本号,顺序是先是主版本好,然后是小版本号,然后是补丁号
续表
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 中使用。
- 指定登录 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.建立你自己的三个别名
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?
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.如何把所有的本地变量列表显示出来?如何把所有环境变量列表显示出来?
3.你可以在哪个初始化文件中保存本地变量?为什么?
4.建立一个叫作 fruit 的数组,并把 5 种不同的水果的名字放入这个数组。
a.打印数组
b.打印数组最后的一个元素
c.打印数组元素的个数
d.删除数组中第一个元素
e.如果在数组中保存一个非水果的元素,是否可以?
5.描述词表与字符串之间的区别。
用 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 是为用户、属组与其他用户打开执行权限的命令。
2 ls 命令的输出表示 joker 文件对所有的用户开放执行权限。文件名结尾的星号 * (之前用了-f 选
项才会出现)表明了它是个可执行程序。
11.1.4 脚本范例
在下面的例子中,用户将在编辑器里创建脚本。并在存盘后,使用 chmod 命令打开执
行权限使脚本运行。程序中如有错误,C Shell 会即时报错。
实例 11.2
说明
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
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.
说明
1 显示双括号里的语句。echo 命令后的–n 让下一句紧跟第一句之后。而在其他版本中的 echo 命令,
将\c 放在后面,也是这样的功用。如:echo“hello\c”。
2 无论你从终端输入什么,在换行符前的字符串都会原封不动地赋值给 name 变量。
3 变量替换后,显示此行。
11.2.2 由输入的字符串创建单词列表
由于用$<引导的读入信息都被看作字符串,你也许会想把字符串截成一个单词列表。也
可以用 Linux 的 head 命令读入用户的输入信息,并直接储存为单词列表。(“命令替换”部
分有详细讲述) 。
实例 11.5
说明
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
说明
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
tcsh 的选项
tcsh –x 脚本名 显示在变量替换后、程序执行前所有脚本源程序
tcsh –v 脚本名 程序执行前显示其每一行脚本源程序
tcsh –n 脚本名 解释但不执行命令
set 命令的参数
set echo 显示变量替换后、脚本执行前所有脚本源程序
set verbose 程序执行前显示其每一行脚本源程序
作为脚本的首行
#!/bin/tcsh –xv 同时打开 echo 和 verbose 的功能。这两个选项会分别激活或与其他的
csh 调用的参数激活
实例 11.8
说明
1 显示 TC Shell 脚本中的内容。 执行变量和命令替换的程序行也同时显示, 以便区别 echo 和 verbose
的不同。
2 由于用了 tcsh 的–v 选项,verbose 激活。此脚本的所有程序行输入都原封不变地显示在屏幕上。
最后程序执行。
3 tcsh 的–x 选项启动了 echo 命令。当变量和命令进行替换后,脚本中的所有程序行都输出在屏幕
446 第 11 章
上,然后程序才执行。由于此参数能让你看到变量/命令替换中什么被替换掉了,所以它比 verbose
选项更常使用。
实例 11.9
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
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。
参数 含义
$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]
说明
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
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
说明
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 此行表明:若 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
说明
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
说明
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
说明
1 若从命令行($#argv)输入的参数值不等于 1,则执行第二行。
2 用 echo 命令打印出脚本名($0)和字符串“require an argument”。
3 程序退出到提示符状态,值为 2。此值将会赋值给母程序的 status 变量。
4 if 引导的程序退出。
5 在此命令行,在无参数的情况下执行 checkon 程序
6 程序退出,其值为 2。此值将赋值给 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
实例 11.23
说明
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? "
说明
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
表 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 章
说明
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 若此文件名检验确定为可读、可写、可执行,则执行该行。
实例 11.27
说明
1 测试所有的文件看是否为可读可写的无格式文件。开头的两个文件返回“真”值(1 1) ,而最后
的文件返回“假”值(0) 。
2 若文件 hdd 为块专门文件则 filetest 命令返回 1 值,反之则返回 0 值。
3 若被测文件 fd 是可读可执行的符号连接,则 filetest 返回 1 值,否则返回 0 值。
-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
934407771
说明
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
$ 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 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 语句。
实例 11.31
case Linux:
echo Linux
breaksw
8 endsw
说明
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.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, 圆括号里括着单词列表。变量 person 将被赋值 bob 作为首次
循环。一旦 bob 被赋值给变量 person 之后,bob 就会左移至顶格,而 sam 就成了列表的第一个词。
当程序达到 end 语句时,循环就又会从顶端开始,此时,sam 就赋值给变量 person。这样的过程
一直持续到最后一个 fred 才告结束。此时列表已经到尽头而程序也就终结了。
2 第一次循环运行的结果是:文件 letter 的内容将会寄给用户 bob。
3 当达到 end 语句的时候,循环控制就回到 foreach 处,然后(列表中)第二个项就被赋值给变量
person。
实例 11.34
3 end
说明
1 命令替换,此处需要用到圆括号来实现。文件 maillist 的内容成为列表。列表中的每个名字(tom、
dick、harry、dan)按顺序一一赋值给变量 person。循环语句执行到 end 后程序控制就回到 foreach
重新执行,列表中的最先的名字被赋值于变量 person,然后其后的名字,再赋值,再执行。列表
一个名字一个名字的减少,直到列表中所有的名字都全部替换掉。
2 此处使用 here 文档。从第一个 EOF 开始把输入信息输送到 mail 程序,直到遇到最后一个 EOF
结束。 (注意:最后一个 EOF 必须得左顶格,而且前后都不能有空格。 )mail 会寄给列表中的每
一个人。
3 foreach 循环中的 end 语句标志着在该循环其中的一个循环体的终结。程序控制再次指向循环顶端。
实例 11.35
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 Script)
#!/bin/tcsh –f
# This script is called runit.
# It loops through a list of files passed as
# arguments
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
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
说明
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 参数组输送给脚本程序。
实例 11.41
#!/bin/tcsh –f
# This script is called baseball
1 echo –n "What baseball hero died in August, 1995? "
用 TC Shell 编程 469
说明
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 退出循环后,执行此行。
(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 循环终止后,程序控制从此开始。
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
11.10 储 存 脚 本
创建好(数个)脚本后,一般都把它们放到一个脚本目录里,然后修改路径使得可以从
任何位置执行这些脚本。
用 TC Shell 编程 473
实例 11.47
说明
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
练习 1——第一个脚本程序
3.脚本的第一行是什么?
练习 2——提取用户的输入
练习 3——命令行变量
如果不在,就打印:
"No such user on our system."
练习 4——条件和文件的测试
练习 5——开关语句
1.使用开关语句重写以下脚本:
#!/bin/tcsh -f
用 TC Shell 编程 477
# Grades program
练习 6——循环
要达到这样的效果,invite 文件就要如下所示的那样编写:
Dear XXX,
Hi XXX, I hope you can make it to our picnic…
当用户选择好有效的入口选项,选项的操作已经完成后,再问用户是否要再看菜单一次。
假如选择的是无效的入口,程序就打印:
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
实例 A.2
$ arch
i386
at-at、atq、artm 及 batch——在指定的时间执行命令
at –c job [job...]
atq [-V] [-q queue] [-v]
atrm [-V] job [job...]
batch [-V] [-q queue] [-f file] [-mv] [TIME]
实例 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...
实例 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。
©1989,1991。
bash 是自由软件联合会的注册商标, bash 是一种脚本兼容命令语言说明器,
它从标准输入或文件中读取命令执行。bash 兼有 Korn Shell 和 C Shell(ksh 和 csh)的某些
优点(参见第 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
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 ]
chmod 命令改变文件的存取模式。存取模式标明了权限和其他属性信息。存取模式可以
以绝对方式表达,也可以以符号方式表达。
实例 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 把用户 id 从 filex 改为 john,只有超级用户才能改变所有权。
2 递归改变 ellie 目录下的所有文件的所有权为 ellie。
chsh——改变登录 Shell
chsh [ -s shell ] [ -1 ] [ -u ] [ -v ] [ username ]
实例 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 ... ]
实例 A.14
1 compress –v book
说明
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 目录里。
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
说明
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 命令来处理, 表示编辑命令将会用来使两个文件相同。
df 命令显示文件系统信息,该文件系统要么就是所有的文件所在的那个文件系统,要么
就是默认的所有文件系统。
实例 A.21
df
Filesystem 1024-blocks Used Auailable Capacity
Mounted on
/dev/hda5 1787100 1115587 579141 66% /
du——显示磁盘使用情况
du [-arskod] [name ...]
实例 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 ... ]
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 ... ]
实例 A.25
1 fgrep '***' *
2 fgrep '[ ] * ? $' filex
说明
1 显示当前目录下所有文件中含有三个星号(*)的行,所有的字符都被视为原意,也就是说,原
义字符并非特殊情况。
2 显示文件中所含的由引号括起来的字符串的行。
file——通过文件内容判断文件类型
file [ -vbczL ] [ -f namefile ] [ -m magicfiles ] file ...
实例 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
实例 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 字节文件并将其删除。{}是用于表示每一个符合条件的文件名。
finger——显示本地或远端用户信息
finger [-lmsp] [user ...] [user@host ...]
实例 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 的倍数,否则制表符应该提前
扩展。
实例 A.30
% fold --help
Usage: fold [OPTION]... [FILE]...
Wrap input lines in each FILE (standard input by default),
writing to
standard output.
ftp——文件传输程序
ftp [-v] [-d] [-i] [-n] [-g] [host]
实例 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
实例 A.33
% fuser --help
usage: fuser [ -a | -q ] [ -n space ] [ -signal ] [ -kmuv ]
gawk——模式搜寻处理语言
gawk [ POSIX or GNU style options ] –f program-file [-- ] file ...
gawk [ POSIX or GNU style options ] [ -- ]program-text file ...
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 的变体,可由
以下的选项控制:
实例 A.34
说明
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 ... ]
发送信号结束一个或多个进程
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]...
如果最后一个参数指定的是一个已存在的目录,link 命令就把每一个给定的文件链接指
向该目录下的同名文件。如果只给定一个文件名,该文件将被链接到当前目录。如果只给出
两个文件名,第一个文件将被链接到第二个文件。如果给出两个文件而最后的参数是一个目
录,将导致出错,在 crossing 一个分区的时候,更常用的是符号链接。
选项:
-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 文件,而且只比较字母数字,
并忽略大小写区别。
选项:
-d 字典字母顺序,即只对字母和数字进行比较
-f 忽略大小写区别
-a 使用另一字典/usr/dict/web2
-t 指定终结字符串,即只比较到该字符串(包括该字符串)
说明
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
说明
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 选项则在每个
目录名前加上反斜杠\,在可执行文件前加上星号*,而在符号链接文件末添上@符号。
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...
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...
-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 文件已经存在,该文件就被覆盖。
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 ]
news 命令可以使用户获得最新发生的新闻和事件。按惯例,这些事件将存储在/var/news
中。如果不指定任何参数,news 命令就按最新访问时间排序,打印出/var/news 下的所有文
件内容,并且在文件前加上合适的标题。
nice——以低优先级来运行命令
nice [ -increment ] command [ arguments ]
实例 A.41
nohup lookup &
说明
lookup 程序将会在后台运行直到终结,中途即使用户退出注册也不会停止。任何生成的结果都
将会放到在当前目录下一个名为 nohup.out 的文件中。
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...
实例 A.42
1 ls | paste - - -
2 paste –s –d"\t\n" testfile1 testfile2
3 paste file1 file2
说明
1 文件分为三项列出并用制表符 Tab 粘贴到一起。
2 用 Tab 制表符把两行合并成一行,而且这个新的行即起了分隔符的作用。也就是说,头两行用
Tab 制表符粘连在一块,下两行用新行连接,再下两行又用制表符 Tab 来粘连,如此类推。-s 开
关使得 testfile1 中相连的行先粘连到一起,然后就是 testfile2……如此类推。
3 file1 中的一行和 file2 中的一行用制表符相连到一起,这样文件的行就犹如两项/列了。
pcat——见 pack
pine——适用于 internet 新闻和 email 的程序
pine [ options ] [ address, address ]
pinef [ options ] [ address, address ]
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]
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
%
说明
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
|-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...]
实例 A.46
1 rdate homebound atlantis
(Output)
rgrep——递归、高亮度的 grep 程序
rgrep [ options ] pattern [file] ......
命令行选项:
-? 额外的帮助(可用-?避免 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],匹配连续重复的任何字符
实例 A.47
说明
1 在当前目录(包括其子目录)搜寻所有带有.c 扩展名的文件。搜寻在行首含有 int 匹配的行,并
将该行以及其行数都打印出来。
2 搜寻所有带有.c 扩展名的文件,打印行首含有“int”的行,还有该行的行数(同上)。
rlogin——远程登录
rlogin [ -L ] [ -8 ] [ -ec ] [ -1 username ] hostname
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...
命令的输入,在本地标准输出上显示远程命令的标准输出,并在本地也同样复制远程命令的
标准错误。本地的中断、退出和终止信号也一样传输给远程命令。当远程命令执行完毕,rsh
即终止运行,若不指定命令,rsh 就用 rlogin 命令将当前用户登录到远程主机。
实例 A.49
说明
1 与远程机器建立连接,并显示该机器正在运行的进程。
2 以 john 登录远程机器,并执行例中的三个命令。
ruptime——显示本地机器的主机状态
ruptime [ -alrtu ]
ruptime 用于显示局域网上的每台机器的状态。这些状态信息是通过接收局域网上的机
器发出的广播包得到的。5 分钟内没有发出状态广播的机器将被视为已关机。通常,状态信
息列表按主机名排序,但也可以通过指定选项来改变显示顺序。
rwho——显示登录本地机器的用户名
rwho [ -a ]
script 把显示在终端上的所有东西创建为一个脚本。脚本将被写入到一个文件里。如果
没有给出文件名,就会储存到一个名为 typescript 的文件里。当 Shell 退出或键入 Ctrl-D 时,
脚本结束。
实例 A.50
1 script
2 script myfile
说明
1 在新 Shell 中启动一次脚本会话。所有在终端上显示的内容都被写入到名为 typescripte 的文件中,
输入^d 或 exit 即可结束本次会话。
2 在新 Shell 中启动一次脚本会话。所有在终端上显示的内容都被写入到名为 myfile 的文件中,输
入^d 或 exit 即可结束本次会话。
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 将所有“Elizabeth”字符串替换为“Lizzy”,并在终端上显示替换过程。
2 删除所有含“Dork”的行,并在屏幕上显示其余文本行。
3 显示 15 行至 20 行。
size——显示目标文件的段字节长度
size [ -f ] [ -F ] [ -n ] [ -o ] [ -V ] [ -x ] filename...
sleep 把执行程序挂起若干秒,主要用于在特定的一段时间后执行该程序。
实例 A.52
说明
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}
实例 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 ] ]
实例 A.54
说明
1 split 将文件 filea 分割为数个 500 行大小的文件,输出文件为 xaa、xbb……
2 split t 将文件 filea 分割为数个 500 行大小的文件,输出文件为 outaa、outbb……
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... ]
实例 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 将退格键(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]
实例 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 …
说明
1 将当前目录下(由.指定)的所有文件拷贝至磁带设备/dec/diskette 软盘中,并显示拷贝的文件。
2 显示磁带设备/dev/fd0 软盘中的内容列表。
3 解开磁带设备/dev/fd0 软盘的内容。
4 将当前目录下的所有文件制成名为 mytarfile 的档案文件。
tee——重复标准输出
tee [ -ai ] [--append] [--ignore-interrupts] [--help] [--version]
[ filename... ]
说明
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 命令的结果为非真。如果为零,
则结果为假。
timex 命令首先执行指定的命令,然后以秒为单位显示命令执行的总耗时、用户时间和
活动时间。如果指定选项的话,还可以列出命令执行过程的统计数据及其子进程的简要信息。
该命令还能显示执行期间系统的总活动时间。timex 将结果输出到标准错误。
top——显示 CPU 峰值处理能力
top [-] [d delay] [q] [c] [S] [s] [i]
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 <<
实例 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 码字符。
\11 制表符
\12 换行符
\042 单引号
\047 双引号
实例 A.65
说明
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 ]
实例 A.66
1 umask
2 umask 027
说明
1 显示当前的文件权限掩码。
2 用目录的权限值(777)。减去掩码(027),就得了 750。文件得权限值为 666,减去掩码 027,
得 640。当创建文件时,文件被赋予由 umask 得出的权限。
uname——显示当前机器名
uname [ -amnprsv ]
uname [ -S system_name ]
实例 A.67
1 uname -n
2 uname -a
说明
1 显示主机名。
2 显示机器名、网络名、操作系统的 release、操作系统名及其版本,相当于同时指定-m、-n-r 和-s
选项。
实例 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
说明
1 删除文件 file1 中相邻的重复行,并将结果输出到文件 file2。
2 显示从第 3 个域起重复的行。
unit——转换数量的度量衡
unit 命令用于转换各式各样的标准单位为其他度量衡相应的单位。该命令与用户的交互
风格如下:
You have: inch
You want: cm
* 2.540000e+00
/ 3.937008e-01
实例 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 ... ]
wc 统计文件或标准输入(如果不指定任何输入文件的话)的行数、单词数和字符数,
在这里,单词的定义是:以空格、制表符或换行符分隔的字符串。
实例 A.71
1 wc filex
2 who | wc -1
3 wc –1 filex
说明
1 显示 filex 文件的行数、单词数和字符数。
2 who 命令的输出结果被重定向到 wc,然后由 wc 命令显示行数。
3 显示文件 filex 的行数。
who——显示当前登录用户
write——向其他用户发送消息
write username [ ttyname ]
write 命令用于从一个用户终端将文本行写到另一个终端上。
xargs——构建参数并执行命令
xargs [ flags ] [ command [ initial–arguments ] ]
xarg 命令将文件的内容填充至命令行中,动态生成命令行。
实例 A.72
说明
1 将目录$1 中所有文件移到目录$2 中,在每次移动文件前均显示 mv 命令。
2 -p 选项用于在每次移除文件时提示用户。
zcat(对压缩文件进行解压,并将结果写到标准输出。类似于 uncompress)——c
zcat [ file . . . ]
实例 A.73
zcat book.doc.Z | more
说明
解开压缩文件 book.doc.Z 并将输出重新定向到 more 命令的输入。
zmore 是一个过滤程序,用于在软拷贝终端上一次显示一屏大小的压缩文件或普通文件
内容。zmore 命令对由 compress,pack 或 gzip 等命令压缩的文件均有效。如果指定文件不存
在,则查找以.gz,.z 或.Z 为后缀的同名文件。显示形式类似于 more 命令:一次显示一屏。
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 命令参数中目录名拼写错误的自动更正、自动的、周期性的以及定时事件(定时任
务、特殊别名、自动退出登录和终端锁定等)。
l 新的内建变量(gid、loginsh、oid、shlvl、tty、uid、version、HOST、REMOTEHOST、
VENDOR、OSTYPE 和 MACHTYPE)
l 只读变量
l 更好的错误报告机制
B.2 bash 与 sh
续表
特征 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)
续表
特征 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
续表
特征 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
续表
特征 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
正确使用引用
的步骤
1.放在字符前面保护这个字符不被翻译
2.像单引号一样放在字符的两边
1.必须成对出现
2.保护所有字符不被翻译,除了:
a.本身
b.“!”和“.”(csh)
c.\
1.必须成对出现
2.保护所有字符不被翻译,除了:
a.本身
b.“!”和“.”(bash,csh)
c.变量替换中的$
d.命令替换中的反单引号“``”
表 C.1 使用单引号和反斜线
表 C.2 使用双引号
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
第二步:
只插入变量,不做其他改变,所有的引用都保持原样。
在变量$name 左边添加一个单引号,与最左边的单引号组成一对,于是两个单引号之间
的部分就被保护起来不会被翻译。在变量$name 右边添加一个单引号,与最右边的单引号组
成一对,于是两个单引号之间的部分就被保护起来不会被翻译。这样变量就暴露出来了。
第三步:
在变量左右增加一对双引号,这样可以保证变量内部的值可以被扩展,同时,即使这个
值中包含空格,也会因为双引号的引用,而不至于导致命令行出现语法错误。
查一下引号的个数,单引号和双引号都应该是偶数。
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.开始引用。
在变量$oldname 左边添加一个单引号,与最左边的单引号组成一对,于是两个单引号
之间的部分就被保护起来不会被翻译。在变量$oldname 右边添加一个单引号。在变量
$newname 左边添加一个单引号,与变量$oldname 右边的单引号组成一对。于是两个单引号
之间的部分就被保护起来不会被翻译。在变量$newname 右边添加一个单引号。与最右边的
单引号组成一对,于是两个单引号之间的部分就被保护起来不会被翻译。
4.计算单引号的个数。如果是偶数那么单引号就全部成对出现了。如果不是,那么你
一定是忘记某个步骤。
5.把所有的变量都用双引号引用起来。