当我 `tail -f` 已使用 `>` 运算符重定向的文件时,为什么我会出现不一致的行为

Why do I have inconsistent behaviour when I `tail -f` a file that has been redirected with the `>` operator

我正在处理一些脚本,遇到了这种情况。这是一个简化的例子。

在一个 tty 上,

# touch file
# tail -f file 2> /dev/null

在另一个 tty 上,在同一目录中,运行 以下脚本:

#!/bin/bash
for i in {1..15}; do
    echo $i > ./file
    sleep 2
done

为什么当我使用 > 运算符时 tail -f 命令不能正确反映文件更改?如果我使用 >> 附加运算符,它会按预期工作。

最终,使用 > 运算符重定向的文件尾部显示如下:

1
2
6
7
9

15

tail -f 仅可靠地检测 附加的 的更改。它 尝试 检测文件被 t运行 压缩、缩短或以其他方式未在末尾修改的写入,但这种检测是参差不齐的。它可以检测到其中一些,但会漏掉很多。

算法

  1. tail 使用 inotify 来观察变化。
  2. 当发生更改事件时,它会迅速 运行s fstat() 检查文件的元数据,包括其大小。
  3. 如果大小较大,则假定已附加数据并读取添加的数据。
  4. 如果尺寸较小,则打印 "file truncated" 并从头开始。

后果

> t运行 将文件大小调整为 0,然后写入新内容。 tail 是否检测到这些类型的写入是命中还是未命中。如果您写入更大或相同数量的字节,它通常会错过这些写入。

  • 第 4 步确保它能够可靠地检测文件长度是否缩短。如果您修改循环以在每次迭代中连续写入较短的字符串,那么 tail 将检测到每个字符串:

    for i in {1..5}; do
      for ((j=6-i; j>=0; --j)); do echo $i; done > file
      sleep 2
    done
    
    tail: file: file truncated
    1
    1
    1
    1
    1
    tail: file: file truncated
    2
    2
    2
    2
    tail: file: file truncated
    3
    3
    3
    tail: file: file truncated
    4
    4
    tail: file: file truncated
    5
    
  • 步骤 1 和步骤 2 之间存在竞争条件。当您 运行 echo $i > file 有两个背靠背修改:文件是 t运行cated,然后写入$i。如果 tail 能够在两次修改之间 运行 fstat(),那么它会检测到 t运行 阳离子。但是,如果速度太慢,它就会错过。这就是通常发生的情况,这就是为什么它错过了大部分但不是全部的写入。

    这也解释了为什么睡觉没有帮助。要消除竞争条件,您需要在 t运行cating 和写入 $i 之间睡眠,而不是在

    之后

    确实,您完全可以这样做:

    for i in {1..15}; do (sleep 2; echo $i) > file; done
    

    > file 运行s 立即和 t运行cates 文件。然后脚本在写入 $i 之前休眠两秒钟。两次修改之间有明显的差距。

    正如预期的那样,tail 现在可以检测到每一个写入:

    1
    tail: file: file truncated
    2
    tail: file: file truncated
    3
    tail: file: file truncated
    4
    tail: file: file truncated
    5
    tail: file: file truncated
    6
    tail: file: file truncated
    7
    tail: file: file truncated
    8
    tail: file: file truncated
    9
    tail: file: file truncated
    10
    tail: file: file truncated
    11
    tail: file: file truncated
    12
    tail: file: file truncated
    13
    tail: file: file truncated
    14
    tail: file: file truncated
    15