过滤器

作者:Paul Dunne

从最基本的层面来说,过滤器是一个程序,它接受输入,转换输入并输出转换后的数据。过滤器的概念与 UNIX 操作系统中的几个概念密切相关:标准输入和输出、输入/输出重定向和管道。

标准输入和输出是指程序将从中获取输入和向其写入输出的默认位置。在命令行交互式运行的程序的标准输入 (STDIN) 是键盘;标准输出 (STDOUT) 是终端屏幕。

通过输入/输出重定向,程序可以使用标准输入或输出以外的位置(例如文件)来获取输入或发送输出。STDIN 的重定向使用 < 符号完成,STDOUT 的重定向使用 > 符号完成。例如,

ls > list

ls 命令的输出(通常会输出到屏幕)重定向到一个名为 list 的文件中。类似地,

cat < list
cat 的输入(在没有文件名的情况下,它应该来自键盘)重定向为来自文件 list——因此我们将该文件的内容输出到屏幕。

管道是通过 I/O 重定向将程序连接在一起的一种方式。管道的符号是 |。例如,

ls | less

是一种常见的舒适查看目录列表输出的方式,当文件数量超过屏幕容纳量时。

通过将 Linux 系统标准提供的简单程序用作其他类似程序的过滤器,可以增强这些程序的功能。我还将展示如何构建您自己的简单程序来满足自定义过滤需求。

本文我没有介绍的一个程序是 Perl。Perl 本身就是一种编程语言,而过滤器是独立于语言的。

grep

程序 grep,“Get Regular Expression and Print”(获取正则表达式并打印),是一个很好的起点。(请参阅 Jan Rooijackers 于 1999 年 3 月发表的“Take Command: grep”。)grep 的原理非常简单:在输入中搜索模式,然后输出模式。例如,

grep 'Linus Torvalds' *

在当前目录中的所有文件中搜索 Linus 的名字。

可以使用各种命令行开关来修改 grep 的行为。例如,如果我们不确定大小写,我们可以写

grep -y 'linus torvalds' *

-y 开关告诉 grep 匹配时忽略大小写。但是,如果您在模式中使用任何大写字母,它们仍然只会匹配大写字母。(这在 GNU grep 中是错误的,GNU grep 在给定 -y 开关时会简单地忽略大小写——那是 -i 开关的作用)。

仅凭关于 grep 的这一点信息,就很容易构建一个实际应用。例如,您可以将姓名和地址详细信息存储在一个文件中,以创建一个可搜索的地址簿。

扩展 Grep

有时,基本的 grep 不够用。例如,假设我们想找到文本字符串的所有出现位置,这些字符串可能指的是 Linus。显然,搜索 'Linus Torvalds' 是不够的——这找不到单独的 Linus 或 Torvalds。我们需要某种方式来说“这个或这个或这个”。这就是 egrep(扩展 grep)的用武之地。这个方便的程序修改了标准 grep,通过使用 | 字符来表示“或”,从而提供了一种这样的条件语法。

egrep 'Linus Torvalds|L\. Torvalds|Mr\. Torvalds' *

现在将找到大多数命名 Linux 发明者的方式。请注意反斜杠以“转义”句点。由于它是正则表达式中的特殊字符,我们必须告诉 egrep 不要将其解释为“魔法”字符。

关于正则表达式的说明

tr

tr 可能是过滤器的缩影。(请参阅 Hans de Vreught 于 1998 年 9 月发表的“Take Command: A Little Devil Called tr”。)tr 是 translate(转换)的缩写,基本上执行其全名所暗示的操作:它将给定的字符或字符集更改为另一个字符或字符集。这是通过将输入字符映射到输出字符来完成的。一个例子将使这一点变得清晰

tr A-Z a-z

将大写字母更改为小写字母。A-Z 是“从 A 到 Z 的所有字母”的简写。

sort

排序是一种非常基本的计算机操作。它通常用于文本,以获得按字母顺序排列的列表或对编号列表进行排序。Linux 有一个强大的过滤器用于排序,逻辑上称为 sort

head 和 tail

这两个非常简单的过滤器具有令人惊讶的多种用途。顾名思义,head 显示文件的头部,而 tail 显示文件的尾部。默认情况下,两者分别显示前十行或后十行,并且 tail 特别有许多其他有用的选项。(请参阅 man 手册。)

可编程过滤器

有时我们需要做一些比上述示例中相对简单的命令行更复杂的事情。为此,我们需要我称之为“可编程过滤器”的东西,即具有脚本语言的过滤器,该语言允许我们指定复杂的操作。

sed

sed,流编辑器,是一种通常用于操作文本行的过滤器,作为使用交互式编辑器的替代方案。(请参阅 Hans de Vreught 于 1999 年 4 月发表的“Take Command: Good Ol' sed”。)有时,启动 vi 或 Emacs 并进行更改(无论是手动还是使用 vi/ex 命令)是不合适的。例如,如果您必须对五十个文件进行相同的更改怎么办?如果您需要更改一个字符串,但不确定它出现在哪些文件中怎么办?

正如在 UNIX 世界中常见的那样,工具经常以不同的方式重复,sed 可以完成 grep 大部分的事情。这是一个 sed 中的简单 grep

sed -n '/Linus Torvalds/p' filename

所有这些操作都是读取标准输入,并且只打印包含字符串“Linus Torvalds”的那些行。

sed 的默认行为是将标准输入传递到标准输出而不进行更改。为了使其执行任何有用的操作,您必须向其提供指令。在我们的第一个示例中,我们通过将字符串括在正斜杠 (//) 中来搜索该字符串,并告诉 sed 使用 p 选项打印任何包含该字符串的行。-n 选项确保不会打印其他行。请记住,默认行为是打印所有内容。

如果 sed 只能做这些,我们最好坚持使用 grep。但是,sed 的优势在于作为流编辑器,根据您提供的规则更改文本文件。让我们看一个简单的例子。

sed 's/Torvuls/Torvalds/g' filename

这使用 sed “替换” (s 选项) 并全局应用它 (g 选项)。它查找每次出现的 “Torvuls” 并将其更改为 “Torvalds”。如果没有末尾的 g 命令,它只会更改每行中第一次出现的 “Torvuls”。

sed '/^From /,/^$/d' filename
这会在标准输入中搜索行首的单词 “From”,后跟一个空格,并删除从包含该模式的行开始到第一个空行(用 ^$ 表示,即行首 (^) 紧跟行尾 ($))为止的所有行。用通俗的英语来说,它从您保存在文件中的 Usenet 帖子中剥离了标头。

双倍行距文本文件只需要一个命令

sed G filename > file.doublespaced

根据我们的手册页,所有这些操作都是“将保留空间的内容附加到当前文本缓冲区”。也就是说,对于每一行,我们输出 sed 用于存储文本的缓冲区的内容。由于我们没有在其中放入任何内容,因此它是空的。但是,在 sed 中,附加此缓冲区会添加新行,而不管缓冲区中是否有任何内容。因此,效果是在每行中添加一个额外的新行,从而使输出双倍行距。

AWK

另一个非常有用的过滤器是 AWK 编程语言。(请参阅 Lou Iacona 于 1999 年 5 月发表的“The AWK Tools”。)尽管名称怪异,但它是一个日常工具。

首先,让我们再次看看另一种执行 grep 的方式:'grep'。快速

awk '/Linus Torvalds/'

与 grep 和 sed 一样,AWK 可以搜索文本模式。与 sed 一样,每个模式都可以与一个操作关联。如果在上面的示例中没有提供操作,则默认操作是打印与模式匹配的每一行。或者,如果没有提供模式,则默认操作是将操作应用于每一行。清单 1 中显示了一个用于使文件中的行居中的 AWK 脚本。

清单 1。

AWK 的优势在于它能够将数据视为表格,即按行和列排列。每个输入行都会自动拆分为字段。默认字段分隔符是“空格”,即空格和制表符,但可以更改为您想要的任何字符。许多 UNIX 实用程序都会生成这种表格输出。在下一节中,我们将看到如何使用我们尚未看到的 shell 构造将这种表格格式作为输入发送到 AWK。

管道:当一个过滤器不够用时

管道 (|) 的基本原理是,它允许我们将一个程序的标准输出与另一个程序的标准输入连接起来。(请参阅 Andy Vaught 于 1997 年 9 月发表的“Introduction to Named Pipes”。)稍加思考就应该清楚地了解,当与过滤器结合使用时,这有多么有用。我们可以通过简单地将过滤器串在一起,在命令行或 shell 脚本中构建复杂的指令“程序”。

过滤器 wc(字数统计)默认将其输出放在四列中。与其指定 -c 开关仅计数字符,不如给出此命令

wc lj.filters | awk ' { print $3 } '

这会获取 wc 的输出

258    1558    8921 lj.filters
并对其进行过滤,以仅将第三列(字符计数)打印到屏幕
8921
如果您想打印整个输入行,请使用 $0 而不是 $3

另一个方便的过滤管道是简单过滤 ls -a 输出的管道,以便仅查看隐藏文件

ls -a| grep ^[.].*

当然,管道极大地提高了可编程过滤器(如 sed 和 awk)的功能。

存储在简单 ASCII 表格中的数据可以通过 AWK 进行操作。作为一个简单的示例,请考虑清单 2 中显示的重量和度量转换器。我们有一个简单的转换文本文件

From    To      Rate---     ---     ----
kg      lb      2.20
lb      kg      0.4536
st      lb      14
lb      st      0.07
kg      st      0.15
st      kg      6.35
in      cm      2.54
cm      in      0.394

要执行脚本,请给出命令

weightconv 100 kg lb
返回的结果是
220
清单 2。
强大的过滤器

“过滤管道”的经典示例来自 The UNIX Programming Environment 这本书

cat $* |tr -sc A-Za-z '\012' |
sort |
uniq -c |
sort -n |
tail

首先,我们使用 cat 将所有输入连接到一个文件中。接下来,我们使用 tr 将每个单词放在单独的行上:-s 压缩,-c 表示使用给定模式的补集,即任何不是 A-Za-z 的字符。它们一起剥离所有不构成单词的字符,并用换行符替换它们;这具有将每个单词放在单独的行上的效果。然后我们将 tr 的输出馈送到 uniq,后者剥离重复项,并使用 -c 参数打印找到重复单词的次数计数。然后我们按数字 (-n) 排序,这为我们提供了一个按频率排序的单词列表。最后,我们只打印输出的最后十行。我们现在有了一个简单的词频计数器。对于任何文本输入,它都会输出十个最常用单词的列表。

结论

过滤器和管道的组合非常强大,因为它允许您分解任务,然后为每个任务选择最佳工具。许多原本必须使用编程语言处理的工作可以通过在 Linux 下在命令行中串联几个简单的过滤器来完成。即使必须使用编程语言来处理特别复杂的过滤器,通过尽可能多地使用现有工具,您仍然可以节省大量的开发工作。

我希望本文能让您对这种力量有所了解。使用过滤器和管道应该使您使用 Linux 工作变得更轻松、更高效。

本文中引用的所有清单都可以通过匿名下载在文件 ftp.linuxjournal.com/pub/lj/listings/issue65/2479.tgz 中获得。

Paul Dunne (paul@dunne.ie.eu.org) 是一位爱尔兰作家和顾问,专门研究 Linux。他唯一一次赶上的截止日期是他第一篇文章的截止日期。他的主页是 http://www.cix.co.uk/~dunnp/

加载 Disqus 评论