Tyl*_*ler 18 shell io-redirection files cat
以下 bash 命令进入无限循环:
$ echo hi > x
$ cat x >> x
Run Code Online (Sandbox Code Playgroud)
我可以猜测它在开始写入标准输出后会cat继续读取x。然而,令人困惑的是,我自己的 cat 测试实现表现出不同的行为:
// mycat.c
#include <stdio.h>
int main(int argc, char **argv) {
FILE *f = fopen(argv[1], "rb");
char buf[4096];
int num_read;
while ((num_read = fread(buf, 1, 4096, f))) {
fwrite(buf, 1, num_read, stdout);
fflush(stdout);
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果我运行:
$ make mycat
$ echo hi > x
$ ./mycat x >> x
Run Code Online (Sandbox Code Playgroud)
它不是循环。鉴于在再次调用之前cat我正在刷新的行为和事实,我希望这个 C 代码能够在一个循环中继续读取和写入。stdoutfread
这两种行为如何一致?什么机制解释了为什么cat循环而上述代码没有?
Bru*_*ger 13
在旧的RHEL系统我已经有了,/bin/cat确实没有for循环cat x >> x。cat给出错误消息“cat:x:输入文件是输出文件”。我可以欺骗/bin/cat通过这样做:cat < x >> x。当我尝试上面的代码时,我得到了您描述的“循环”。我还写了一个基于“cat”的系统调用:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int
main(int ac, char **av)
{
char buf[4906];
int fd, cc;
fd = open(av[1], O_RDONLY);
while ((cc = read(fd, buf, sizeof(buf))) > 0)
if (cc > 0) write(1, buf, cc);
close(fd);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这也循环。此处唯一的缓冲(与基于 stdio 的“mycat”不同)是内核中发生的事情。
我认为发生的事情是文件描述符 3(的结果open(av[1]))在文件中的偏移量为 0。文件描述符 1(stdout)的偏移量为 3,因为“>>”导致调用 shelllseek()在在将文件描述符交给cat子进程之前。
执行read()任何类型的 a,无论是进入 stdio 缓冲区,还是普通char buf[]文件都将write()推进文件描述符 3的位置。执行 a 将推进文件描述符 1 的位置。这两个偏移量是不同的数字。由于“>>”,文件描述符 1 的偏移量总是大于或等于文件描述符 3 的偏移量。因此任何“类猫”程序都会循环,除非它进行一些内部缓冲。有可能,甚至可能, a FILE *(这是符号stdout和f代码中的类型)的stdio 实现包含自己的缓冲区。fread()实际上可能会执行系统调用read()来填充内部缓冲区 fo f。这可能会也可能不会改变stdout. 调用fwrite()上stdout可能会也可能不会改变f. 因此,基于 stdio 的“猫”可能不会循环。或者它可能。如果不阅读大量丑陋、丑陋的 libc 代码,就很难说。
我做了一个strace在RHEL cat-它只是做的继承read()和write()系统调用。但是 acat不必以这种方式工作。有可能mmap()输入文件,然后执行write(1, mapped_address, input_file_size). 内核会做所有的工作。或者您可以sendfile()在 Linux 系统上的输入和输出文件描述符之间进行系统调用。传闻旧的 SunOS 4.x 系统会执行内存映射技巧,但我不知道是否有人做过基于 sendfile 的 cat。在这两种情况下,“循环”是不会发生的,因为两者write()并sendfile()要求长度与传输参数。