Bash匿名管道

11

在设计一系列命令以执行某个任务时,我遇到了匿名管道行为不如预期的问题。由于我运行的原始命令太复杂,无法在此处解释,因此我创建了一个示例来展示问题(我知道这些命令基本上没有做任何事情)。此外,我使用 pv 来显示数据是否实际从输入复制到输出。

cat /dev/zero | pv > /dev/null

这个操作按预期工作。(将数据从/dev/zero复制到/dev/null)

cat /dev/zero | tee /dev/null | pv > /dev/null

这也像预期的那样运行(复制数据并将两个副本都发送到 /dev/null)

cat /dev/zero | tee >(pv -c > /dev/null) | pv -c > /dev/null

这个命令只能部分运行。虽然从STDIN到STDOUT的复制仍然有效(pv会短暂地显示进度),但整个命令会被匿名管道阻塞,匿名管道收不到任何数据,因此tee也会被阻塞,无法写入其中一个输出(我通过让其写入文件而不是/dev/null来检查了这一点)。

如果有人知道为什么在bash中(如预期的那样)不能正常工作,我会很感激帮助。

PS:如果我使用zsh而不是bash,则命令将按预期运行。不幸的是,需要运行该命令的系统没有zsh,我无法在该系统上部署zsh。


直接通过 bash -c 运行或在子shell中运行(即:(cat /dev/zero | tee >(pv -c > /dev/null) | pv -c > /dev/null))似乎是有效的... 在交互式 shell 中运行它会像你所说的那样挂起,我不知道为什么。 - FatalError
1
这只在 pv 上发生吗?因为当我使用 cat 替换第一个 pv 时,它对我有效。也许 pv 不支持进程替换魔法? - Vasily G
据我所知,cat不能从标准输入读取。通过将第一个pv替换为cat,来自匿名管道的读取有效地忽略了匿名重定向。我认为这不是期望的效果。然而,将pv替换为其他内容(如awk '{print $0}'或perl -ne 'print $_')具有相同的效果(两个命令都有效地将标准输入复制到标准输出)。 - Fabraxias
1
@Fabraxias:cat 命令可以从标准输入读取。尝试使用 echo "abc" | cat,如果你使用的是旧版 Unix 系统,则可能需要使用 echo "abc" | cat -- 表示从标准输入读取)。祝大家好运。 - shellter
@shellter - 对你的评论进行一点补充 -- 即使在较新的Unix系统中,使用 - 作为标准输入也非常方便。考虑以下例子: yadda | cat header.txt - footer.txt - ghoti
1个回答

2
当您使用<( ... )进行进程替换时,其中运行的进程没有控制终端。 但是pv始终将其结果显示到终端上; 如果没有,则会停止。

如果您执行代码并且在其运行时执行ps axf,则会看到类似以下内容:

23412 pts/16   S      0:00  \_ bash
24255 pts/16   S+     0:00      \_ cat /dev/zero
24256 pts/16   S+     0:00      \_ tee /dev/fd/63
24258 pts/16   S      0:00      |   \_ bash
24259 pts/16   T      0:00      |       \_ pv -c
24257 pts/16   S+     0:00      \_ pv -c

这告诉你,进程替代中执行的pv -c处于T状态,即已停止。 它正在等待控制终端才能运行。 它没有任何控制终端,因此它将永远停止,并且bash最终停止向该管道发送数据。


嗯,我知道对于<()使用匿名管道将子shell的STDIN重定向到外壳。因此,执行“cat <(gunzip -c some.gz)”实际上可以工作(是的,这是另一个无用的命令和非常迂回的zcat方式)。但是,据我所了解,>()正好相反-通过匿名管道将外壳的输出重定向到内壳。换句话说,/dev/fd/63不应该是停止的pv的自动输入吗? - Fabraxias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接