dash:通过管道将 STDIN 传输到多个命令,并将它们的输出按定义的顺序传输到 STDOUT

dash:通过管道将 STDIN 传输到多个命令,并将它们的输出按定义的顺序传输到 STDOUT

起初我以为这个答案是解决方案,但现在我想我需要一个临时文件作为缓冲区。

这工作不可靠:

#!/bin/sh
echo 'OK' |
{
    {
        tee /dev/fd/3 | head --bytes=1 >&4
    } 3>&1 | tail --bytes=+2 >&4
} 4>&1

当我在终端中运行它时,有时我会得到:

好的

有时我得到:


看起来完全是随机的。因此,作为解决方法,我将输出写入文件并在管道完成后将tail其读回。stdout

#!/bin/sh
echo 'OK' |
{
    {
        tee /dev/fd/3 | head --bytes=1 >&4
    } 3>&1 | tail --bytes=+2 >file
} 4>&1
cat file

dash这可以在没有临时文件的情况下完成吗? Shell 变量作为缓冲区也不是一个选项,因为输出可能包含 NUL 字节。

答案1

如果您想并行运行消费者和生产者,但序列化消费者的输出,则需要延迟第二个消费者的输出。为此,您需要以某种方式存储其输出,最好的方法是使用临时文件。

zsh

{cat =(producer > >(consumer1 >&3) | consumer2)} 3>&1

bash有一个问题,因为它不等待进程替换命令,所以你必须在那里使用令人讨厌的解决方法

在这里,我们使用=(...)进程替换的形式将输出存储comsumer2在临时文件中,cat然后再存储。我们无法为超过 2 个消费者执行此操作。为此,我们需要手动创建临时文件。

不使用时=(...),我们必须手动清理临时文件。我们可以通过预先创建和删除它们来处理这个问题,这样就不必担心脚本被杀死的情况。仍然有zsh

tmp1=$(mktemp) && tmp2=$(mktemp) || exit
{
  rm -f -- $tmp1 $tmp2
  producer > >(consumer1) > >(consumer2 >&3) > >(consumer3 >&5)
  cat <&4 <&6
} 3> $tmp1 4< $tmp1 5> $tmp2 6< $tmp2

dash编辑(我最初错过了需要解决方案的事实)

对于dash(或任何不在 2 以上的 fd 上设置 close-on-exec 标志并使用管道而不是套接字对的 POSIX shell |),以及在/dev/fd/x支持的系统上:

tmp1=$(mktemp) && tmp2=$(mktemp) || exit
{
  rm -f -- "$tmp1" "$tmp2"
  {
    {
      {
        producer | tee /dev/fd/4 /dev/fd/6 | consumer1 >&7
      } 4>&1 | consumer2 >&3
    } 6>&1 | consumer3 >&5
  } 7>&1
  cat - /dev/fd/6 <&4
} 3> "$tmp1" 4< "$tmp1" 5> "$tmp2" 6< "$tmp2"

在 Linux 上,这可以与dash, bash, zsh, mksh, busybox sh,一起使用,但不行。这种方法不能超过 4 个消费者,因为我们仅限于 fds 0 到 9。poshksh93

答案2

最好的解决方案使用临时文件。当进程替换不可行时,这使得代码可读且易于理解。

tmpfile=$(mktemp)

producer | tee "$tmpfile" | consumer1
consumer2 <"$tmpfile"

rm -f "$tmpfile"

甚至

tmpfile=$(mktemp)

producer >"$tmpfile"

consumer1 <"$tmpfile"
consumer2 <"$tmpfile"

rm -f "$tmpfile"

相关内容