Linux 管道:命令行工具的协同工作方式
通过运用 Linux 管道,您可以将一系列独立的命令行实用程序高效地组织起来,形成一个协同工作的团队。这种方法可以简化复杂的流程,显著提高您的工作效率。本文将向您详细展示如何实现这一目标。
管道的广泛应用
管道是 Linux 和类 Unix 操作系统中最强大的命令行功能之一,其应用范围极其广泛。在各类 Linux 命令行相关的文章中,管道几乎无处不在。即使在其他网站,而非仅仅是我们自己的网站上,您也会发现管道的使用非常普遍。例如,在 How-To Geek 的多篇 Linux 文章中,管道都以各种形式得到了应用。
Linux 管道使得执行在默认情况下 shell 中无法实现的操作成为可能。Linux 的设计理念是让小型实用程序专注于各自 特定的功能,避免冗余功能,强调“做好一件事”的原则。通过管道连接这些命令,一个命令的输出可以作为另一个命令的输入,从而形成一个强大的处理链。每个命令都为团队贡献其独特的才能,最终您将组建一支高效的命令行工作队伍。
简单示例
假设我们有一个目录,其中包含多种类型的文件。我们希望知道该目录下特定类型文件的数量。虽然有其他方法可以实现这一目标,但本文的重点是介绍管道,因此我们将使用管道来完成此操作。
首先,可以使用 ls
命令轻松获取文件列表:
ls
接下来,为了筛选出我们感兴趣的文件类型,我们将使用 grep
命令。假设我们要查找文件名或扩展名中包含“page”一词的文件。
我们将使用 shell 特殊字符“|”将 ls
的输出传递给 grep
:
ls | grep "page"
grep
命令会打印 匹配搜索模式 的行。这样,我们就得到一个只包含 “.page” 文件的列表。
即使是这个简单的示例也展示了管道的功能。 ls
的输出没有直接显示在终端窗口中,而是作为 grep
命令的输入。我们看到的输出是 grep
命令产生的,它是这个命令链中的最后一个命令。
扩展命令链
现在,让我们进一步扩展我们的管道命令链。我们可以通过添加 wc
命令来 计算 “.page” 文件的数量。 我们将在 wc
中使用 -l
(行数)选项。请注意,我们还在 ls
中添加了 -l
(长格式)选项。稍后我们将使用它。
ls -l | grep "page" | wc -l
grep
不再是链中的最后一个命令,因此我们看不到它的输出。 grep
的输出被传递给 wc
命令。我们在终端窗口中看到的输出是来自 wc
命令的。wc
命令报告目录中有 69 个 “.page” 文件。
让我们再次扩展它。我们将从命令行中删除 wc
命令,并用 awk
命令替换它。ls
命令在带有 -l
(长格式)选项的情况下,输出中有九列。我们将使用 awk
命令 打印 第五列、第三列和第九列。这些分别是文件的大小、所有者和名称。
ls -l | grep "page" | awk '{print $5 " " $3 " " $9}'
对于每个匹配的文件,我们都会获得这些列的列表。
现在,我们将把这个输出传递给 sort
命令。我们将使用 -n
(数字)选项,让 sort
命令知道第一列应该被 视为数字。
ls -l | grep "page" | awk '{print $5 " " $3 " " $9}' | sort -n
输出现在按文件大小顺序排序,并且我们自定义选择了三列。
添加另一个命令
我们将通过添加 tail
命令来结束这个管道命令链。 我们将指示它列出 最后五行输出。
ls -l | grep "page" | awk '{print $5 " " $3 " " $9}' | sort -n | tail -5
这相当于命令“显示此目录中最大的五个 “.page” 文件,并按大小排序”。当然,没有一个单一的命令可以做到这一点,但通过使用管道,我们创建了自己的命令。我们可以将此命令(或任何其他长命令)添加为别名或 shell 函数,以避免重复输入。
这是输出结果:
我们可以通过在 sort
命令中添加 -r
(反向)选项来反转大小顺序,并使用 head
而不是 tail
来选择 输出的开头。
这次,五个最大的 “.page” 文件从大到小列出:
近期示例
以下是最近 How-To Geek 文章中的两个有趣的示例。
一些命令,例如 xargs
命令,旨在 接受管道输入。 例如,我们可以让 wc
命令计算多个文件的 单词、字符和行数,通过管道将 ls
的输出传递给 xargs
,然后将文件名列表作为命令行参数传递给 wc
。
ls *.page | xargs wc
单词、字符和行的总数在终端窗口的底部显示。
以下是一种获取当前目录中唯一文件扩展名的排序列表以及每种类型的计数的方法:
ls | rev | cut -d'.' -f1 | rev | sort | uniq -c
这里发生了很多操作:
ls
:列出目录中的文件。rev
:在文件名中 反转文本。cut
:在指定的分隔符“.”的第一次出现处 切割字符串。在此之后的所有文本将被丢弃。rev
:反转剩余的文本,即文件扩展名。sort
:按字母顺序对列表进行排序。uniq
:计算 列表中唯一条目 的数量。
输出显示按字母顺序排序的文件扩展名列表,以及每种类型的计数。
命名管道
我们还可以使用另一种类型的管道,称为命名管道。 前面示例中的管道是由 shell 在处理命令行时动态创建的。管道被创建、使用,然后被丢弃。它们是短暂的,不会留下自己的痕迹。它们只在正在使用它们的命令运行时存在。
命名管道在文件系统中显示为持久对象,因此可以使用 ls
命令来查看它们。它们是持久的,因为它们在计算机重新启动后仍然存在,尽管那时任何未读的数据都将被丢弃。
命名管道通常用于允许不同的进程发送和接收数据,但这种用法现在并不常见。毫无疑问,仍然有人在使用它们,并且取得了很好的效果,但我最近没有遇到过这种情况。为了完整起见,或者仅仅是为了满足您的好奇心,以下是如何使用命名管道的方法。
命名管道是通过 mkfifo
命令创建的。这个命令 将在当前目录中创建一个名为“geek-pipe”的命名管道。
mkfifo geek-pipe
如果我们使用带有 -l
(长格式)选项的 ls
命令,我们可以看到命名管道的详细信息:
ls -l geek-pipe
列表的第一个字符是“p”,表示它是一个管道。如果是“d”,则表示文件系统对象是一个目录,而破折号“-”表示它是一个常规文件。
使用命名管道
让我们使用我们的管道。在前面的示例中,未命名管道将数据从发送命令立即传递到接收命令。通过命名管道发送的数据将保留在管道中,直到被读取。数据实际上保存在内存中,因此无论管道中是否有数据,命名管道的大小都不会在 ls
列表中发生变化。
我们将在此示例中使用两个终端窗口。我将使用标签:
# Terminal-1
在一个终端窗口中,以及
# Terminal-2
在另一个终端窗口中,以便您可以区分它们。井号“#”告诉 shell 后面是注释,请忽略它。
让我们将前面的示例中的所有输出重定向到命名管道。因此,我们在一个命令中同时使用了未命名和命名管道:
ls | rev | cut -d'.' -f1 | rev | sort | uniq -c > geek-pipe
我们将命名管道的内容重定向到 cat
命令,以便 cat
命令在第二个终端窗口中显示该内容。这是输出结果:
您会看到您已返回到第一个终端窗口中的命令提示符。
那么,刚刚发生了什么?
我们将一些输出重定向到命名管道。
第一个终端窗口没有返回到命令提示符。
数据保留在管道中,直到从第二个终端的管道中读取。
我们返回到第一个终端窗口中的命令提示符。
您可能会认为可以通过在命令末尾添加 &
来将第一个终端窗口中的命令作为后台任务运行。您是对的。在这种情况下,我们会立即返回到命令提示符。
不使用后台处理的重点是强调命名管道是一个阻塞进程。将某些东西放入命名管道只会打开管道的一端。在读取程序提取数据之前,不会打开另一端。内核暂停第一个终端窗口中的进程,直到从管道的另一端读取数据。
管道的力量
如今,命名管道只是一种比较少见的使用方式。
另一方面,传统的 Linux 管道是终端工具箱中最有用的工具之一。当您可以编排一组命令来产生一致的结果时,Linux 命令行就会开始发挥其强大功能,这将会让您感受到前所未有的力量。
实用建议:最好一次添加一个命令并使其正常工作,然后再将它传递给下一个命令,从而逐步构建您的管道命令。