Linux FIFO 缓冲取决于读取器和写入器的调用顺序

Linux FIFO 缓冲取决于读取器和写入器的调用顺序

我正在尝试编写一个简单的 python 脚本,该脚本从 fifo 读取,然后写入另一个 fifo。

我使用以下命令创建了两个 FIFO:

$ mkfifo input
$ mkfifo output

我使用以下命令调用该脚本:

$ tail -f input | stdbuf -oL ../entropyCalc/entropy.py > output

,使用以下命令观察 FIFO output

$ tail -f output

然后使用以下命令调用编写器:

$ echo "/path/to/a/valid/file" >> input

问题是我希望 fifo 在处理输入文件后立即输出结果,但我只观察到当我调用(执行)脚本一次时,退出然后重新执行脚本。此后一切正常。

总之:如果执行脚本 -> 启动读取器 -> 写入fifo ,则
在读取器中看不到任何输出但是,当我执行脚本 -> 启动读取器 -> 写入 fifo 时, 该命令会输出结果-> 终止脚本 -> 重新执行脚本input
tail -f output

我不确定是什么导致了这种行为,因为我希望系统在结果写入标准输出后立即写入文件。如果我不使用stdbuf -oL限制缓冲到一行的缓冲,我会期望缓冲。

python 脚本是一个简单的熵计算器:

#! /usr/bin/env python2

import sys, os
import numpy as np
from scipy.stats import entropy

while 1:
    try:
        line = sys.stdin.readline()
    except KeyboardInterrupt:
        break

    if not line:
        break

    line = line.strip()

    if not line == '':
        fname1 = open(line)
        fsize = os.path.getsize(line)
        f1 = np.frombuffer(fname1.read(fsize), dtype=np.uint8)
        value,counts = np.unique(f1, return_counts=True)
        print line,str(entropy(counts))
        sys.stdout.flush()

我在 Ubuntu 18.04.3 上使用 bash 4.4

答案1

你用tail错了。

您所观察到的只是表明tail -f output正如广告所宣传的那样。而且你的使用过程中还有更多的陷阱tail -f input

请注意,这些tail误用也会破坏脚本的操作完整性。详情请参阅下文...

简短说明

您的使用问题tail -f input

  • 它首先等待文件结尾。
  • 一旦文件结束第一的到达(第一个文件名提供者终止),它只会将最后 10 个文件名传递给脚本;破坏运营诚信。
  • 它缓冲直到达到每个文件结尾,使得交互式/稀疏文件名输入不可用。

您的使用问题tail -f output

  • 它首先等待文件结尾。
  • 一旦文件结束第一的达到(你的脚本第一次被杀死),到目前为止它只会给出脚本输出的最后 10 行;破坏运营诚信。

(缓冲不是问题,tail -f output因为它的输出腿是终端)

结论以下为补救措施。

长解释

的工作描述tail基本上是:“输出之前的10行文件结尾”。-f开关仅添加“以及随后附加的所有内容”。

  • 为了实现其主要目标,tail必须不断地无声地、无休止地读取输入(缓冲区中仅保留最新的 10 行),直到到达文件结尾。然后它会将缓冲区中的最后 10 行转储到输出,然后退出(或继续同-f相操作)。

现在,让我提醒你文件结尾于管道在上游程序终止之前永远不会出现1 ...

结果就是tail -f output意志,按设计,保持默默地缓冲脚本的结果,而不发出任何内容......直到您第一次终止脚本的那一刻:文件结尾被发送到tail -f output,那就是您开始看到输出的时候。

  • 你看到的并不是完整的输出到目前为止,而是仅输出最后 10 行在那时候。

    如果您要求脚本分析 20 个文件(一次少于 10 个文件,请参阅下一点),您将仅获得在第一次终止脚本之前分析的最后 10 个文件的结果。这打破了脚本操作的期望(即完整性),其中的结果每一个所做的分析应该在输出中报告。

  • 这个问题也影响tail -f input。将包含 11 个以上文件名的文件列表转储到input管道中第一次,将导致只有 10 个最底部的名称到达您的脚本,破坏脚本操作的完整性再次。第二次及以后,这将不再是问题。

故事还没有结束……

一旦到达第一个文件结尾,tail -f将继续分阶段操作-f,这意味着输出随后附加到输入文件的所有内容

在这个阶段,它还不会关闭输入句柄;它将注册一个inotify输入监听器文件,然后等待。每次任何程序写入/关闭输入文件时,tail都会再次读取输入句柄(并且输出刚刚读到的所有内容),直到遇到文件结尾2,然后又回到等待状态。冲洗并重复,直到手动终止tail -f

  • 这里的一个问题是“输出刚刚读到的所有内容也受到通常的输出缓冲当输出不是终端时。

    它不会影响您tail -f output本身的使用(因为它的输出支路是一个终端)。tail -f input但是,将会受到影响:

    • 如果您使用 交互地输入多个文件名cat > input,您将看到在您终止之前处理不会开始cat
    • 这意味着将input管道连接到仅偶尔发出文件名的长时间运行的程序也不起作用。

结论

根据我的理解,您曾经tail -f input屏蔽脚本接收文件结尾,因此您的脚本可以像守护进程,而文件名提供者程序可以来来去去。

所以我的建议是使用tail -n +1 -f它,以确保tail不要无休止地寻找文件结尾,并消除最后 10 行问题。然后用stdbuf它来控制可能成为问题的缓冲3,4 ...

stdbuf -oL tail -n +1 -f input | ../entropyCalc/entropy.py > output 2>&1

然后在监控端,使用:

tail -n +1 -f output

或者:

while true; do cat output; done

脚注

  • 1或者更准确地说,当上游程序关闭管道时。 (在你的情况下,它只会发生tail -f output在你的脚本终止时)
  • 2永远不会发生在你的身上tail -f output,除非你终止了你的脚本第二时间。
  • 3您的脚本已经在其输出阶段进行了行缓冲,因此不需要stdbuf那里。
  • 4标准错误重定向2>&1还将确保脚本错误也显示在监控终端上。

相关内容