Bash 进程替换:如何可移植地引用标准输入?

Bash 进程替换:如何可移植地引用标准输入?

这里的问题是大胆的下面的句子。假设我正在尝试这样做:

diff -u <(some command) {mystery-syntax}

我正在比较标准输入的输出some command和一些数据,例如终端中的复制粘贴。像这样:

$ diff -u <(echo foo) {mystery-syntax}
bar
[Ctrl-D]
--- /dev/fd/63  2021-11-04 11:28:19.360366909 -0700
+++ /dev/fd/62  2021-11-04 11:28:19.360366909 -0700
@@ -1 +1 @@
-foo
+bar

{mystery-syntax}元令牌涵盖什么实际语法?

diff实用程序不使用-指定标准输入的约定,因此我们无法使用它。

一个答案是:/dev/fd/0

$ diff -u <(echo foo) /dev/fd/0
bar
[Ctrl-D]
--- /dev/fd/63  2021-11-04 11:31:02.837040697 -0700
+++ /dev/fd/0   2021-11-04 11:30:56.807964644 -0700
@@ -1 +1 @@
-foo
+bar

好的,所以我们让它以一种方式工作,在具有 Linux 风格的系统上/dev/fd但是有没有一种方法可以不引用/dev文件系统(这是特定于系统的实现细节)?我相信具有除/dev/fd.该手册指出:

支持命名管道 (FIFO) 或 /dev/fd 命名打开文件方法的系统支持进程替换。

例如,显而易见的:

diff <(echo foo) <(cat)

不起作用;无论出于何种原因,即使<(cat)进程替换只需要 的输出端catcat仍然以没有标准输入的方式调用:

$ diff -u <(echo foo) <(cat)
cat: -: Input/output error
--- /dev/fd/63  2021-11-04 11:33:59.303347814 -0700
+++ /dev/fd/62  2021-11-04 11:33:59.303347814 -0700  
@@ -1 +0,0 @@
-foo

这里也有一些“聪明”的行为。如果我们显式重定向 /dev/fd/0cat的输入,那仍然会不知何故感到无聊:

$ diff -u <(echo foo) <(cat < /dev/fd/0)
cat: -: Input/output error

但!如果我们给cat它一个here-document作为它的标准输入,就不会产生干扰:

$ diff -u <(echo foo) <(cat <<<"bar")
--- /dev/fd/63    2021-11-04 11:40:03.612616179 -0700
+++ /dev/fd/62    2021-11-04 11:40:03.612616179 -0700
@@ -1 +1 @@
-foo
+bar

答案1

diff如果命令行上给出的文件名之一是-(破折号),则实用程序将从标准输入中读取其位置。

$ echo hello | diff -u <( echo ok ) -
--- /dev/fd/12  Fri Nov 12 21:25:18 2021
+++ -   Fri Nov 12 21:25:18 2021
@@ -1 +1 @@
-ok
+hello

来自 GNUdiff手册:

如果 aFILE-,则读取标准输入。

来自 OpenBSD 手册:

如果 或file1file2,则-使用标准输入代替它。

来自 POSIX 标准:

file1,file2
要比较的文件的路径名。如果file1file2操作数是-,则应使用标准输入代替它。

答案2

diff -u <(echo foo) <(cat)在交互中不起作用,bash因为进程替换中的命令不会放在前台,因此无法从终端设备读取。与在后台运行的cat &wherecat一样(不在终端设备的前台进程组中),一旦尝试从其控制终端读取数据,运行的进程cat就会被一个信号挂起。SIGTTIN

echo hello | diff -u <(echo ok) <(cat)之所以有效,是因为由两个子壳组成的整个管道位于前台,并且无论如何都是cat从管道而不是终端读取。进程替换最终位于前台,因为它们是由前台的子 shell(该管道的第二个组件)启动的。

bash-5.0$ diff -u <(echo ok) <(ps -o pid,ppid,pgid,args)
--- /dev/fd/63  2021-11-12 22:39:41.985207894 +0000
+++ /dev/fd/62  2021-11-12 22:39:41.985207894 +0000
@@ -1 +1,6 @@
-ok
+    PID    PPID    PGID COMMAND
+   9779    9772    9779 /bin/zsh
+1957388    9779 1957388 bash --norc
+1958861 1957388 1957388 bash --norc
+1958862 1957388 1958862 diff -u /dev/fd/63 /dev/fd/62
+1958863 1958861 1957388 ps -o pid,ppid,pgid,args
--- /dev/fd/63  2021-11-12 22:39:01.017237420 +0000
+++ /dev/fd/62  2021-11-12 22:39:01.017237420 +0000
@@ -1 +1,7 @@
-ok
+    PID    PPID    PGID COMMAND
+   9779    9772    9779 /bin/zsh
+1957388    9779 1957388 bash --norc
+1958325 1957388 1958324 diff -u /dev/fd/63 /dev/fd/62
+1958326 1958325 1958324 [bash] <defunct>
+1958327 1958325 1958324 bash --norc
+1958328 1958327 1958324 ps -o pid,ppid,pgid,args

查看ps上面第一种情况下如何在不同的进程组中运行,以及第二种情况下如何在与其余进程相同的进程组中运行。

如果你运行:

bash-5.0$ (diff -u <(echo ok) <(ps -o pid,ppid,pgid,args))
--- /dev/fd/63  2021-11-12 22:42:04.761112701 +0000
+++ /dev/fd/62  2021-11-12 22:42:04.761112701 +0000
@@ -1 +1,6 @@
-ok
+    PID    PPID    PGID COMMAND
+1960605    9772 1960605 /bin/zsh
+1960612 1960605 1960612 bash --norc
+1960757 1960612 1960757 diff -u /dev/fd/63 /dev/fd/62
+1960759 1960757 1960757 bash --norc
+1960760 1960759 1960757 ps -o pid,ppid,pgid,args

当我们在前台启动一个完整的子 shell 时,一切又恢复正常了。

因此,这里最简单的解决方法是在子 shell 中运行管道。

无论如何,这仅适用于交互式 shell(正在执行终端作业控制)以及 stdin 是会话的控制终端时。

在脚本中(您通常只关心脚本中的可移植性),即使 stdin 是终端也没有问题,diff -u <(echo ok) <(cat)因为 shell 是非交互式的并且不进行作业控制。

然而,这cat没有任何用处,它与以下中的 UUOC 相同:

cat | diff -u <(echo ok) -

或者

cat | diff -u <(echo ok) /dev/stdin

在不支持/dev/fd/x//dev/stdin的系统上,对于与 不同的命令diff,不支持-指定 stdin,<(cat)在 bash 或 zsh 中(但不是在该功能来自的 ksh93u 之前的 ksh 版本中)使用临时命名管道,因此diff -u <(echo ok) <(cat)仍将在这些系统上工作。

由于我们提到了可移植性,请注意,在 中zsh,您需要:

echo hello | { diff <(echo ok) <(cat); }

对于 to 的标准输入cat也是来自 的管道echo

另请注意:

echo hello | cmd /dev/stdin # or /dev/fd/0, /proc/self/fd/0²

不适用于 Linux 上的 ksh93,因为ksh93's|使用套接字对而不是管道³,并且在 Linux(和 Cygwin)上,与其他系统相反,打开/dev/stdin不像dup(0),它打开与在标准输入上打开相同的文件,并且您可以不是open()插座。

更多阅读相关问答:


bash¹ 如今已经很少了,而且没有的系统可能比没有的系统多得多/dev/fd/x

² 除了那些命令,例如gawk那些命令本身视为或可以视为/dev/stdin含义标准输入,而无需实际打开文件,与-.

³ 因为 Linux 的管道实现不允许您查看 ksh93 需要执行某些(可疑)优化的内容。

相关内容