管道生产者如何告诉管道消费者它已到达“文件结尾”?”(未命名管道,而不是命名管道)

管道生产者如何告诉管道消费者它已到达“文件结尾”?”(未命名管道,而不是命名管道)

我有一个应用程序需要制片人将文件名发送到消费者,并且有制片人指示给消费者当最后一个文件名被发送并且文件结尾已达到。

为简单起见,在以下示例中制片人 用 和 来演示echoprintf而消费者则用 来演示cat。我试图推断“此处文件”方法,但没有成功,使用<<EOF来指示生产者包装器(如果存在这样的事情)要寻找什么作为指示文件结尾。如果它有效,cat应该EOF从输出中过滤掉。

例 1)

输入

{
echo "Hello World!" 
printf '\x04' 
echo "EOF"
} <<EOF |\
cat

输出

bash: warning: here-document at line 146 delimited by end-of-file (wanted `EOF')
Hello World!
EOF

例 2)

输入

{ 
echo "Hello World!" 
printf '\x04' 
echo "EOF"
} |\
cat <<EOF

输出

bash: warning: here-document at line 153 delimited by end-of-file (wanted `EOF')

用于指示分隔符的“此处文件”方法仅适用于静态文本,而不适用于动态创建的文本,这是否正确?

——实际应用——

inotifywait -m --format '%w%f' /Dir |  <consumer>

消费者正在等待文件写入目录/Dir。如果当写入文件“/Dir/EOF”时,消费者只需通过编写 shell 脚本即可检测逻辑文件结束条件,如下所示:

inotifywait -m --format '%w%f' /Dir |<</Dir/EOF  <consumer>

——回应贾尔斯的回答——

理论上是否可以实现

cat <<EOF
hello
world
EOF

作为

SpecialSymbol="EOF"
{
    echo hello
    echo world
    echo $SpecialSymbol
} |\
while read Line; do 
  if [[ $Line == $SpecialSymbol ]]
    break
  else 
    echo $Line
  fi
done |\
cat

理论上可能我的意思是“它会支持现有的使用模式,并且只启用以前非法语法的额外使用模式吗?”- 意味着现有的法律法规不会被破坏。

答案1

对于管道,一旦所有生产者都关闭了管道的文件描述符并且消费者读取了所有数据,消费者就会看到文件末尾。

所以,在:

{
  echo foo
  echo bar
} | cat

catecho一旦第二个终止并cat读取了foo\nbar\n,就会看到文件结尾。你没有什么可做的了。

但要记住的是,如果管道左侧的某些命令启动某个后台进程,则该后台进程将继承管道的 fd(其标准输出),因此在cat该进程也终止之前不会看到 eof或关闭其标准输出。如:

{
  echo foo
  sleep 10 &
  echo bar
} | cat

您会看到cat10 秒过去后还没有返回。

在这里,您可能希望将sleep的 stdout 重定向到其他内容,例如/dev/null如果您不希望将其(非)输出提供给cat

{
  echo foo
  sleep 10 > /dev/null &
  echo bar
} | cat

如果您希望在运行左侧子 shell 中的最后一个命令之前关闭管道的写入端|,则可以关闭 stdout 或使用 重定向到子 shell 中间的该子 shell exec,例如:

{
  echo foo
  exec > /dev/null
  sleep 10
} | (cat; echo "cat is now gone")

但请注意,除了命令之外,大多数 shell 仍会等待该子 shell cat。因此,虽然您会cat is now gone立即看到(foo读取后),但您仍然需要等待 10 秒才能完成整个管道。当然,在上面的例子中,这样写会更有意义:

echo foo | cat
sleep 10

<<ANYTHING...content...ANYTHING是一个here-document,它使命令的stdin成为一个包含以下内容的文件内容。那里没有用。\4是一个字节,当从终端读取时,终端设备保存的数据将刷新到从中读取数据的应用程序(当没有数据时,read()返回 0,表示文件结束)。再说一次,这里没有任何用处。

答案2

您的尝试的问题在于您所谓的“此处文件方法”根本不存在。这里的文件是shell编程语言的一个语法特征。它们不会被诸如cat.

考虑一个带有此处文档的 shell 脚本:

cat <<EOF
hello
world
EOF

它的工作方式是,作为解析脚本的一部分,shell 解释器会看到here 文档语法结构。当它运行这些代码行时,解释器会收集数据并将其作为输入传递给cat.根据 shell 的不同,这基本上等同于

echo 'hello
world
' >/tmp/heredoc.tmp
cat </tmp/heredoc.tmp
rm /tmp/heredoc.tmp

(但对临时文件进行更明智的管理)或

echo 'hello
world
' | cat

如果您有echo EOF并且希望它指示此处文档的结尾,则命令的输出echo必须是 shell。

{
  echo 'cat <<EOF'
  echo hello
  echo world
  echo EOF
} | sh

这确实有效,但很少有用。

这些都不能帮助你完成你打算做的事情。这里的文档并没有什么神奇之处,使它比任何其他输入都“更难”结束。文件结尾不是通过输入通道传输的标记,而是输入通道的条件。当输入是常规文件时,当应用程序尝试读取文件的最后一个字节时,将触发文件结束条件。当输入是管道(命名或未命名)时,当应用程序尝试读取但生产者已关闭管道时,将触发文件结束条件。

通常这样写就足够了

producer | consumer

退出时producer,管道的写入端将关闭,因为没有进程再打开它。如果您希望提前关闭管道,请安排生产者关闭其标准输出(exec >&-在 sh 中,close(1)在 C 中)。

请注意,要到达管道上的文件结尾,所有打开管道写入端的进程都必须将其关闭。如果生产者运行后台进程,您可能需要fcntl(1, F_SETFD, fcntl(1, F_GETFC, 0) | FD_CLOEXEC)在分叉它们之前在管道 ( ) 上设置 close-on-exec 标志。

答案3

如果你想使用 FIFO 那么你当然可以这样做:

脚本1

FIFO_PATH="/path/to/fifo"
exec 3<"$FIFO_PATH" # stalls until FIFO is opened for writing, too
while read line; do
    : whatever
done <&3
exec 3<&-
: continue script

脚本2

FIFO_PATH="/path/to/fifo"
exec 3>"$FIFO_PATH" # stalls until FIFO is opened for reading, too
echo foo >&4
exec 4>&-
: continue script

相关内容