Linux中的匿名和命名管道
1. 概述
在使用 Linux 命令行界面时,通常会将程序的输出重定向为另一个程序的输入。 在本教程中,我们将研究在 Linux 中使用管道和命名管道。
2. 什么是管道?
管道是基于 Unix 的系统中的一种重要机制,它允许我们将数据从一个进程传递到另一个进程,而无需在磁盘上存储任何内容。
在 Linux 中,我们有两种类型的管道:管道(也称为匿名或未命名管道)和 FIFO(也称为命名管道)。
3. 管道
管道通过将命令串在一起使用,由管道字符分隔,’|’。这通常被称为管道,每个 shell 都定义了它的行为。
shell 在后台运行的单独进程中执行每个命令,从最左边的命令开始。
然后,左侧命令的标准输出连接到右侧命令的标准输入。这提供了流的单向性。这种机制一直持续到管道中的所有进程都完成为止。
像 Bash 和 Zsh 这样的 shell 使用标记“ |& ”来指代管道,将左侧命令的标准输出和标准错误与右侧命令的标准输入连接起来。
假设我们想使用netstat 命令查看使用 localhost 正在运行的进程并使用grep 实用程序进行过滤:
$ netstat -tlpn | grep 127.0.0.1
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
在这个示例输出中,我们在netstat中看到了脚本的 stdout 和 stderr,而不管过滤器如何。 现在,让我们将 stderr 合并到 stdout 并将其传递给grep的标准输入:
$ netstat -tlpn |& grep 127.0.0.1
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
我们已经将警告信息隐藏起来了。
3.1. Bash 中的管道
Bash 有一个名为PIPESTATUS的变量,其中包含最近执行的管道中进程的退出状态列表:
$ exit 1 | exit 2 | exit 3 | exit 4 | exit 5
$ echo ${PIPESTATUS[@]}
1 2 3 4 5
整个管道执行的返回状态将取决于pipefail变量的状态。
如果设置了此变量,则管道的返回状态将是最右边的非零状态命令的退出状态,或者如果所有命令成功退出,则将为零:
$ set -o pipefail
$ exit 1 | exit 2 | exit 3| exit 4 | exit 0
$ echo $?
4
禁用pipefail选项后,管道的返回状态将是最后一个命令的退出状态:
$ set +o pipefail
$ exit 1 | exit 2 | exit 3| exit 4 | exit 0
$ echo $?
0
Bash 也有lastpipe选项,它指示 shell 在当前环境的前台执行最后一个命令。
3.2. Zsh 中的管道
Zsh 对管道的控制与 Bash 类似,但有一些区别。例如,Zsh 有 pipestatus命令,它类似于 Bash 中的 PIPESTATUS 变量。 此外,Zsh 在单独的进程中执行每个管道中的命令,除了最后一个命令,它在当前 shell 环境中执行。
4. 命名管道
FIFO,也称为命名管道,是一种类似于管道但在文件系统上具有名称的特殊文件。多个进程可以像任何普通文件一样访问这个特殊文件进行读写。
因此,该名称仅作为需要在文件系统中使用名称的进程的参考点。
FIFO 具有与任何其他文件相同的特性。例如,它具有所有权、权限和元数据。
FIFO 的另一个重要特性是它提供双向通信。
在 Linux 中,我们可以使用命令mknod (使用字母“p”表示 FIFO 类型)和mkfifo 创建一个 FIFO :
$ mkfifo pipe1
$ mknod pipe2 p
$ ls -l
prw-r--r-- 1 cuau cuau 0 Oct 7 21:17 pipe1
prw-r--r-- 1 cuau cuau 0 Oct 7 21:17 pipe2
在这里,我们可以看到我们的 FIFO 的文件类型用字母“p”表示。
这种机制允许我们使用我们的 shell 创建更复杂的应用程序。
命名管道和匿名管道可以一起使用。让我们创建一个结合 FIFO 和管道的反向 shell。
我们将使用nc 实用程序创建一个客户端/服务器应用程序,其中“服务器”端将提供其 shell,“客户端”端将能够访问它。
首先,让我们安装netcat-openbsd包。我们可以使用以下命令 将它安装在任何 Ubuntu/Debian 系统上:
$ sudo apt install netcat-openbsd
接下来,让我们创建一个名为fifo_reverse的 FIFO,输入mkfifo fifo_reverse。
然后,让我们使用两个不同的用户登录,每个用户都充当“客户端”(比如“user1”)和“服务器”(比如“user2”)。让我们在 user2 shell 上运行这个管道:
user2_prompt$ bash -i < fifo_reverse |& nc -l 127.0.0.1 1234 > fifo_reverse
在这个单行程序中,shell 读取我们的 FIFO 的内容并将其传递给交互式 Bash shell。 接下来,交互式 shell 的 stdout 和 stderr 都将传递给nc命令,该命令将在地址 127.0.0.1 的端口 1234 上进行侦听。 最后,当“客户端”成功建立连接时,nc会将接收到的内容写入我们的 FIFO,交互式 shell 将能够执行接收到的内容。 现在,使用 user1 shell,让我们输入:
user1_prompt$ nc 127.0.0.1 1234
user2_prompt$
我们已经获得了 user2 提示,但使用了结合匿名和命名管道的 user1 shell。
5. 临时命名管道
一些 shell 具有称为进程替换的功能,它将命令列表的输入或输出连接到 FIFO。然后这些命令将使用此 FIFO 的名称。
这种机制在 Bash 和 Zsh 中的表示法是*<(command list)将列表的结果传递给实际命令的标准输入,或>(command list)*将实际命令的标准输出传递给标准输入的名单。
让我们使用我们所看到的将多个命令的输出传递给wc命令:
$ wc -l \
<(find / -mindepth 1 -maxdepth 1 -type d) \
<(find /opt -mindepth 1 -maxdepth 1 -type d)
20 /proc/self/fd/11
2 /proc/self/fd/12
22 total
在此示例输出中,我们使用 find命令获取*/和/opt*目录中的目录数。
6. 何时使用命名管道或匿名管道?
使用匿名管道而不是命名管道取决于我们正在寻找的特性。其中一些可以是持久性、双向通信、具有文件名、创建过滤器和限制访问权限等。
例如,如果我们想多次过滤命令的输出,使用匿名管道似乎是最合适的选择。我们还要记住,当我们使用匿名管道时,我们使用的 shell 将发挥核心作用。
另一方面,如果我们需要一个文件名并且我们不想将数据存储在磁盘上,我们正在寻找的是一个 FIFO。如果我们只需要一个名称作为参考,其内容直接来自另一个进程。
另外,让我们考虑一下,虽然匿名管道可能看起来像管道类型的管道,但 FIFO 可以创建更复杂的图表。