什么是 EOF
?
百度百科上这样解释:EOF是一个计算机术语,为End Of File的缩写,在操作系统中表示资料源无更多的资料可读取。资料源通常称为档案或串流。通常在文本的最后存在此字符表示资料结束。
在这个解释中,认为EOF
是表示文件结束的字符——这就是本文要重点讨论的,EOF是不是一个字符?
在Unix、Linux系统上,用C语言读写文件,经常会遇到EOF。之所以很多人认为EOF是一个字符串,可能是因为在C语言的程序中,会用getchar()
和getc()
检查是否遇到了EOF。
#include <stdio.h>
...
while ((c = getchar()) != EOF)
putchar(c);
或者:
FILE *fp;
int c;
...
while ((c = getc(fp)) != EOF)
putc(c, stdout);
这样,getchar()
或getc()
都从输入中获取下一个字符。因此,这可能导致我们对EOF的本质感到困惑。当然,这仅仅是一种猜测。下面看看另外的理由。
什么是字符?字符可以看成是文本的最小组成党委,比如A, b, B
等都是字符。在Unicode字符集中,每个字符都对应一个数字编码,例如大写字母A
的字符编码是65(用十进制表示)。在Python 3中,可以这样查看:
>>> ord('A')
65
>>> chr(65)
'A'
或者,也可以在Unix/Linux中这样查看:
$ man ascii
下面用一下段C语言程序,来看看EOF。在ANSI C中,EOF在<stdio.h>
标准库中,它的数字编码值一般是-1
。将下面的程序保存为printeof.c
,并运行:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("EOF value on my system: %d\n", EOF);
return 0;
}
$ gcc -o printeof printeof.c
$ ./printeof
EOF value on my system: -1
在Mac OS和Ubuntu系统上测试,都是输出-1
。
那么,那个“字符”的数字编码是-1
呢?
那就用前面演示的Python中的函数,来检索一下,看看-1
对应的字符是什么。
# 在Python交互模式中
>>> chr(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: chr() arg not in range(0x110000)
没有!在ASCII字符集中没有任何一个字符的数字编码是-1
。
所以,现在可以断言:EOF不是一个字符。
再换一个角度考察。
如果EOF是字符,你就能在文件末尾“看”到它。下面检测一下文本文件helloworld.txt的内容,并且用xxd
指令输出这个文件的二进制/十六进制形式。
$ cat helloworld.txt
Hello world!
$ xxd helloworld.txt
00000000: 4865 6c6c 6f20 776f 726c 6421 0a Hello world!.
在以十六进制表示的输出内容中,此文件是以0a
结尾的,那么这个0a
是什么呢?
# Python交互模式
>>> chr(0x0a)
'\n'
事实再次说明,EOF不是字符。
它是什么?
EOF(end-of-file)是操作系统内核提供的一个条件,它可以被程序检测到。
下面我们来看一下,几种不同的编程语言在通过高级I/O接口读一个文本文件的时候,是如何检测到这条件的(用于检测的所有程序,可以从代码仓库获得:https://github.com/rspivak/2x25/tree/master/eofnotchar)
while
循环一次一个字节地将文件中的内容复制到标准输出,一直到文件末尾// mcat.go
package main
import (
"fmt"
"os"
"io"
)
func
main() {
file, err := os.Open(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "mcat: %v\n", err)
os.Exit(1)
}
buffer := make([]byte, 1) // 1-byte buffer
for {
bytesread, err := file.Read(buffer)
if err == io.EOF {
break
}
fmt.Print(string(buffer[:bytesread]))
}
file.Close()
}
$ go run mcat.go helloworld.txt
Hello world!
/* mcat.js */
const fs = require('fs');
const process = require('process');
const fileName = process.argv[2];
var readable = fs.createReadStream(fileName, {
encoding: 'utf8',
fd: null,
});
readable.on('readable', function() {
var chunk;
while ((chunk = readable.read(1)) !== null) {
process.stdout.write(chunk); /* chunk is one byte */
}
});
readable.on('end', () => {
console.log('\nEOF: There will be no more data.');
});
$ node mcat.js helloworld.txt
Hello world!
EOF: There will be no more data.
上面的示例中的高级I/O例程如何确定文件结束条件?在Linux系统上,例程直接或间接使用内核提供的read()
系统调用,例如,C语言中的getc()
使用read()
系统调用,当指示到end-of-file
条件,则返回EO。
read()`系统调用返回0代表EOF条件。
下面把前面的C语言程序改写一下,注意观察:
/* syscat.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char c;
fd = open(argv[1], O_RDONLY, 0);
while (read(fd, &c, 1) != 0)
write(STDOUT_FILENO, &c, 1);
return 0;
}
$ gcc -o syscat syscat.c
$ ./syscat helloworld.txt
Hello world!
上面的代码中,注意观察read()
函数,返回的0就代表EOF,当然,Pytyon程序也可以改写。
# syscat.py
import sys
import os
fd = os.open(sys.argv[1], os.O_RDONLY)
while True:
c = os.read(fd, 1)
if not c: # EOF
break
os.write(sys.stdout.fileno(), c)
$ python syscat.py helloworld.txt
Hello world!
Python3.8+的程序
# syscat38.py
import sys
import os
fd = os.open(sys.argv[1], os.O_RDONLY)
while c := os.read(fd, 1):
os.write(sys.stdout.fileno(), c)
# 执行结果
$ python3.8 syscat38.py helloworld.txt
Hello world!
至此,应该明确了一下几点:
参考资料:https://ruslanspivak.com/eofnotchar/