EOF是不是字符

编译:老齐

与本文相关的图书推荐:《Python大学实用教程》

EOF是不是字符_第1张图片

本书是面向零基础学习者的Python入门读物,包含完整的Python语法知识、针对性的练习题,本书强调学习中的实战,案例和习题均从开发实践的角度进行设计。


什么是 EOF

百度百科上这样解释:EOF是一个计算机术语,为End Of File的缩写,在操作系统中表示资料源无更多的资料可读取。资料源通常称为档案或串流。通常在文本的最后存在此字符表示资料结束。

在这个解释中,认为EOF是表示文件结束的字符——这就是本文要重点讨论的,EOF是不是一个字符?

EOF是不是字符_第2张图片

在Unix、Linux系统上,用C语言读写文件,经常会遇到EOF。之所以很多人认为EOF是一个字符串,可能是因为在C语言的程序中,会用getchar()getc()检查是否遇到了EOF。

#include 
...
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

EOF是不是字符_第3张图片

下面用一下段C语言程序,来看看EOF。在ANSI C中,EOF在标准库中,它的数字编码值一般是-1。将下面的程序保存为printeof.c,并运行:

#include 

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 "", 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)

  1. C语言程序

    /* mcat.c */
    #include 
    
    int main(int argc, char *argv[])
    {
      FILE *fp;
      int c;
    
      if ((fp = fopen(*++argv, "r")) == NULL) {
        printf("mcat: can't open %s\n", *argv);
        return 1;
      }
    
      while ((c = getc(fp)) != EOF)
        putc(c, stdout);
    
      fclose(fp);
    
      return 0;
    }
    

    编译:

    $ gcc -o mcat mcat.c
    

    执行:

    $ ./mcat helloworld.txt
    Hello world!
    
    • 此程序通过命令行参数打开一个文件
    • while循环一次一个字节地将文件中的内容复制到标准输出,一直到文件末尾
    • 如果遇到EOF,则关闭文件,并返回客户端
  2. Python 3 程序

    # mcat.py
    import sys
    
    with open(sys.argv[1]) as fin:
        while True:
            c = fin.read(1) # read max 1 char
            if c == '':     # EOF
                break
            print(c, end='')
    
    $ python mcat.py helloworld.txt
    Hello world!
    

    在Python3.8+中,还可以用海象运算符,精简程序:

    # mcat38.py
    import sys
    
    with open(sys.argv[1]) as fin:
        while (c := fin.read(1)) != '':  # read max 1 char at a time until EOF
            print(c, end='')
    
    $ python3.8 mcat38.py helloworld.txt
    Hello world!
    
  3. Go 程序

    // 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!
    
  4. JavaScript(node.js)

    /* 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条件,则返回EOread()`系统调用返回0代表EOF条件。

EOF是不是字符_第4张图片

下面把前面的C语言程序改写一下,注意观察:

/* syscat.c */
#include 
#include 
#include 
#include 

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!

至此,应该明确了一下几点:

  • EOF不是Unicode字符集中的字符
  • 在Unix/Linux系统中,文件的最后找不到所谓的EOF字符,根本就没有这样一个字符
  • EOF是程序能够检测到的Unix/Linux内核提供的一个条件

参考资料:https://ruslanspivak.com/eofnotchar/

你可能感兴趣的:(Linux,Python)