在 中bash
,我可以使用进程替换并将进程的输出视为保存在磁盘上的文件:
$ echo <(ls)
/dev/fd/63
$ ls -lAhF <(ls)
lr-x------ 1 root root 64 Sep 17 12:55 /dev/fd/63 -> pipe:[1652825]
不幸的是,dash
.
Process Substitution
在破折号中模拟的最佳方法是什么?
我不想将输出保存为临时文件(/tmp/
),然后必须将其删除。还有其他方法吗?
答案1
您可以通过手动执行管道来重现外壳在引擎盖下的功能。如果你的系统有条目,你可以使用文件描述符改组:你可以翻译/dev/fd/NNN
main_command <(produce_arg1) <(produce_arg2) >(consume_arg3) >(consume_arg4)
到
{ 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
我展示了一个更复杂的示例来说明多个输入和输出。如果您不需要从标准输入读取,并且使用进程替换的唯一原因是该命令需要显式文件名,您可以简单地使用/dev/stdin
:
main_command <(produce_arg1)
produce_arg1 | main_command /dev/stdin
如果没有,您需要使用/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"
答案2
当前赏金通知中的问题:
一般的例子太复杂了。有人可以解释一下如何实现以下示例吗?
diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)
似乎有了答案这里。
如图所示吉尔斯的回答,总体思路是将“生产者”命令的输出发送到管道的不同阶段的新设备文件1,使它们可供“消费者”命令使用,这些命令可能会将文件名作为参数(假设您的系统为您提供了访问文件描述符作为/dev/fd/X
)。
实现您想要的最简单的方法可能是:
xz -cd file1.xz | { xz -cd file2.xz | diff /dev/fd/3 -; } 3<&0
(使用file1.xz
in 代替是"$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
虽然它可能与您的具体问题无关,但值得注意的是:
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
incmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0
将无法读取用户的任何输入。这将需要更多的文件描述符。例如,要匹配diff <(echo foo) <(read var; echo "$var")
你会需要类似的东西
{ echo foo | { read var 0<&9; echo "$var" | diff /dev/fd/3 -; } 3<&0; } 9<&0
1 有关它们的更多信息可以在 U&L 上找到,例如了解 /dev 及其子目录和文件。
答案3
有人可以解释一下如何实现以下示例吗?
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
p1
是p2
临时命名管道,它们可以命名为任何名称。
在 中创建管道会更聪明/tmp
,例如在 Gilles 的目录中回答显示,并注意您在这里不需要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"
(您也可以不使用引号,因为mktemp
可能会创建“漂亮”的文件名。)
管道解决方案确实有一个警告,如果主命令 ( diff
) 在读取所有输入之前终止,则后台进程可能会挂起。
答案4
关于什么:
cat "$2" | xz -d | diff /dev/sdtin /dev/stderr 2<<EOT
`cat "$1" | xz -d`
EOT