为什么某些 shell 内置的“read”无法从“/proc”中的文件读取整行?

为什么某些 shell 内置的“read”无法从“/proc”中的文件读取整行?

在一些类似 Bourne 的 shell 中,read内置命令无法从文件中读取整行/proc(下面的命令应该在 中运行zsh$=shell$shell其他 shell 替换):

$ for shell in bash dash ksh mksh yash zsh schily-sh heirloom-sh "busybox sh"; do
  printf '[%s]\n' "$shell"
  $=shell -c 'IFS= read x </proc/sys/fs/file-max; echo "$x"'       
done
[bash]
602160
[dash]
6
[ksh]
602160
[mksh]
6
[yash]
6
[zsh]
6
[schily-sh]
602160
[heirloom-sh]
602160
[busybox sh]
6

read标准要求标准输入必须是文本文件,这个要求会导致不同的行为吗?


阅读 POSIX 定义文本文件,我做了一些验证:

$ od -t a </proc/sys/fs/file-max 
0000000   6   0   2   1   6   0  nl
0000007

$ find /proc/sys/fs -type f -name 'file-max'
/proc/sys/fs/file-max

NUL的内容中没有字符/proc/sys/fs/file-max,并且还将find其报告为常规文件(这是 中的错误吗find?)。

我猜 shell 在幕后做了一些事情,比如file

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty

答案1

问题在于/procLinux 上的这些文件就其stat()/fstat()外观而言显示为文本文件,但其行为却并非如此。

因为它是动态数据,所以您只能read()对它们执行一次系统调用(至少对于其中一些数据)。执行多个操作可能会得到两个不同内容的两个块,因此,似乎第二个read()操作不会返回任何内容(意味着文件结束)(除非您lseek()回到开头(并且仅回到开头))。

read实用程序需要一次读取一个字节的文件内容,以确保不会读取超过换行符的内容。这就是dash

 $ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
 read(0, "1", 1)                         = 1
 read(0, "", 1)                          = 0

有些 shell 喜欢bash进行优化以避免执行如此多的read()系统调用。他们首先检查文件是否可查找,如果是,则分块读取,因为他们知道如果他们已经读过它,可以将光标放回换行符后面:

$ strace -e lseek,read bash -c 'read a' < /proc/sys/fs/file-max
lseek(0, 0, SEEK_CUR)                   = 0
read(0, "1628689\n", 128)               = 8

使用 时bash,对于超过 128 字节大且只能在一次读取系统调用中读取的 proc 文件,您仍然会遇到问题。

bash-d使用该选项时似乎也禁用了该优化。

ksh93进一步优化甚至变得虚假。 ksh93read确实会回溯,但会记住它为 next 读取的额外数据read,因此 next read(或任何其他读取数据的内置函数,如cathead)甚至不会尝试read该数据(即使该数据已被修改)之间的其他命令):

$ seq 10 > a; ksh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 2
$ seq 10 > a; sh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 st

答案2

如果您有兴趣了解为什么?是这样的,你可以在内核源码中看到答案这里:

    if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
            *lenp = 0;
            return 0;
    }

基本上,对于作为数字的 sysctl 值的*pposread() 不会实现查找(非 0) 。!write每当从 完成读取时,就会从配置中的条目调用/proc/sys/fs/file-max相关例程 __do_proc_doulongvec_minmax()file-max桌子在同一个文件中。

其他条目,例如/proc/sys/kernel/poweroff_cmd通过 which 实现的, proc_dostring()允许查找,因此您可以dd bs=1对其进行操作并从 shell 中读取,没有任何问题。

请注意,自内核 2.6 以来,大多数/proc读取都是通过名为的新 API 实现的 序列文件 这支持寻求,因此例如阅读/proc/stat不应引起问题。正如我们所看到的,该/proc/sys/实现并没有使用这个 api。

答案3

第一次尝试时,这看起来像是 shell 中的错误,其返回值小于真正的 Bourne Shell 或其衍生产品(sh、bosh、ksh、heirloom)的返回值。

原始的 Bourne Shell 尝试读取一个块(64 字节),较新的 Bourne Shell 变体读取 128 字节,但如果没有换行符,它们会再次开始读取。

背景: /procfs 和类似的实现(例如挂载的/etc/mtab虚拟文件)具有动态内容,并且stat()调用不会导致娱乐首先是动态内容。因此,此类文件的大小(从读取到 EOF)可能与stat()返回的大小不同。

鉴于 POSIX 标准要求实用程序期望短读在任何时候,软件认为 aread()的返回值小于已订购EOF 指示的字节数已损坏。正确实现的实用程序会read()在返回小于预期的情况下再次调用 - 直到返回 0。对于read内置的情况,当然足以阅读直到EOF 或者直到NL看到a。

如果您运行truss桁架克隆,您应该能够验证仅6在实验中返回的外壳的不正确行为。

在这种特殊情况下,它似乎是一个 Linux 内核错误,请参阅:

$ sdd -debug bs=1 if= /proc/sys/fs/file-max 
Simple copy ...
readbuf  (3, 12AC000, 1) = 1
writebuf (1, 12AC000, 1)
8readbuf  (3, 12AC000, 1) = 0

sdd: Read  1 records + 0 bytes (total of 1 bytes = 0.00k).
sdd: Wrote 1 records + 0 bytes (total of 1 bytes = 0.00k).

Linux内核返回0与第二个read,这当然是不正确的。

结论:首先尝试读取足够大的数据块的 Shell 不会触发此 Linux 内核错误。

答案4

/proc 下的文件有时使用 NULL 字符来分隔文件内的字段。看来 read 无法处理这个问题。

相关内容