级别: 中级 Martin C Brown (questions@mcslp.com ), 自由撰稿人和顾问, MCslp
2007 年 4 月 26 日 研究 Z Shell (zsh) 的重要组成部分和如何使用其功能来简化您的 UNIX® 系统管理任务。zsh 是一个流行的 Shell,是对原始 Bourne 和 Korn Shell 的替代。它提供了印象深刻的系列附加功能,包括用于自动完成不同命令、文件和路径以及用于将键绑定到功能和操作的改进。
关于本系列
典型的 UNIX® 管理员拥有一套经常用于辅助管理过程的关键实用工具、诀窍和系统。存在各种用于简化不同过程的关键实用工具、命令行链和脚本。其中一些工具来自于操作系统,而大部分的诀窍则来源于长期的经验积累和减轻系统管理员工作压力的要求。本系列文章主要专注于最大限度地利用各种 UNIX 环境中可用的工具,包括简化异构环境中的管理任务的方法。
zsh 背景介绍
UNIX 和 Linux® 环境下的 Shell 通常可归入两个类别之一,这两个类别基于最初的 UNIX 版本所附带的原始 Shell。这两个类型分别是 Bourne Shell 和 C Shell;后者的独特之处在于其格式和结构类似于 C 编程语言的格式和结构。
Bourne Shell 比 C Shell 更容易使用和理解,但是对于您可能希望在 Shell 编程环境中实现的复杂脚本编程,它可能就不太实用了。Korn Shell 提供了 Bourne Shell 的易用性和附加的作业控制扩展(允许您容易地管理多个后台作业)、命令行编辑和历史记录,以及用于简化编程的附加 C Shell 元素。
Z Shell (zsh) 是在考虑交互式使用而不是编程的情况下设计的,因此它整合了大量显著简化命令使用和运行的功能。这些功能的示例包括更广泛的文件名匹配 (globbing)、用于重定向输入和输出的多个 I/O 流,以及一个可完全自定义的命令行完成系统。
文件名生成
文件名 globbing 是将文件名或文件规范转换为供命令行(例如,在复制或移动文件时)使用的文件列表的后台过程。基本文件名 globbing 包括使用 ? 来代表单个字符,或使用 * 来代表一个或多个字符。
例如,若要列出所有的 C 源文件,您可以使用 清单 1。
清单 1. 列出所有的 C 源文件
$ ls *.c
barney.c betty.c fred.c wilma.c
|
并且您可以使用字母集合(就像您可能在正则表达式中使用的那样),例如,用于列出具有“c”或“o”扩展名的文件,如清单 2 所示。
清单 2. 列出具有“c”或“o”扩展名的文件
$ ls *.[co]
barney.c betty.c fred.o
barney.o fred.c wilma.c
|
使用 zsh,相同的通配符仍然有效,但是您还可以使用扩展的 globbing 来指定附加选项。若要启用扩展的 globbing,可以设置 EXTENDEDGLOB 环境变量,或者使用: $ setopt extendedglob。
您现在可以使用诸如 ^ 字符等构造,以显示与给定的文件规范不匹配的所有文件。例如,若要列出与 *.c 规范不匹配的所有文件,可以使用清单 3。
清单 3. 列出与 *.c 不匹配的文件
zsh$ ls ^*.c
barney.o betty.h fred.h fred.o
|
表达式 <x-y> 匹配一系列整数。例如,如果您已使用日期对访问日志归档,则可以选择在某个特定日期范围内的所有文件。为实现这点,可以按顺序使用年、月、日来对文件命名,并使用零来补齐空缺。例如,若要列出 2006 年 11 月 3 日和 10 日之间的日志,您可以使用清单 4。
清单 4. 列出 2006 年 11 月 3 日和 10 日之间的日志
zsh$ ls access<20061103-20061110>.log
access20061103.log access20061106.log access20061109.log
access20061104.log access20061107.log access20061110.log
access20061105.log access20061108.log
|
您还可以使用类似于正则表达式的组来匹配文件。例如,使用清单 5 来列出所有名为 fred 和 barney 的文件。
清单 5. 列出所有 fred 和 barney 文件
zsh$ ls (fred|barney)*
barney.c fred.c fred.o
barney.o fred.h
|
通过使用 **/,还可以搜索子目录;该过程是递归的,例如 find 命令,以便您能产生 find 命令的等效结果,例如 $ find . -name "*.c"。
在 zsh(带扩展的 globbing)中,这可以扩展为: zsh$ ls **/*.c。
您也可以组合进一步的示例,例如使用清单 6 中的命令来查找子目录中的所有 C 源文件和编译后的目标文件。
清单 6. 查找所有 C 源文件和编译后的目标文件
zsh$ ls **/*.(c|o)
glob/barney.c glob/betty.c glob/fred.o
glob/barney.o glob/fred.c glob/wilma.c
|
最后,zsh 支持许多后缀修饰符。若要获取可执行文件列表,可以在 globbing 表达式结尾使用后缀修饰符 (*)(请参见清单 7)。
清单 7. 获取可执行文件列表
您还可以在 globbing 表达式结尾使用 (x) 来获取可执行文件列表(请参见清单 8)。
清单 8. 使用 (x) 来获取可执行文件列表
上述列表文件可由文件的所有者执行。大写的 X 将选择可由其他人执行的文件。同样的原理也适用于文件的 R/r(可读取)和 W/w(可写入)模式。
命令或进程替换
在大多数 Shell 环境中,您都可以使用基本的进程替换来将一个命令的输出嵌入到另一个命令的输入或参数中。这可以使用反引号运算符来实现(请参见清单 9)。
清单 9. 使用反引号运算符来执行进程替换
$ emacs `find . -name "*.html"`
|
您可以在 zsh 中使用许多选项。最主要的方法是使用 $() 构造。此构造提供对反引号的直接替代,并且它还具有更容易在某些命令组合中嵌入和嵌套的优点。例如,您可以将清单 9 重写为清单 10。
清单 10. 对使用反引号来执行进程替换的替代
zsh$ emacs $(ls **/*.html)
|
这里的进程替换运行目录列表组件并返回一个文件名列表,后者又提供给 emacs 命令的参数列表。
另一个有用的构造是 =(list) 结构。当使用此功能时,括号中的元素会生成一个临时文件,并返回该文件的名称。例如,您可以使用清单 11 来生成一个文本文件。
清单 11. 生成一个文本文件
zsh$ cat =(print -l tom dick harry)
tom
dick
harry
|
更有用的是,您可以将它与其他元素组合,以支持更复杂的输出和过滤。例如,您可以使用以下命令(请参见清单 12)来获取与 imapd 和 httpd(IMAP 邮件服务和 Apache http 服务)匹配的进程列表。
清单 12. 获取与 imapd 和 httpd 匹配的进程列表
zsh$ ps -ax |fgrep -f =(print -l httpd imapd)
406 ?? Ss 0:02.05 /usr/sbin/httpd
426 ?? S 0:01.32 /usr/sbin/httpd
429 ?? S 0:06.42 imapd: sulaco.mcslp.pri [192.168.0.101] appleblog user.appleblog
434 ?? S 0:57.81 imapd: sulaco.mcslp.pri [192.168.0.101] mcarc user.mcarc
435 ?? S 0:00.14 imapd: sulaco.mcslp.pri [192.168.0.101] mlists user.mlists
436 ?? S 0:00.12 imapd: sulaco.mcslp.pri [192.168.0.101] play user.play
437 ?? S 0:01.16 imapd: sulaco.mcslp.pri [192.168.0.101] mc user.mc
507 ?? S 0:01.25 /usr/sbin/httpd
|
在清单 12 中,print 命令在单独的行上将每个参数输出到一个临时文件。然后 fgrep 使用该文件作为针对进程列表的匹配列表。此临时文件在该命令完成后被删除。
由于该功能从输出创建临时文件,您可以使用它来执行以前要求您自己创建并删除临时文件的功能和序列。例如,若要比较两个不同目录的文件列表,您可以将每个目录中的文件列表输出到一个临时文件,然后使用 diff 命令来比较输出。使用 zsh,在单个命令行上即可实现相同的结果(请参见清单 13)。
清单 13. 比较两个不同目录的文件列表
zsh$ diff =(ls new) =(ls old)
3d2
< barney.o
9d7
< fred.o
|
在此示例中,两个被替换的 ls 命令生成一个临时文本文件,然后 diff 命令对该文件进行内联比较。
多个 I/O 流
与进程替换系统相关的另一个功能是更广泛的重定向系统。在大多数 Shell 中,您都习惯于针对文件执行重定向(请参见清单 14)。
清单 14. 针对文件执行重定向
$ crontab <newtab
$ ls > filelist
|
现在使用 zsh,您可以同时重定向到多个输出(请参见清单 15)。
清单 15. 同时重定向到多个输出
zsh$ ls >listone >listtwo
|
您还可以从多个流输入(请参见清单 16)。
清单 16. 从多个流输入
zsh$ sort <listone <listtwo
|
管道是隐式的重定向,因此其工作方式大致相同。例如,在任何 Shell 中,您都可以使用 tee 命令来重定向文件和标准输出(请参见清单 17)。
清单 17. 使用 tee 命令来重定向文件和标准输出
$ ls | tee listone
barney
barney.c
barney.o
betty.c
betty.h
fred
fred.c
fred.h
fred.o
wilma.c
|
在 zsh 中,您可以使用清单 18。
清单 18. 在 zsh 中重定向到文件和标准输出
zsh$ ls >fileone |cat
barney
barney.c
barney.o
betty.c
betty.h
fileone
fred
fred.c
fred.h
fred.o
wilma.c
|
作为进程替换的扩展,通过使用 <(list) 和 >(list) 构造,您还可以针对另一个命令进行重定向(请参见清单 19)。
清单 19. 使用 < 和 > 来针对另一个命令进行重定向
zsh$ sort <(ls) <(ls /usr)
X11R6
barney
barney.c
barney.o
betty.c
betty.h
bin
fileone
fred
fred.c
fred.h
fred.o
include
lib
libexec
local
sbin
share
standalone
wilma.c
|
在清单 19 中,您将两个 ls 命令的输出组合为 sort 命令的标准输入,从而输出两个不同目录中的文件的混合和排序列表。
此功能的一个典型使用场合是使用剪切和粘贴来将一个文件中的字段提取并重新组合到另一个文件中。对于普通的 Shell,您可能会使用许多临时文件(请参见清单 20)。
清单 20. 使用临时文件来将一个文件中的字段提取并重新组合到另一个文件中
$ cut -f1 fileone >t1
$ cut -f5 filetone >t5
$ paste t1 t5
|
在 zsh中,您无需临时文件即可完成此任务,如清单 21 所示。
清单 21. 在 zsh 中剪切和粘贴以提取和重新组合文件中的字段
zsh$ paste -d: <(cut -d: -f1 /etc/passwd) <(cut -d: -f5 /etc/passwd)
|
此外,由于可以容易地嵌套重定向的替换,您可以创建复杂的结构,例如以与源文件不同的顺序组合 passwd 文件中的两个字段、删除注释,然后对它们排序(请参见清单 22)。
清单 22. 在 zsh 中创建复杂结构
zsh$ sort <(egrep -v '^#' <(paste -d: <(cut -d: -f5 /etc/passwd) <(cut -d: -f1
/etc/passwd) ) )
:# mode. At other times this information is handled by one or more of
Amavisd User:amavisd
Apple Events User:eppc
Application Owner:appowner
Application Server:appserver
...
Xgrid Agent:xgridagent
Xgrid Controller:xgridcontroller
sshd Privilege separation:sshd
|
通过按清单 23 所示来查看每个元素,您可以简化清单 22。
清单 23. 简化的过程
- <(cut -d: -f1 /etc/passwd) - Get the first field
- <(cut -d: -f5 /etc/passwd) - Get the fifth field
- <(paste -d: <(f5) <(f1) ) - Recombine them in a different order
- <(egrep -v '^#' <(paste...) - Remove the comments
- sort <(egrep ...) - Sort the standard input
|
文件和命令完成
在某些 Shell (包括 zsh)中,您可以通过按 TAB 键来完成文件或命令。让我们使用当前目录作为示例(请参见清单 24)。
清单 24. 当前目录清单
zsh$ ls
barney betty.c fred fred.o
barney.c betty.h fred.c wilma.c
barney.o fileone fred.h
|
使用完成功能,您可以输入文件名的开头:zsh$ cd bar。然后按 TAB 键并获得完整或部分完成: zsh$ cd barney.
第二次按 TAB,您将看到可用文件列表(请参见清单 25)。
清单 25. 获得可用文件列表
zsh$ cd barney
barney* barney.c barney.o
|
同样的完成过程也适用于目录和路径。zsh 在完成功能方面做了进一步的优化。
自定义完成功能
标准形式的完成功能的局限性在于,它只能在您从命令行上执行输入时完成文件和路径(包括命令)。然而,在其他许多领域,您可能也希望能够完成命令而不必完成所有的输入。例如,源代码控制系统 Subversion 提供了许多二级命令,您在输入其他参数之后另外输入这些命令。例如,若要将更改提交回 Subversion 存储库中,您可以使用 commit 命令:$ svn commit。或者,若要更新,您可以使用 update 命令: $ svn update.
但您必须人工执行这些输入。通过使用 zsh 中的自定义完成控制,您可以作为完成过程的一部分来将这些子命令添加到 svn。完成控制是非常复杂(并且有时非常混淆)的系统,但是其基本原理是相当容易理解的。
完成功能是通过许多命令来控制的,但是最主要的命令为 compctl。此命令为基本的完成功能提供了一个简单接口。可以全局地应用完成功能(换句话说,就如文件和路径),或者可将其应用于特定的命令。
存在一系列可用的选项和格式,但是为了延续 Subversion 思想,您可以使用 -k 选项来提供一组单词,充当 svn 命令的潜在完成目标(请参见清单 26)。
清单 26. 使用 -k 选项
zsh$ compctl -k '(commit checkout update status)' svn
|
现在,当您在命令开头输入 svn 时,您需要按 TAB 来完成该命令(请参见清单 27)。
清单 27. 按 TAB 来完成命令
zsh$ svn com <TAB>
zsh$ svn commit
|
完成系统包括标准的功能和完成特性——例如,系统上的有效用户、主目录、主机、网络等对象的查找。您还可以自己添加和扩展这些功能来产生自定义的完成结果。
例如,您可以创建一个名为 activeusers 的自定义功能,用于返回用户命令产生的输出: zsh$ function activeusers { reply =(`users`) }。
您现在可以使用此功能作为另一个命令的完成目标,例如 chat 命令: zsh$ compctl -K activeusers chat。
现在,当您在命令行上输入 chat 时,可以获得仅返回当前登录用户列表的完成列表。
自定义完成系统的可用选项和可能性实在太多了,无法在此进行一一介绍。有关可用选项的官方文档,请参见参考资料。
总结
zsh 整合了一系列旨在简化用户与 Shell 环境间交互的功能。相关扩展包括替换命令和针对不同进程重定向信息的更好方法。单凭这些选项就可使您能够将其他 Shell 中需要的许多命令转换为 zsh 中的单个命令行条目。
zsh 与其他 Shell(甚至是诸如 bash 等 Shell 中提供的最新改进)之间的真正区别在于,zsh 能够自定义完成系统以处理文件名和路径以外的更多内容。扩展该功能以支持现有命令的附加参数只是其中一个示例,但是该系统是如此灵活,您几乎可以完成任何命令或命令行元素。
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | Martin Brown 成为一名职业作家已经有 8 年多的时间了。他是很多书籍和文章的作者,内容涉及很多主题。他的特长包括很多开发语言和平台 —— Perl、Python、Java、JavaScript、Basic、Pascal、Modula-2、C、C++、Rebol、Gawk、Shellscript、Windows、Solaris、Linux、BeOS、Mac OS/X 等等 —— 还包括 Web 编程、系统管理和集成。他会定期为 ServerWatch.com、LinuxToday.com 和 IBM developerWorks 撰写文章,在 Computerworld、Apple Blog 以及其他站点都会定期更新自己的 blogger,同时还为 Microsoft 撰写一些主题文章。您可以通过 questions@mcslp.com 与他联系。 |
对本文的评价
|