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

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)

  • `&lt;/dev/fd/8 &gt;/dev/fd/9` 不等同于 Linux 上的 `&lt;&amp;8 &gt;&amp;9`,应该避免使用。 (2认同)

fra*_*san 7

当前悬赏通知中的问题:

一般的例子太复杂了。有人可以解释如何实现以下示例吗?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)

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

  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)cmd3cmd1cmd2 | { 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 及其子目录和文件中


ilk*_*chu 5

有人可以解释一下如何实现以下示例吗?
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)

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

在 中创建管道会更聪明,例如在 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) 在读取所有输入之前终止,则后台进程可能会挂起。