在一些类似 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
问题在于/proc
Linux 上的这些文件就其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
(或任何其他读取数据的内置函数,如cat
或head
)甚至不会尝试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 值的*ppos
read() 不会实现查找(非 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 无法处理这个问题。