当我 `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运行 压缩、缩短或以其他方式未在末尾修改的写入,但这种检测是参差不齐的。它可以检测到其中一些,但会漏掉很多。
算法
- tail 使用 inotify 来观察变化。
- 当发生更改事件时,它会迅速 运行s fstat() 检查文件的元数据,包括其大小。
- 如果大小较大,则假定已附加数据并读取添加的数据。
- 如果尺寸较小,则打印 "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
我正在处理一些脚本,遇到了这种情况。这是一个简化的例子。
在一个 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运行 压缩、缩短或以其他方式未在末尾修改的写入,但这种检测是参差不齐的。它可以检测到其中一些,但会漏掉很多。
算法
- tail 使用 inotify 来观察变化。
- 当发生更改事件时,它会迅速 运行s fstat() 检查文件的元数据,包括其大小。
- 如果大小较大,则假定已附加数据并读取添加的数据。
- 如果尺寸较小,则打印 "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