【问题标题】:How can I have output from one named pipe fed back into another named pipe?如何将一个命名管道的输出反馈到另一个命名管道?
【发布时间】:2012-03-10 07:01:17
【问题描述】:

我正在向 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 行的命令会从$PIPE_ECHO 获取$data 并将其输出到$PIPE_LOG。但是,它不起作用。相反,我必须将该输出直接发送到日志文件,而不通过 $PIPE_LOG

为什么这不符合我的预期?

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

解决方案:A.H.'s answer 帮助我了解我没有正确使用命名管道。从那以后,我什至不使用命名管道来解决我的问题。该解决方案在这里:http://pastebin.com/VFLjZpC3

【问题讨论】:

  • 您说这是一个 bash 脚本,但您在脚本的第一行指定了 /bin/sh。您确定需要在 bash 中执行此操作吗?请记住,并非每个操作系统都默认安装 bash。
  • 我想说潜在的问题是,如果你使用它的路径而不是 bash [path] 运行这个脚本,你将调用 bash 的“sh”版本(即使它是相同的可执行文件),它的工作原理并不完全相同(限制)。
  • 我尝试用“bash”替换“sh”,它的工作方式相同(即它没有)。
  • 有些东西我不明白:如果我用“#!/bin/sh”运行它,我会遇到错误(21: [[: not found)并且它不起作用.如果我使用“#!/bin/bash”运行,它就像一个魅力(它回显“Echo & Log File”,在日志文件中,我有“Log File Only”和“Echo & Log File”)。是你所期望的吗?
  • 我希望第 34、35 和 36 行都将“Echo & Log File”输出到日志文件,因此日志文件中应该有 3 个“Echo & Log File”行。实际上,第 34 行和第 35 行没有,但第 36 行有。我想在 34 或 35 中使用该方法,因此我不必复制输出逻辑。在生产版本中,我将其他内容添加到输出中,例如日期时间戳,我最好能够将“pipe_echo”输出传递给“pipe_log”输出而不是复制逻辑“pipe-echo”函数中的“pipe_log”例程。

标签: bash pipe named-pipes sh mkfifo


【解决方案1】:

在我看来,您并不了解命名管道到底是什么。命名管道不像普通管道那样 one 流。它是 一系列 普通管道,因为命名管道可以关闭,并且生产者端的关闭可能显示为消费者端的关闭。 p>

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

一个小示范应该让区别更清楚一点:

打开三个外壳。 第一个外壳:

1> mkfifo xxx
1> cat xxx

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

第二个外壳:

2> cat > xxx 

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

第三个外壳:

3> echo Hello > xxx
3>

这个生产者立即返回。

第一个外壳

Hello

消费者收到数据,将其写入 - 因为还有一个消费者保持开门,所以继续等待。

第三壳

3> echo World > xxx
3> 

第一个外壳

World

消费者收到数据,将其写入 - 因为还有一个消费者保持开门,所以继续等待。

第二个shell:写入cat &gt; xxx窗口:

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

第一个外壳

And good bye!
1>

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


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

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

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

【讨论】:

  • 很好的答案,它说得很清楚。但奇怪的是它在 RHEL 6.2 和 Debian Squeeze 中的行为非常不同,我不明白为什么。在 RHEL 中,第一个循环根本不退出(卡在写而不是读……而不是读)。在 Debian 中,发送多个日志可以正常工作,但是添加 sleep 会导致整个事情挂起。我想深入探讨这个主题。
  • @huelbois:正如我所写:这是一个由sleep 明确表示的竞争条件:如果没有生产者“保持大门敞开”的“源源不断”,消费者将退出,下一个生产者将阻塞。没有睡眠,“源源不断”的可能性更大。不同的操作系统发行版也表示不同的硬件,因此也有不同的调度行为。
  • @huelbois:我试图隐藏一些内部结构,但现在我认为最好为您命名:普通管道和 FIFO(又名命名管道)之间的一个关键区别是 where 阻塞正常发生:使用标准管道的缺点。和产品。将阻止read/write 调用。另一方面,FIFO 通常会阻塞消费者和生产者的 open 调用!在两个“见面”之后,open 调用都返回并且两个缺点。和产品。继续他们的工作(可能再次阻塞)。这意味着同步是在open 期间完成的,而不是在read/write 期间完成的。
  • 谢谢,A.H. 你说得对……我没有正确理解 FIFO 的工作原理。你能帮我弄清楚如何使这项工作?这就是我要完成的工作: 上面的代码是启动脚本 (rc.d/init.d) 的一部分,它执行另一个供应商提供的脚本来启动应用程序。当供应商提供的脚本运行时,它将消息输出到 STDOUT(并最终退出)。我想捕获这些消息,添加一些东西(时间戳、程序名称),并将生成的消息重定向到日志文件(并可选择回显到 STDOUT/终端)。
  • A.H.:让我知道我是否应该将此作为新问题重新发布。我不确定这方面的礼仪。谢谢!
【解决方案2】:

看来问题出在“cat pipe data to log file”部分。

让我们看看:你使用“&”将循环置于后台,我猜你的意思是它必须与第二个循环并行运行。

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

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

您可以查看此示例:

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 读取 => 写入挂起。

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

希望对您有所帮助...

【讨论】:

  • 您可以尝试运行此脚本。它不挂。问题是第 34 和 35 行没有向日志文件输出任何内容。
  • 你是对的,对不起。但是今天通过 while read 和 fifo 学到了一些东西!
猜你喜欢
  • 2019-11-08
  • 2015-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-12
  • 2018-11-17
  • 1970-01-01
相关资源
最近更新 更多