Mar*_*ter 21 dash process-substitution
在 中bash
,我可以使用进程替换并将进程的输出视为保存在磁盘上的文件:
$ echo <(ls)
/dev/fd/63
$ ls -lAhF <(ls)
lr-x------ 1 root root 64 Sep 17 12:55 /dev/fd/63 -> pipe:[1652825]
Run Code Online (Sandbox Code Playgroud)
不幸的是,dash
.
Process Substitution
在破折号中模拟的最佳方式是什么?
我不想将输出保存为某个地方的临时文件 ( /tmp/
) 然后必须删除它。有没有替代方法?
Gil*_*il' 12
您可以通过手动进行管道连接来重现外壳在引擎盖下所做的事情。如果您的系统有条目,您可以使用文件描述符改组:您可以翻译/dev/fd/NNN
main_command <(produce_arg1) <(produce_arg2) >(consume_arg3) >(consume_arg4)
Run Code Online (Sandbox Code Playgroud)
到
{ produce_arg1 |
{ produce_arg2 |
{ main_command /dev/fd5 /dev/fd6 /dev/fd3 /dev/fd4 </dev/fd/8 >/dev/fd/9; } 5<&0 3>&1 |
consume_arg3; } 6<&0 4>&1; |
consume_arg4; } 8<&0 9>&1
Run Code Online (Sandbox Code Playgroud)
我已经展示了一个更复杂的例子来说明多个输入和输出。如果您不需要从标准输入中读取,并且您使用进程替换的唯一原因是该命令需要显式文件名,您可以简单地使用/dev/stdin
:
main_command <(produce_arg1)
produce_arg1 | main_command /dev/stdin
Run Code Online (Sandbox Code Playgroud)
没有,您需要使用命名管道。命名管道是一个目录条目,因此您需要在某处创建一个临时文件,但该文件只是一个名称,它不包含任何数据。/dev/fd/NNN
tmp=$(mktemp -d)
mkfifo "$tmp/f1" "$tmp/f2" "$tmp/f3" "$tmp/f4"
produce_arg1 >"$tmp/f1" &
produce_arg2 >"$tmp/f2" &
consume_arg3 <"$tmp/f3" &
consume_arg4 <"$tmp/f4" &
main_command "$tmp/f1" "$tmp/f2" "$tmp/f3" "$tmp/f4"
rm -r "$tmp"
Run Code Online (Sandbox Code Playgroud)
当前悬赏通知中的问题:
一般的例子太复杂了。有人可以解释如何实现以下示例吗?
diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)
似乎在这里有答案。
如Gilles 的回答所示,总体思路是将“生产者”命令的输出发送到管道不同阶段的新设备文件1,使它们可用于“消费者”命令,这些命令可能将文件名作为参数(假设您的系统允许您访问文件描述符/dev/fd/X
)。
实现您正在寻找的最简单方法可能是:
xz -cd file1.xz | { xz -cd file2.xz | diff /dev/fd/3 -; } 3<&0
Run Code Online (Sandbox Code Playgroud)
(使用file1.xz
代替"$1"
了可读性,xz -cd
而不是cat ... | xz -d
因为一个命令就足够了)。
第一个“生产者”命令的输出通过xz -cd file1.xz
管道传输到复合命令 ( {...}
);但是,它不是立即作为下一个命令的标准输入被使用,而是被复制到文件描述符中3
,从而使复合命令中的所有内容都可以作为/dev/fd/3
. 第二个“生产者”命令的输出,xz -cd file2.xz
既不消耗其标准输入,也不消耗来自文件描述符的任何内容3
,然后通过管道传输到“消费者”命令diff
,该命令从其标准输入和从/dev/fd/3
.
可以添加管道和文件描述符复制,以便根据需要为尽可能多的“生产者”命令提供设备文件,例如:
xz -cd file1.xz | { xz -cd file2.xz | { diff /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0
Run Code Online (Sandbox Code Playgroud)
虽然它可能与您的具体问题无关,但值得注意的是:
cmd1 <(cmd2) <(cmd3)
,cmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0
并对( cmd2 | ( cmd3 | ( cmd1 /dev/fd/3 /dev/fd/4 ) 4<&0 ) 3<&0 )
初始执行环境有不同的潜在影响。
相反,在发生的事情cmd1 <(cmd2) <(cmd3)
,cmd3
并cmd1
在cmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0
将无法读取用户的任何输入。这将需要更多的文件描述符。例如,要匹配
diff <(echo foo) <(read var; echo "$var")
Run Code Online (Sandbox Code Playgroud)
你需要类似的东西
{ echo foo | { read var 0<&9; echo "$var" | diff /dev/fd/3 -; } 3<&0; } 9<&0
Run Code Online (Sandbox Code Playgroud)1 更多关于它们的信息可以在 U&L 上找到,例如在理解 /dev 及其子目录和文件中。
有人可以解释一下如何实现以下示例吗?
diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)
我认为命名管道比重定向更容易理解,所以用最简单的术语来说:
mkfifo p1 p2 # create pipes
cat "$2" | xz -d > p1 & # start the commands in the background
cat "$1" | xz -d > p2 & # with output to the pipes
diff p1 p2 # run 'diff', reading from the pipes
rm p1 p2 # remove them at the end
Run Code Online (Sandbox Code Playgroud)
p1
是p2
临时命名管道,它们可以命名为任何名称。
在 中创建管道会更聪明,例如在 Gilles 的答案/tmp
所示的目录中,并注意您在这里不需要,所以:cat
tmpdir=$(mktemp -d)
mkfifo "$tmpdir/p1" "$tmpdir/p2"
xz -d < "$2" > "$tmpdir/p1" &
xz -d < "$1" > "$tmpdir/p2" &
diff "$tmpdir/p1" "$tmpdir/p2"
rm -r "$tmpdir"
Run Code Online (Sandbox Code Playgroud)
(您也可以不使用引号,因为mktemp
可能会创建“漂亮”的文件名。)
管道解决方案确实有一个警告,如果主命令 ( diff
) 在读取所有输入之前终止,则后台进程可能会挂起。