3

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

为什么这不像我预期的那样工作?

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

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

4

2 回答 2

9

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

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

一点演示应该会使区别更清楚一点:

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

1> mkfifo xxx
1> cat xxx

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

第二个外壳:

2> cat > xxx 

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

第三个外壳:

3> echo Hello > xxx
3>

该生产者立即返回。

第一个外壳

Hello

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

第三壳

3> echo World > xxx
3> 

第一个外壳

World

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

第二个外壳:写入cat > xxx窗口:

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

第一个外壳

And good bye!
1>

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


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

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

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

于 2012-02-20T10:49:04.130 回答
0

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

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

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

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

您可以使用此示例进行检查:

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

该脚本将挂起,直到您从另一个外壳(或第一个外壳)编写任何内容。但是一旦读取工作,它就会结束。

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

问题是,在运行脚本(假设脚本“a”)之后,您还剩下一个“a”进程。所以,是的,在某种程度上,脚本就像我之前写的那样挂起(不是我当时想的那个愚蠢的答案:))。除非你只写一个日志(无论是日志文件还是回显,在这种情况下它都有效)。

(当写入 PIPE_LOG 时,来自 PIPE_ECHO 的读取循环会挂起,并且每次都会让进程运行)。

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

  • 仅从 PIPE_LOG 读取一行,然后循环结束
  • 然后将第二条消息发送到 PIPE_LOG(从 PIPE_ECHO 接收到之后),但进程不再从 PIPE_LOG 读取 => 写入挂起。

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

希望它会有所帮助...

于 2012-02-19T20:58:37.300 回答