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


18

在中bash,我可以使用Process Substitution并将进程的输出视为已保存在磁盘上的文件:

$ echo <(ls)
/dev/fd/63

$ ls -lAhF <(ls)
lr-x------ 1 root root 64 Sep 17 12:55 /dev/fd/63 -> pipe:[1652825]

不幸的是,中不支持Process Substitution dash

Process Substitution用破折号模拟的最佳方法是什么?

我不想将输出另存为临时文件(/tmp/),然后将其删除。还有其他方法吗?


根据您要执行的操作,您可能可以使用管道。
埃里克·雷诺夫

老实说,如果不是您主要关心的是可移植性,那么将其安装bash在您的设备上会不会更容易?
Arkadiusz Drabczyk

1
您在悬赏通知中提供的示例恰好是此链接的答案的主题。如此处所示,可以使用Gilles答案的简化版本(假设可以/dev/fd使用和xz -cd <file>代替cat <file> | xz -dxz -cd "$1" | { xz -cd "$2" | { diff /dev/fd/3 /dev/fd/4; } 3<&0; } 4<&0
fra

1
@ fra-san-实际上是我需要的。如果需要,可以将其作为答案。谢谢。
Martin Vegter

Answers:


4

当前悬赏通知中的问题:

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

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

虽然这可能与您的特定问题无关,但值得注意的是:

  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")
    

    您将需要类似的东西

    { echo foo | { read var 0<&9; echo "$var" | diff /dev/fd/3 -; } 3<&0; } 9<&0
    

1 关于它们的更多信息可以在U&L上找到,例如,在了解/ dev及其子目录和文件中


12

您可以通过手动执行管道操作来重现外壳在引擎盖下的功能。如果您的系统有条目,则可以使用文件描述符改组:您可以翻译/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
</dev/fd/8 >/dev/fd/9<&8 >&9在Linux 上并不等效,因此应避免在Linux上使用。
斯特凡Chazelas

@Gilles-我对复杂的示例感到困惑。您能否显示如何模仿: diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)
Martin Vegter

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)在读取所有输入之前就死了,则可以保留后台进程。


0

关于什么:

cat "$2" | xz -d | diff /dev/sdtin /dev/stderr 2<<EOT
`cat "$1" | xz -d`
EOT
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.