如何在 Dash 中模拟流程替换?

如何在 Dash 中模拟流程替换?

在 中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.xzin 代替是"$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

虽然它可能与您的具体问题无关,但值得注意的是:

  1. 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 )对初始执行环境有不同的潜在影响。

  2. 与 中发生的情况相反cmd1 <(cmd2) <(cmd3)cmd3并且cmd1incmd2 | { 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

p1p2临时命名管道,它们可以命名为任何名称。

在 中创建管道会更聪明/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

相关内容