对话 UNIX,第 13 部分: 另外十种命令行组合

发现 UNIX 命令行中更多的快捷方法和强大功能

这个月,我们将介绍 UNIX® 命令行向导的另外十种秘诀。

Martin Streicher, 首席技术官, McClatchy Interactive

图片Martin Streicher 是 Linux Magazine 的主编。他从普度大学获得了计算机科学硕士学位,从 1982 年开始用 Pascal、C、Perl、Java 和(最近)Ruby 编程语言编写类 UNIX 的系统。



2007 年 10 月 18 日

本文是“对话 UNIX”系列文章中的第 13 部分:我以前认为 13 是个不吉利的数字,直到我浏览 Internet 搜寻这个数字之所以不吉利的原因。实际上,13 这个数字可以说是喜忧参半(请参见参考资料)。

好的方面:13 是元素铝的原子序数,而铝可用于制作各种祭神仪式的奠酒容器;篮球职业运动员 Wilt Chamberlain 身着 13 号球衣(我们都知道,Wilt 是非常幸运的);按照某种禁忌转换方式,13 是第 7 个质数,而数字 7 象征着幸运。

不好的方面:绞刑架有 13 级台阶;制造混乱的神“洛基”和犹大,都是第 13 个到达的;并且无论您怎么对其划分(除以 2、3、4、或者6),在餐馆中 13 个人都很难坐,这可能正是洛基和犹大被认为是局外人的原因。

陪审团最多不超过 13 个人。所以,除非您在 13 号星期五阅读本文,并且在位于 Mockingbird Lane(这是个历史悠久的地方)1313 号的办公楼的第 13 层,否则都是值得庆幸的。“对话 UNIX”现在是个长满青春痘的青少年了。本文将介绍十种命令行组合和 Shell 诀窍,以庆祝本系列文章进入青春期。恭喜您!

临时设置一个环境变量

环境变量,如 EDITOR 和 TZ,可能影响命令执行的结果。(前者选择进行文本编辑时所启动的程序;而后者可以指定您的时区。)通常,您可以在 Shell 启动文件中设置环境变量,以便对所有的 Shell 会话产生作用,并且您可以在任何时候使用像 export TZ=GMT 这样的命令为一个 Shell 会话更改环境变量的值。

此外,您可以为单个命令临时地修改一个环境变量的值。只需要在启动命令行的时候设置环境变量以及您希望运行的命令即可。例如,要为单个命令更改您的首选编辑器,可以在它的前面加上 EDITOR=editor,如下所示:

$ printenv
...
EDITOR=vi
...
$ EDITOR="pico" less bigfile

这个组合可以使用 lessbigfile 进行分页。如果您在 less 中输入 v 以编辑文件,那么将启动 pico 而不是 vi。下面是另一个实际的使用情况:

$ date
Sun Aug  5 16:14:17 EDT 2007
$ TZ="Japan" date
Mon Aug  6 05:14:06 JST 2007

对 TZ 进行的临时更改将影响 date 的即时实例解释系统当前日期和时间的方式。

查看您实际正在运行的命令

大量的 Shell 特性可以影响到如何解释您所输入的命令名。每种 Shell 都有一个内置命令的分类;PATH 环境变量用于指定搜索的列表和目录;而别名可以作为简写。要运行一个程序可以使用许多方法,如何了解实际执行的是什么命令呢?使用 Shell 内置的 type 命令可以揭示实际的情况。

假设您拥有下面的这些 Shell 设置:

PATH=/bin:/usr/bin:/usr/local/bin
alias vi=pico

您可以在 /usr/bin 和 /usr/local/bin 中找到 Perl 的副本。要查明您正使用的是哪个 Perl,可以输入 type perl

$ perl -v 
This is perl, v5.8.7 built for darwin-2level
$ type perl
perl is /use/local/bin/perl
$ type -a perl
perl is /usr/local/bin/perl
perl is /usr/bin/perl
$ type -a -w perl
perl: command
perl: command

type perl 命令显示了如何在命令行中对 perl 命令进行解释。在这个示例中,/usr/local/bin/perl 是实际的扩展结果。type -a 命令显示了 Shell 所知道的所有 Perl 实例,这在很大程度上依赖于 PATH 变量。

可以针对您常用的其他命令使用 type

$ type -a vi
vi is an alias for pico
vi is /usr/bin/vi
$ type -a cd
cd is a shell builtin
cd is /usr/bin/cd

type 命令显示出,vi 实际上是 pico 的别名。type 命令还显示出,cd 是一个内置的命令,并且与外部命令 /usr/bin/cd 是相同的。

使得 find 命令具有更好的可移植性

去年曾经介绍了许多关于 find 的使用的内容,但是我忽略了其中的一个选项,它使得 find 命令行可以移植到其他操作系统。

通常,UNIX® 系统中很少使用带空格的文件名。然而,在 Mac OS X 和 Microsoft® Windows® 中常常使用更长的、更具描述性的文件名,并且在 UNIX 中它们也变得越来越多,这是因为该操作系统不断地积聚更多的桌面特性。毕竟,将一份报告保存为 2007 Business Plan 明显要比 bizplan07.ooo 好得多。

find 命令使用嵌入的特殊字符列举长文件名,但是,如果您希望将 find 与另一个命令组合使用,那么最安全的方法是,使用 NUL 字符(而不是空格)分隔列表中的每个文件名。让我们来了解其中的差异。

我们假设您拥有三个文件夹,其中一个或者多个目录的名称中包含空格:

$ ls -1
Business Plan 2007
Expense Report
Pictures from Spain

如果您对大量的文件运行 find 命令,并且将结果列表传递给 xargs,那么文件名中的空格将会导致错误:

$ find . -type f -print | xargs ls -1
ls: ./Business: No such file or directory
ls: ./Expense: No such file or directory
ls: ./Pictures: No such file or directory
ls: 2007: No such file or directory
ls: Plan: No such file or directory
ls: Report: No such file or directory
ls: Spain: No such file or directory
ls: from: No such file or directory

传递给 xargs 的结果是单个字符串 . ./Business Plan 2007 ./Expense Report ./Pictures from Spain。在缺省情况下,xargs 将使用空格(或者换行符)对输入字符串进行划分,以便产生可以进行操作的一个文件列表。在这个示例中,因为文件名中包含空格,所以这样做将会产生错误的列表,如前所述。

一种适当的、可移植的技术是使用 find -print0,加上 xargs -0,以便使用 NUL 字符对文件名进行划分。下面是这种推荐的方法:

$ find . -type f -print0 | xargs -0 ls -1
./Business Plan 2007
./Expense Report
./Pictures from Spain

另外,如果您希望预览 xargs 产生的命令,可以添加选项 -p 或者 -t-p 选项显示每个合成的命令,并提示您进行确认。输入大写的或者小写的 y 以便运行命令,输入任何其他的内容可以拒绝该命令。-t 选项可以在执行每个命令之前将命令回显到 stderr

更充分地利用 find 命令

尽管 find 非常有用,但是有两个隐含的设置可能会限制它的结果(并使得您不知所措):-name 匹配是区分大小写的,并且不会根据符号链接对文件系统进行遍历。

因此,一个以 find -name '*plan*' 开头的命令将忽略名称中包含 Plan 字符串的文件,假设您的 home 目录中包含名为 music 的符号链接,而它指向装入到 /media/music 的 TB 级的存储介质,那么这个命令将不会列出您的符号链接 music。

您可以使用 -iname 覆盖区分大小写的匹配,并且您可以使用 -follow 根据符号链接进行遍历。下面是使用了这两种选项的一个示例:

$ alias ls='ls -aF'
$ ls -1 
bin/
lib/
src/
tomb/
tunes@
$ find . -name '*music*' -type f -print 
$ find . -iname '*music*' -type f -print 
$ find . -name '*music*' -type f -follow -print 
$ find . -iname '*music*' -type f -follow -print 
./tunes/Muse/Origin Of Symmetry/04 Hyper Music.m4a
./tunes/Radiohead/OK Computer/04 Exit Music (For A Film).mp3

正如 -F 选项生成的 @ 符号注释所表示的,tunes 是一个符号链接。要查找名称中包含字符串“music”的任何变体的所有歌曲,您必须使用 -iname *music*。要遍历到 tunes 所指向的文件系统层次结构,您必须使用 -follow

为了使得 find 更具可移植性,并且类似于 Spotlight 的搜索特性,那么应该使用 -print0 -follow -iname pattern

收集许多命令的输出的简单方法

通过使用 > output>> output 修饰符,您可以很容易地捕获一个命令行的输出,其中前者用于创建或者覆盖文件 output,而后者则将内容追加到 output。您可以组合使用任何修饰符以生成一系列命令的文本,如果您正尝试对系统状态进行快照,这种方法是非常有价值的,例如:

$ ps > state.`date '+%F'`
$ w >> state.`date '+%F'`

反勾号反引号操作符 (``) 可以对命令进行扩展。在 Shell 对命令行进行解释时,将执行反勾号之间的命令,并在最终的扩展结果中使用该命令的输出。在本示例中,参数周围的单引号用于保持参数不变,从而可以避免 Shell 对 +% 进行解释。

在执行了这两个命令之后,创建了文件 state.YYYY-MM-DD,如 state.2007-08-05,其内容与以下所示类似:

  PID TTY          TIME CMD
 9997 pts/1    00:00:00 zsh
10351 pts/1    00:00:00 ps

 17:56:04 up 21 days,  2:53,  2 users,  load average: 0.89, 0.94, 0.91
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
adamgood pts/0    c-67-169-182-255 Sat17    0.00s  0.37s  0.36s pine
mstreich pts/1    cpe-071-065-224- 17:17    0.00s  0.01s  0.00s w

不过,每次输入反勾号操作是非常麻烦的。您可以使用下面的命令来代替这个序列:

$ file=state.`date '+%F'`
$ ps > $file
$ w >> $file

但是,虽然这样做稍微有效一些,但仍然可能出现错误,因为在第二个或者后续的命令中,很可能使用 > 而不是 >>。要捕获一系列命令的输出,最简单的方法是使用大括号 ({ }) 将命令括起来。

$ { ps; w } > state.`date '+%F'`

ps 命令运行(列出用户当前的进程),然后是 w(它将显示谁正在使用这台计算机),并将收集到的输出保存到一个文件中。

注意: 您还可以在圆括号中嵌入一个命令序列,以得到相同的结果;然而,两者之间有一个重要的区别。在圆括号中的系列命令将在一个子 Shell 中运行,并且不会对当前 Shell 的状态产生影响。

例如,您可能希望运行这个序列:

$ { cd $HOME; ls
      -1}; pwd

它将与下面的命令产生相同的输出:

$ (cd $HOME; ls); pwd

大括号中的命令更改了当前 Shell 的工作目录。后面的这种技术则无能为力。是使用组合还是子 Shell,这取决于您的目的,尽管子 Shell 的功能更强大一些,下面将对其进行描述。

子 Shell 可以为您提供帮助!

尽管通常运行子 Shell 将聚合的输出通过管道传递给单个命令,但您还可以使用子 Shell 对命令进行扩展,就像反勾号那样。然而更有价值的是,子 Shell 可以包含另一个子 Shell,所以还可以进行嵌套扩展。

让我们来看看下面简单的例子。

$ {ps; w} > state.$(date '+%F')

这个命令与 { ps; w } > state.`date '+%F'` 是相同的。$( ) 符号运行圆括号中的命令,然后使用输出来替换自己。换句话说,$() 可以进行扩展,就像反勾号一样。然而,与反勾号不同的是,$( ) 非常复杂,并且甚至可以包括其他 $( ) 扩展。下面提供了一些示例:

$ (cd $(grep strike /etc/passwd | cut -f6 -d':'); ls)

这个命令在密码文件中搜索用户 strike 对应的条目,提取其 home 目录(密码文件中的第 6 个字段,如果您从 0 开始数)字段,更改到这个目录,并列出其中的内容。grep /etc/passwd strike | cut -f6 -d':' 的输出将在执行任何其他操作之前进行扩展。

下面是另一个示例,这次的用户名来自于 whoami 的结果:

(cd $(grep $(whoami) /etc/passwd | cut -f6 -d':'); ls)

因为子 Shell 有许多用途,所以与组合或者反勾号操作符相比,您可能更喜欢使用它。

不再输入长路径名

有些特性,如 PATH 和 MANPATH 环境变量,可以减少输入工作量。这两个变量分别为搜索可执行文件和 man 页面定义了一系列目录。

Shell 支持另一个搜索路径:CDPATH。顾名思义,CDPATH 列出了搜索命名目录的目录列表。让我们看看它是如何工作的。

假设您的 home 目录中有三个目录,它们分别是 tomb、current 和 personal。tomb 目录中包含旧的工作项目;current 目录中包含当前工作的内容;而 personal 目录中包含您所感兴趣的一些文件和内容。执行 ls -R tomb current personal 命令可以得到与下面所示类似的内容:

$ ls -R tomb current personal
current:
./        ../       einstein/ herbie/

personal:
./       ../      fishing/ novel/

tomb:
./       ../      mariner/ marvin/  voyager/

对于这种结构,如果不使用 CDPATH,要更改到任何目录都需要记住文件夹的位置,并输入完全限定的(或者相对的)路径名:

$ cd ~/tomb/mariner
$ cd ~/personal/novel
$ cd ~/current/einstein

为了简化这项任务,可以将 CDPATH 设置为您所需要的搜索命名目录的目录列表:

$ export CDPATH=.:~/:..:../..:

这是 CDPATH 的最小设置。它将按顺序搜索当前目录(.,或者“点”)、您的 home 目录 (~/)、父目录(..,或者“点点”)、父目录的父目录目录 (../..)。最小设置首先搜索本地目录以及附近的一些目录。

在设置了这个 CDPATH 之后,您可以快速地更改到任何顶层目录:

$ pwd
/tmp
$ cd current
/home/strike/current
$ cd personal/fishing
/home/strike/personal/fishing
$ cd novel
/home/strike/personal/novel
$ cd /tmp
$ cd personal/novel
/home/strike/personal/novel
$ cd /tmp
$ cd novel
cd: no such file or directory: novel

除最后一个 cd 命令之外,所有命令的参数都在 CDPATH 中存在匹配的目录。然而,因为 personal 目录不在 CDPATH 中,所以无法找到 novel(如果您位于相对路径之外)。

如果您希望搜索 personal 目录和其他的两个目录,那么可以将它们添加到 CDPATH 的最后一个冒号的后面,或者根据您所需要的搜索顺序进行添加。添加三个目录,假设您的 Shell 启动文件中包含前面的 export 命令:

$ export CDPATH=$CDPATH:~/current:~/tomb:~/personal

现在,您只需要输入希望切换到的目录的名称即可:

$ cd current
/home/strike/current
$ cd /tmp
$ cd einstein
/home/strike/current/einstein
$ cd fishing
/home/strike/personal/fishing
$ cd personal/novel
/home/strike/personal/novel

与 PATH 和 MANPATH 一样,如果 CDPATH 中的多个条目都包含匹配项,那么在找到第一个匹配项后将停止搜索。例如,如果您向 tomb 中添加一个名为 novel 的目录,那么 cd novel 命令将得到 ~/tomb/novel。

$ mkdir ~/tomb/novel
$ cd /tmp
$ cd novel
/home/strike/tomb/novel
$ cd personal/novel
/home/strike/personal/novel

如果其条目中包含唯一的目录名,那么 CDPATH 是最有效的。否则,必须输入足够长的路径以进行区别,比如 personal/novel。

取得事半功倍的效果

您已经看到了许多示例,说明文本文件在 UNIX 系统中有着广泛的用途。大多数系统启动文件都是文本文件,包括 Shell 脚本、配置文件,当然还包括数据文件。除了文本编辑器之外,最有价值的实用工具就是翻页工具 (pager)、或者允许您逐页浏览文本文件的应用程序。

应用程序 less 是最常用的翻页工具之一,并且它提供了大量的选项以调整它的行为。事实上,您可以将 LESS 环境变量设置为相关选项的列表,以便控制 less 的缺省工作方式。下面是一组有用的选项:

export LESS="-Nmsx4"
  • -N 可以显示行号。
  • -m 能够以百分比的形式显示在文件的当前位置。
  • -s 可以将多个空行“压缩”或者减少为单个空行。
  • -x4 可以将制表位设置为四个空格。

请仔细地阅读 less 的 man 页面,以便找到对您最有帮助的选项。

从下到上阅读文件

在 UNIX 系统中,许多文件会不断地增大,直到被截断或者进行存档。例如,最重要的一些系统处理,如电子邮件传输和远程访问、持续日志记录活动,都会在文件的末尾添加新的条目。并且是最感兴趣的日志文件的末尾。如果某个服务崩溃了,那么最后发生的事件将提供最有价值的线索。

有两种方法可以逆序显示文件中的行:tac(将 cat 反过来)和 tail -r 命令。

$ cat smallfile
a
b
c
$ tac smallfile
c
b
a
$ tail -r smallfile
c
b
a

您可能会发现 tac 更加实用一些,因为它将显示整个文件,这与 tail 是不同的,后者将对输出进行截断,只显示若干行的内容。例如,您可以组合使用 tacless 以创建一个别名,用于对文件进行逆序分页:

$ alias rless="LESSOPEN='|tac %s' less"
$ rless smallfile
c
b
a

rless 别名临时地将 LESSOPEN 设置为 |tac %s,这是特定于 less 的一个环境变量。这样可以强制使用 tac 对每个文件(%s 是文件名的占位符)进行预处理(所以使用了管道 |)。

下面提供了这个相同技巧的另一种变体,但是它使用了 perl 而不是 tac,在您的系统中可能无法使用这个命令:

LESSOPEN="|perl -e 'print reverse (<>)' %s" less small

包含 perl 的那行命令表示“将所有的输入行读入一个匿名数组 ((<>)),颠倒元素的顺序,并打印这个新的数组”。

进行新的数学运算

如果您需要计算一个结果,那么并不需要转到一个新的应用程序。您可以继续在命令行中完成这项任务。您可以使用 dc(这是一种逆波兰式计算器),或者 bc(这是一种用于数学运算的完整的脚本编程语言)。或者,如果您需要马上获得答案,那么可以使用命令行和 $(( )) 操作符。

$ echo $(( 100 / 10 ))
10
$ echo $(( 10 ** 2 ))
100

Shell 并没有提供大量的算术操作符,但是已经足以完成大多数编程任务,包括移位、求余和比较。

还有很多的内容需要学习

“对话 UNIX”已经是第 13 部分了,但是仍然有许多内容需要介绍。我们需要学习更多的命令和技巧、研究各种各样的相关概念,当然还包括大量的开放源代码软件,以便提高您的工作效率。

还有一点就是,必须克服各种困难。高年级学生有时会捉弄人,有时的确令人尴尬,但他们相处融洽。也许我看起来像是在倚老卖老了!. . 孩子们相处很融洽,是吧!

感谢您的阅读!我希望您能够喜欢本专栏。

参考资料

学习

获得产品和技术

  • IBM 试用软件:从 developerWorks 可直接下载这些试用软件,您可以利用它们开发您的下一个项目。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=263070
ArticleTitle=对话 UNIX,第 13 部分: 另外十种命令行组合
publish-date=10182007