如何将一个命名管道的输出反馈到另一个命名管道?

时间:2012-02-19 19:49:49

标签: bash pipe named-pipes sh mkfifo

我正在为bash脚本添加一些自定义日志记录功能,并且无法弄清楚为什么它不会从一个命名管道获取输出并将其反馈到另一个命名管道。

以下是脚本的基本版本(http://pastebin.com/RMt1FYPc):

#!/bin/bash

PROGNAME=$(basename $(readlink -f $0))
LOG="$PROGNAME.log"
PIPE_LOG="$PROGNAME-$$-log"
PIPE_ECHO="$PROGNAME-$$-echo"

# program output to log file and optionally echo to screen (if $1 is "-e")
log () {
  if [ "$1" = '-e' ]; then 
    shift
    $@ > $PIPE_ECHO 2>&1 
  else 
    $@ > $PIPE_LOG 2>&1 
  fi
}

# create named pipes if not exist
if [[ ! -p $PIPE_LOG ]]; then 
  mkfifo -m 600 $PIPE_LOG
fi
if [[ ! -p $PIPE_ECHO ]]; then 
  mkfifo -m 600 $PIPE_ECHO
fi

# cat pipe data to log file
while read data; do
  echo -e "$PROGNAME: $data" >> $LOG 
done < $PIPE_LOG &

# cat pipe data to log file & echo output to screen
while read data; do
  echo -e "$PROGNAME: $data"
  log echo $data   # this doesn't work
  echo -e $data > $PIPE_LOG 2>&1   # and neither does this
  echo -e "$PROGNAME: $data" >> $LOG   # so I have to do this
done < $PIPE_ECHO &

# clean up temp files & pipes
clean_up () {
  # remove named pipes
  rm -f $PIPE_LOG
  rm -f $PIPE_ECHO
}
#execute "clean_up" on exit
trap "clean_up" EXIT 

log echo "Log File Only"
log -e echo "Echo & Log File"

我认为34号线上的命令和35将从$data获取$PIPE_ECHO并将其输出到$PIPE_LOG。但是,它不起作用。相反,我必须将该输出直接发送到日志文件,而不通过$PIPE_LOG

为什么这不符合预期?

编辑:我将shebang改为“bash”。但问题是一样的。

解决方案:A.H.'s answer帮助我理解我没有正确使用命名管道。我已经解决了我的问题,甚至没有使用命名管道。该解决方案位于:http://pastebin.com/VFLjZpC3

2 个答案:

答案 0 :(得分:9)

在我看来,你不明白命名管道到底是什么。命名管道不像普通管道那样一个流。它是普通管道的系列,因为命名管道可以关闭,生产者端的关闭可能在消费者端显示为关闭。

可能部分是这样的:消费者将读取数据,直到没有更多数据。没有更多数据意味着,在read调用时没有生产者打开命名管道。这意味着只有在没有至少一个生产者的时间点没有时间点的情况下,多个生产者才能喂养一个消费者。想一想自动关闭的门:如果有一个稳定的人流,通过将门把手递给下一个门或者同时挤压多个人来保持门始终打开,门就打开了。但是一旦门关闭,它就会一直关闭。

一点点演示应该会使区别变得更加清晰:

打开三个炮弹。 第一次 shell:

1> mkfifo xxx
1> cat xxx

没有显示输出,因为cat已打开命名管道并正在等待数据。

第二次 shell:

2> cat > xxx 

没有输出,因为这个cat是一个生产者,它使命名管道保持打开状态,直到我们告诉他明确地关闭它。

第三次 shell:

3> echo Hello > xxx
3>

该生产者立即返回。

第一个shell

Hello

消费者收到数据,写下来并且 - 由于还有一个消费者保持开门,继续等待。

第三个外壳

3> echo World > xxx
3> 

第一个shell

World

消费者收到数据,写下来并且 - 由于还有一个消费者保持开门,继续等待。

第二个Shell :写入cat > xxx窗口:

And good bye!
(control-d key)
2>

第一个shell

And good bye!
1>

^ D 键关闭了最后一个生产者cat > xxx,因此消费者也退出了。


在你的情况下,这意味着:

  • 您的log函数会尝试多次打开和关闭管道。不是个好主意。
  • 您的while循环都比您想象的更早退出。 (使用(while ... done < $PIPE_X; echo FINISHED; ) &
  • 进行检查
  • 根据您的各种生产者和消费者的日程安排,门可能会被关闭,有时甚至没有关闭 - 您内置了竞争条件。(对于测试,您可以在sleep 1的末尾添加log {1}}功能。)
  • 您的“测试用例”只尝试一次 - 尝试多次使用它们(您将阻止,尤其是使用sleep),因为您的生产者可能找不到任何消费者

所以我可以解释代码中的问题,但我不能告诉你一个解决方案,因为不清楚你的需求的边缘是什么。

答案 1 :(得分:0)

似乎问题出现在“cat pipe data to log file”部分。

让我们看看:你使用“&amp;”把循环放在后台,我想你的意思是它必须与第二个循环并行运行。

但问题是你甚至不需要“&amp;”,因为只要fifo中没有更多数据可用,while..read就会停止。 (至少你必须先得到一些第一次阅读才能工作)。如果没有更多数据可用,则下一次读取不会挂起(这会带来另一个问题:程序如何停止?)。

我想while读取会在执行读取之前检查文件中是否有更多数据可用,如果不是这样,则会停止。

您可以查看此示例:

mkfifo foo
while read data; do echo $data; done < foo

此脚本将挂起,直到您从另一个shell(或第一个shell)写入任何内容。但只要阅读有效,它就会结束。

编辑: 我已经在RHEL 6.2上进行了测试,它就像你说的那样工作(例如:糟糕!)。

问题在于,在运行脚本(假设脚本为“a”)之后,你还有一个“a”进程。所以,是的,在某种程度上,脚本像我之前写的那样挂起(不是那个愚蠢的答案,因为我认为:))。除非您只写一个日志(仅限日志文件或回显,在这种情况下它可以工作)。

(这是PIPE_ECHO的读取循环,在写入PIPE_LOG时会挂起并且每次都会运行一个进程)。

我添加了一些调试消息,这就是我所看到的:

  • 只从PIPE_LOG读取一行,然后循环结束
  • 然后将第二条消息发送到PIPE_LOG(在从PIPE_ECHO接收之后),但该过程不再从PIPE_LOG =&gt;读取。写作挂起。

当你ls -l / proc / [pid] / fd时,你可以看到fifo仍然是打开的(但已被删除)。 事实上,脚本退出并删除了fifos,但仍然有一个进程使用它。 如果你没有在清理时删除log fifo并将其捕获,它将释放挂起过程。

希望它会有所帮助...