这一页详细介绍了创建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