1. 文件操作和过滤

绝大多数命令行工作是针对文件的。我们会在本节中讨论如何观察及过滤文件内容,如何仅用一条命令就能从文件中提取所需信息,以及如何对文件的内容排序。

1.1. cat、tail、head、tee:文件打印命令

这些命令的语法基本上相同的:命令名 [选项] [文件],而且您可以在管道中使用这些命令。这些命令的功能都是根据特定的条件选择文件内容进行打印。

cat 工具会将指定的所有文件的全部内容依次打印到标准输出(一般是计算机的显示屏)。这是最常用的命令之一。例如,您可以使用:

# cat /var/log/mail/info

将邮件守护程序的日志文件打印到标准输出[14]cat 命令的 -n 选项很有用,它能够同时打印行号。

某些文件,如守护程序日志文件(如果相应的守护程序运行了的话)可能非常大[15],在屏幕上打印全部内容可能没什么必要。您一般只是需要看看一个文件的某几行。您可以使用 tail 命令完成这一功能。默认情况下,下面的命令将会打印 /var/log/mail/info 文件的最后十行:

# tail /var/log/mail/info

像日志这样的文件应该是在不断变化的,因为与其相关的守护程序每时每刻都在记录着它所执行的动作和发生的事件。所以,如果您想要交互地观看日志文件的最新更动,请使用 -f 选项:

# tail -f /var/log/mail/info

在本例中,/var/log/mail/info 文件的所有更改都会立即打印到屏幕上。当您想要知道您系统的工作原理时,使用带 -f 选项的 tail 命令将非常有用。例如,通过查看 /var/log/messages 日志文件,您可以时刻跟踪系统信息和各种守护程序。

如果您指定 tail 同时处理多个文件,则它会先打印出一行文件名然后才是其内容。这一特性同样适用于 -f 选项,可以使您了解到系统各部分之间是如何协调运作的。

您可以使用 -n 选项显示文件的最后 n 行。例如,要显示最后 2 行,您应该执行:

# tail -n2 /var/log/mail/info

与其他命令一样,您可以同时使用多个选项。例如,同时使用 -n2-f 将让您从该文件最后两行起不断看到写入该日至文件的新的文本行。

head 命令与 tail 十分相似,只是打印文件的头几行。默认情况下,下面的命令将会打印 /var/log/mail/info 文件的头十行:

# head /var/log/mail/info

tail 一样,您也可以使用 -n 选项指定要打印的行数。例如,要打印前两行,您应该执行:

# head -n2 /var/log/mail/info

您还可以组合使用这几条命令。例如,如果您想要只显示第九行和第十行,您可以先使用 head 命令选择文件的前十行,然后再将结果通过管道送到 tail 命令。

# head /var/log/mail/info | tail -n2

竖线后面的部分将选择最后两行,然后将其打印到屏幕。同样地,您也可以选择只打印文件的倒数第 20 行:

# tail -n20 /var/log/mail/info |head -n1

在本例中,我们让 tail 选择了文件的最后 20 行,然后将结果通过管道传送给 head。然后 head 命令将会从得到的数据中取出第一行打印到屏幕上。

假定您想要将上例中的结果在屏幕上显示的同时还保存到文件 results.txt 中。tee 工具可以帮到我们。其语法是:

tee [选项] [文件]

现在,我们可以将上一命令做如下更改:

# tail -n20 /var/log/mail/info |head -n1|tee results.txt

我们再来举一个例子。我们想要选择最后 20 行,将其保存到 results.txt,但是只在屏幕上显示这 20 行中的第一行。那么,我们应该输入:

# tail -n20 /var/log/mail/info |tee results.txt |head -n1

tee 命令有一个非常有用的选项(-a),它允许您将数据追加到已有文件。

在下一节中,我们将会看到如何使用 grep 作为过滤器,以便从来自其它服务的信息中拆出 Postfix 信息。

1.2. grep: 定位文件中的字符串

不管是命令的名字还是缩写(“General Regular Expression Parser (普通正则表达式解析器)”)都显得非常古怪,但该命令的作用和用法却很简单:grep 将在一个或多个文件中查找给定的模式。其语法为:

grep [选项] <模式>
       [一个或多个文件]

。如果列出了多个文件,则会在结果的每一行开头附加相应的文件名。使用 -h 选项可以不显示这些文件名;使用 -l 选项可以只列出文件名。模式是一个正则表达式,尽管在大多数情况下只是一个简单的单词。下面列出了最常用的几个选项:

  • -i:进行不区分大小写的搜索;

  • -v:反转搜索。显示与模式匹配的行;

  • -n:显示找到行的行号;

  • -w:让 grep 在进行模式匹配时匹配整个单词。

让我们回到对邮件守护程序日志文件的分析中。我们想要在 /var/log/mail/info 中找到包含 postfix 模式的所有行。我们就需要输入这个命令:

# grep postfix /var/log/mail/info

grep 命令可用于管道。这样,下面的命令与上面的例子所得到的结果相同:

# cat /var/log/mail/info | grep postfix

不过,请注意,此处 cat 并非必要。而另一方面,组合使用 grep 以及 tail 却能得到系统运行中的有用信息。

如果我们想要找到不与 postfix 模式匹配的所有行,我们就应该使用 -v 选项:

# grep -v postfix /var/log/mail/info

现在,假定我们想要查找关于成功发出邮件的全部信息。这样,我们需要找到邮件守护程序(包含 postfix 模式)在日志文件中添加的行,而且这些行还必须包含成功发送的信息(status=sent)[16]

# grep postfix /var/log/mail/info |grep status=sent

我们在本例中使用了两次 grep。这种方法虽然可以达到我们的目的,但显得有点繁琐。我们可以使用 fgrep 工具达到相同的效果。实际上,fgrep 只是一个调用 grep -F 的快捷方式。首先,我们需要创建一个包含需要匹配的模式的文件(每行一个)。该文件可以这样创建(我们用 patterns.txt 作为该文件的文件名):

# echo -e 'status=sent\npostfix' >./patterns.txt

可以用 cat 命令查看结果。\n 是一个特殊的模式,它表示“换行”。

然后,我们将会用 patterns.txt 文件中的模式列表作为参数调用 fgrep 工具,而不是“两次调用grep

# fgrep -f ./patterns.txt /var/log/mail/info

文件 ./patterns.txt 可以包含任意多个模式。例如,要选择已经成功发送给 peter@mandrakesoft.com 的邮件的有关信息,只需将此电子邮件地址添加到 ./patterns.txt 文件,试试这条命令:

# echo 'peter@mandrakesoft.com' >>./patterns.txt

显然,您可以将 greptail 或者 head 组合起来使用。如果我们想要查找发送给 peter@mandrakesoft.com 的最后第二封邮件,只需输入:

# fgrep -f ./patterns.txt /var/log/mail/info | tail -n2 | head -n1

在这里,我们应用了上面讲述的过滤器,并将其放入 tailhead 命令的管道。这将会从数据中取出倒数第二行。

1.3. 正则表达式和过滤:egrep

使用 grep 只能查找固定模式的数据。那么怎样才能找出发送给 “ABC 公司” 每个员工的电子邮件呢?列出他们所有的电子邮件可不是一项简单的任务,可能最终会漏掉某人,或是需要手工查询。

fgrep 相类似,grep 有一个调用 grep -E 命令的快捷方式:egrep。它使用正则表达式来替代模式去匹配,这样就为我们提供了一个更为强大的方式来搜索文本。

除了我们在第 3 节 “Shell 通配符”中提到的统配模式之外,以下介绍一些额外的正则表达式:

  • [:alnum:][:alpha:][:digit:] 是预定义的字符集和,它们分别表示字母数字集和、字母集和以及数字集和(都包括大写和小写)。使用它们的额外好处是:它们包含国际化字符并且会考虑到本地化的系统。

  • [:print:] 代表所有屏幕可打印字符。

  • [:lower:][:upper:] 分别代表所有小写和大写字母。

可供选择的字符集和还有很多,请参看 egrep(1)。上述只是其中最为常用的。

一个正则表达式后面可以跟上一个或者多个重复符:

?

前面的项目是可选的,并最多出现一次。

*

前面的项目可以出现多次或不出现。

+

前面的项目至少出现一次。

{n}

其面的项目恰好出现了 n 次。

{n,}

前面的项目至少出现 n 次。

{n,m}

前面的项目至少出现 n 次,并且最多出现 m 次。

把某个正则表达式放在圆括号中就可以在后面引用它。比如,表达式 [:alpha:]+ 代表某个单词。如果您想要查找出现了两次的单词,您可以把它用圆括号括起来,并且在后面用 \1 来引用它(如果它是第一个待引用组的话)。最多您可以“存储” 9 个这样的组。

$ echo -e "abc def\nabc abc def\nabc1 abc1\nabcdef\nabcdabcd\nabcdef abcef" > testfile
$ egrep "([[:alpha:]]+) \1" testfile
abc abc def
$
[注意]注意

[] 是字符集和名的一部分,因此在使用字符集和的时候必须包括它们。第一个 [ 以及其对应的 ] 表示将要引用某个字符集和,第二个 [ 以及其对应的 ] 是字符集和名的一部分。

返回的那一行仅同由空格分隔的两组相同字符相匹配。其他的同该正则表达式不匹配。

您也可以使用 | 运算符连接两个正则表达式,其结果或是同 | 左边的正则表达式匹配,或是同其右边的正则表达式匹配。对上述 testfile 文件,您可以试着构造一个正则表达式与其中的两次重复出现的单词或是两次重复出现的字母数字组合匹配。

$ egrep "([[:alpha:]]+) \1|([[:alpha:][:digit:]]+) \2" testfile
abc abc def
abc1 abc1
$

请注意,对于第二组圆括号括起的待引用组,必须使用 \2 来引用,否则将不会得到期望的结果。对于上述要求,更有效的表达式是:

$ egrep "([[:alnum:]]+) \1" testfile
abc abc def
abc1 abc1
$

最后,要匹配某些特殊字符必须“逃逸”它们:在它们前面添加反斜杠。这些字符有:?+{|()。它们分别与下列相匹配:\?\+\{\|\(\)

这个简单的技巧能够避免您在文本中“重复重复”单词。

所有工具中的正则表达式均遵从上述规则,或是非常类似。花点时间来理解它们将会有助于您使用其他工具(比如 sed)。sed 可以操纵文本,使用正则表达式来改变它们等等。

1.4. wc:统计文件中的元素

wc 命令(Word Count (单词计数))用于统计文件中的行数、字符串和单词的数量。它还可用于统计字节数、字符数以及最长行的长度。其语法为:

wc [选项] [文件]

下面的选项比较有用:

  • -l:打印行数;

  • -w:打印单词数;

  • -m:打印总计字符数;

  • -c:打印字节数;

  • -L:打印所文本中最长行的长度。

wc 命令默认情况下会打印行数、单词数和字符数。下面是一些例子:

如果我们想要查找系统中的用户数,我们就可以输入:

$wc -l /etc/passwd

如果我们想要知道系统中的 CPU 数,我们可以输入:

$grep "model name" /proc/cpuinfo |wc -l

在上一节中,我们得到了成功发送到列于 ./patterns.txt 文件中的电子邮件地址的邮件列表。如果我们想要知道一共有多少封邮件,那么可以将过滤结果通过管道重定向到 wc 命令:

# fgrep -f ./patterns.txt /var/log/mail/info | wc -l

1.5. sort:排序文件内容

下面列出了这一强大工具的语法[17]

sort [选项] [文件]

现在我们来考虑一下对 /etc/passwd 文件进行排序。正如您看到的,这个文件并未经过排序:

$ cat /etc/passwd

如果我们想要按照 login 域进行排序,则应输入:

$ sort /etc/passwd

默认情况下,sort 命令会按照第一个域(本例中就是 login)对数据进行升序排序。如果我们想要以降序方式排序,可以使用选项 -r

$ sort -r /etc/passwd

每个用户在 /etc/passwd 文件中都有他自己的 UID。下述命令将按照 UID 域进行升序排序:

$ sort /etc/passwd -t":" -k3 -n

我们在这里用到了下列 sort 选项:

  • -t":":通知 sort 域之间通过 ":" 符号分隔;

  • -k3:意味着要根据第三列进行排序;

  • -n:表明排序是按照数字顺序,而非字母顺序。

如果要想按照降序排序,就是:

$ sort /etc/passwd -t":" -k3 -n -r

请注意,sort 还有两个比较重要的选项:

  • -u:执行严格定序:重复的排序关键字将被丢弃;

  • -f:忽略大小写。

最后,要想查找拥有最大 UID 的用户,可以键入下述命令:

$ sort /etc/passwd -t":" -k3 -n |tail -n1

我们先是对 /etc/passwd 文件按 UID 进行了升序排序,然后将结果通过管道传送给 tail 命令,它会输出排序列表的最后一行。



[14] 本节中有一些例子是基于实际的服务器日志文件(服务、守护程序等)。请确定 syslogd (对守护程序记录日志的程序)以及相应的守护程序(本例中为 Postfix)已经运行,而您已经是 root 了。当然,您总是可以将我们的例子应用到其他文件。

[15] 例如,/var/log/mail/info 文件包含了所有发出邮件的信息,用户通过 POP 协议获取邮件的信息,等等。

[16] 虽然只用通过过滤状态信息就能得到结果,但是还是请您按照我们的例子实习一下,这样您可以掌握新命令。

[17] 我们只在这里简单讨论 sort,因为光这一个命令的用法就可以写一本书。