如何使用命名管道编写 bash 管道的“组合”功能

如何使用命名管道编写 bash 管道的“组合”功能

这一页详细介绍了创建compose函数的伪代码n命令并在管道中执行它们:

我们编写一个命令compose,使得

compose cmd1 cmd2 ... cmdn

其行为类似于 shell 命令:

cmd1 | cmd2 | ... | cmdn

我正在尝试命名管道compose对用 Bash 实际编写感兴趣。不幸的是,当我这样做时,我没有得到任何输出,大概是由于读取和写入不同管道的竞争条件。我已经进行了多次迭代,但不断出现令人困惑的行为。我把它简化为这个更小的问题:

echo foo |   # stdin
{
  mkfifo p   # create pipe p
  cat > p &  # direct stdin to pipe p
  cat < p    # read pipe p to stdout
  rm p       # remove pipe p
}

我希望这个输出foo,但我什么也没得到。我究竟做错了什么?

答案1

问题中示例代码的问题很微妙;为了写入命名管道,您需要&将该命令置于后台,否则它将阻塞等待读取。然而,当你这样做时,“在后台启动的命令&将其标准输入重定向到 [to] /dev/null",含义/dev/null是通过管道输入的内容p,而不是标准输入。

在 Bash 中,解决方法很简单,将 stdin 重定向到后台进程0<&0。然后该示例将正常工作:

$ echo foo | { mkfifo p; cat 0<&0 > p & cat < p; rm p; }
foo

完整的compose函数最终看起来像这样:

compose() {
  dir=$(mktemp -d)                 # Create a temp dir to hold the pipes
  cd $dir                          #   to avoid filename conflicts
  i=0                              #
  mkfifo "pipe$i"                  # Create pipe0, the output for command $1
  ($1 0<&0 > "pipe$i" &)           # Start $1, reading stdin and writing to pipe0
  shift                            # Shift off $1 since it's running
    for c in "$@"; do              # Loop over the remaining commands
    ii=$((i+1))                    # 
    mkfifo "pipe$ii"               # Create a pipe i+1, the next command's output
    ($c < "pipe$i" > "pipe$ii" &)  # Start the next command, reading from the
    i=$ii                          #   i'th pipe and writing to the i+1'th
  done                             #
  cat "pipe$i"                     # Output the last pipe, executing the commands
  cd - > /dev/null                 # Change back to the old directory
  rm -rf $dir                      # Remove all the pipes
}

答案2

compose()
    case    $#  in  
    [01])  "$@" ;;          ## if 1 or fewer args just run what we've got
       *)  "$1" | {         ## otherwise pipe output from $1 into a
    shift; compose "$@"     ## self-call until all args are gone
};  esac

相关内容