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


19

在某些类似Bourne的shell中,read内置/proc命令无法从file in读取整行(以下命令应在in中运行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报告为常规文件(这是bug find吗?)。

我猜壳在幕后做了些什么,例如file

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

Answers:


31

问题在于,/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文件有问题,这些文件只能在一个read系统调用中读取。

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

ksh93甚至使优化进一步变得虚假。ksh93 read确实会找回,但会记住它为下一个读取的额外数据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

是的,strace基于解释的解释更容易理解!
史蒂芬·基特

谢谢,动态数据很有意义。那么外壳如何检测它的动态数据呢?如果我这样做cat /proc/sys/fs/file-max | ...,问题就解决了。
cuonglm '16

3
外壳无法检测到它。它是动态数据这一事实意味着它procfs无法处理read(2)对同一文件的多个连续调用。行为不取决于外壳。使用cat和管道化是cat可行的,因为要以足够大的块读取文件。read然后,shell的内置函数一次从管道读取一个字符。
史蒂芬·基特

1
中有一个肮脏的解决方法mkshread -N 10 a < /proc/sys/fs/file-max
Ipor Sircer

1
@IporSircer。确实。似乎可以使用类似的解决方法zsh:(read -u0 -k10或使用sysread; $mapfile[/proc/sys/fs/file-max]无效,因为不能mmap编辑这些文件)。在任何情况下,无论使用哪种外壳,都可以始终使用a=$(cat /proc/sys/fs/file-max)。对于某些include mkshzshand ksh93a=$(</proc/sys/fs/file-max)也可以使用,并且不会派生过程来进行读取。
斯特凡Chazelas

9

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

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

基本上,*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。


3

第一次尝试时,这看起来像是外壳程序中的错误,其返回值小于真实的Bourne Shell或其派生值(sh,bosh,ksh,传家宝)。

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

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

鉴于POSIX标准要求实用程序随时可以进行短读,因此认为read()返回小于预定字节数的a是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内核在第二秒返回0read这当然是不正确的。

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


好的,通过对Linux内核错误的新验证退出了答案。
2016年

这不是一个错误,这是一个功能!
Guntram Blohm

这真是一个奇怪的说法。
2016年

如果有记录,它将成为一个功能。在阅读kernel.org/doc/Documentation/filesystems/proc.txt时,我没有看到有关该行为的文档。就是说,它显然是由实现者按预期工作的,因此,如果将其视为错误,那是设计中的错误,而不是实现中的错误。
查尔斯·达菲

0

/ proc下的文件有时使用NULL字符分隔文件内的字段。似乎read无法解决此问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.