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

cuo*_*glm 21 linux shell proc read

在一些类似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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

我猜外壳在引擎盖下做了一些事情,比如file

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty
Run Code Online (Sandbox Code Playgroud)

Sté*_*las 32

问题是这些/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
Run Code Online (Sandbox Code Playgroud)

一些 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
Run Code Online (Sandbox Code Playgroud)

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

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

ksh93使优化进一步变得虚假。ksh93read确实会回溯,但会记住它为下一个读取的额外数据read,因此下一个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
Run Code Online (Sandbox Code Playgroud)

  • 外壳没有检测到它。它是动态数据的事实意味着`procfs` 无法处理对同一文件的多个连续`read(2)` 调用;行为不依赖于外壳。使用 `cat` 和管道是有效的,因为 `cat` 以足够大的块读取文件;shell 内置的 `read` 然后一次从管道中读取一个字符。 (3认同)

meu*_*euh 11

如果你有兴趣知道为什么?就是这样,您可以在此处的内核源代码中看到答案:

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

基本上,*ppos对于!write作为数字的 sysctl 值的读取(),不会实现搜索(不是 0)。每当从 完成读取时,都会从同一文件中配置中的条目中调用有/proc/sys/fs/file-max问题的例程 。__do_proc_doulongvec_minmax()file-max

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

请注意,自内核 2.6 以来,大多数/proc读取都是通过名为seq_file的新 API 实现的 ,这支持搜索,因此例如读取/proc/stat不应导致问题。在/proc/sys/实施中,我们可以看到,不使用此API。