我观察到,在 Ubuntu 24.04.2coreutils
版本上9.4-3ubuntu6
运行:
$ tail -c 4097 /dev/zero
$ echo $?
0
立即退出,状态码为 0。我预计该命令将无限期阻塞,因为 /dev/zero 是一个无休止的流。
相反,以下命令的行为符合预期(即,它们会阻塞直到被中断):
$ tail -c 4096 /dev/zero
^C
$ echo $?
130
$ cat /dev/zero | tail -c 4097
^C
$ echo $?
130
调试尝试
strace 输出显示了两次调用之间的差异:
strace tail -c 4096 /dev/zero | strace tail -c 4097 /dev/zero |
---|---|
… | … |
关闭(3)= 0 | 关闭(3)= 0 |
openat(AT_FDCWD, “/dev/zero”, O_RDONLY) = 3 | openat(AT_FDCWD, “/dev/zero”, O_RDONLY) = 3 |
fstat(3,{st_mode=S_IFCHR|0666,st_rdev=makedev(0x1, 0x5),…}) = 0 | fstat(3,{st_mode=S_IFCHR|0666,st_rdev=makedev(0x1, 0x5),…}) = 0 |
lseek(3,-4096,SEEK_END) = 0 | lseek(3,-4097,SEEK_END) = 0 |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | 读取(3,“\0\0\0\0\0\0\0\0\0\0\…,4097)= 4097 |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | fstat(1,{st_mode=S_IFIFO|0600,st_size=0,…}) = 0 |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | 写入(1,“\0\0\0\0\0\0\0\0\0\0\…,4096 |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | 关闭(3)= 0 |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | 写入(1,“\0”,1)= 1 |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | 关闭(1)= 0 |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | 关闭(2)= 0 |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | 退出组(0)=? |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | ~~+~~ 以 0 退出 ~~+~~ |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | |
读取(3,“\0\0\0\0\0\0\0\0\0\00”…,8192)= 8192 | |
… |
首先要注意的是,设备文件可以不支持
seek
;也许令人惊讶的是,在这样的文件上查找会成功,而无需移动文件中的位置。在 9.6 版本之前,coreutils
tail
会尝试从文件末尾开始查找,并根据查找后的位置计算最终位置(无论该位置之后请求了多少字节)。因此,使用 时/dev/zero
,它会决定需要从位置 0 读取到n。然后,它会查看要读取的数据量以确定如何读取:如果是一页(在本例中为 4,096 字节)或更少,它会通过管道传输到文件末尾;如果超过一页,它会转储所需数量的字节。至关重要的是,这里的管道传输会一直读取到文件末尾,而这永远不会发生!因此,读取超过一页的数据就算完成,而读取一页或更少的数据则会永远循环下去。这个问题在 2024 年被报告为一个 bug,和你的预期一样——命令应该循环执行。不过,这个问题在 9.6 版本中已经修复,从这类设备读取数据时不会出现循环。