read
。这个函数是与 open
和 write
配合使用,用于从文件描述符指向的文件中读取数据。
1. 函数介绍 Link to heading
read
是一个 Linux 系统调用,它的主要作用是从指定的文件描述符(file descriptor)所关联的文件、管道、套接字等输入流中读取数据,并将其存放到程序提供的缓冲区中。你可以把它想象成从一个“数据源”里“拿出”一些字节放到你自己的“篮子”(缓冲区)里。
2. 函数原型 Link to heading
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
3. 功能 Link to heading
尝试从文件描述符 fd
指向的文件或资源中读取最多 count
个字节的数据,并将这些数据存储到从 buf
指向的内存地址开始的缓冲区中。
4. 参数 Link to heading
int fd
: 这是文件描述符 (file descriptor)。它是一个非负整数,由open
、creat
、socket
等系统调用成功执行后返回,用以标识一个已打开的文件或资源。void *buf
: 这是一个指向缓冲区的指针。读取到的数据将被存放到从这个地址开始的内存空间中。你需要确保这个缓冲区至少有count
个字节的空间。size_t count
: 这是你希望读取的最大字节数。函数不会读取超过count
个字节的数据。
5. 返回值 Link to heading
read
函数返回实际读取到的字节数,这个数可能小于或等于请求的 count
。
- 成功时:
- 返回实际读取到的字节数 (0 <= 返回值 <=
count
)。 - 如果返回值为 0,通常表示已经到达文件末尾 (End Of File, EOF) 或者没有更多数据可读(例如从管道或网络套接字读取时对端已关闭)。
- 返回实际读取到的字节数 (0 <= 返回值 <=
- 出错时:
- 返回 -1,并设置全局变量
errno
来指示具体的错误类型(例如EAGAIN
、EBADF
、EFAULT
等)。
- 返回 -1,并设置全局变量
6. 相似函数,或关联函数 Link to heading
pread
: 类似于read
,但它允许你在一次调用中同时指定要读取的文件描述符、缓冲区、读取字节数以及文件内的偏移量。它不会改变文件的当前偏移量。readv
: 允许你将数据读入到多个不连续的缓冲区(一个iovec
数组)中,这在处理结构化数据时很有用。write
: 与read
相反,用于将数据写入到文件描述符指向的文件或资源中。open
: 通常在调用read
之前使用,用来获取文件描述符。
7. 示例代码 Link to heading
示例 1:从普通文件读取内容 Link to heading
#include <unistd.h> // read, write, close
#include <fcntl.h> // open, O_RDONLY
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit
#include <errno.h> // errno
#define BUFFER_SIZE 1024
int main() {
int fd; // 文件描述符
char buffer[BUFFER_SIZE]; // 用于存储读取数据的缓冲区
ssize_t bytes_read; // 实际读取的字节数
// 1. 打开一个文件以供读取 (假设当前目录下有名为 "example.txt" 的文件)
fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("Error opening file"); // 打印错误信息
exit(EXIT_FAILURE);
}
printf("File opened successfully with fd: %d\n", fd);
// 2. 循环读取文件内容,直到文件末尾
while ((bytes_read = read(fd, buffer, BUFFER_SIZE)) > 0) {
// 3. 将读取到的内容写入标准输出 (屏幕)
// 注意:这里没有处理 write 可能只写入部分数据的情况,
// 在实际应用中可能需要循环确保所有数据都被写入。
if (write(STDOUT_FILENO, buffer, bytes_read) != bytes_read) {
perror("Error writing to stdout");
close(fd); // 出错也要记得关闭文件
exit(EXIT_FAILURE);
}
// 可选:打印本次读取了多少字节
// printf("Read %zd bytes\n", bytes_read);
}
// 4. 检查 read 是否因错误而返回
if (bytes_read == -1) {
perror("Error reading file");
close(fd);
exit(EXIT_FAILURE);
}
// 5. 关闭文件描述符
if (close(fd) == -1) {
perror("Error closing file");
exit(EXIT_FAILURE);
}
printf("\nFinished reading file.\n");
return 0;
}
代码解释:
- 定义了缓冲区大小
BUFFER_SIZE
。 - 使用
open()
以只读模式 (O_RDONLY
) 打开名为example.txt
的文件。如果失败,perror()
会打印具体错误原因,程序退出。 - 进入
while
循环,调用read(fd, buffer, BUFFER_SIZE)
。它会尝试读取最多 1024 个字节。 - 如果
read
返回大于 0 的值,说明读到了数据。然后使用write()
将这些数据写到标准输出 (STDOUT_FILENO
,其值为 1)。 - 循环继续,直到
read
返回 0(EOF)。 - 检查
read
是否返回 -1(错误)。 - 最后,使用
close()
关闭文件描述符,释放资源。
示例 2:从标准输入读取一行 Link to heading
这个例子演示如何从键盘(标准输入)读取用户输入的一行文本。
#include <unistd.h> // read
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit
#define MAX_INPUT_SIZE 256
int main() {
char input_buffer[MAX_INPUT_SIZE];
ssize_t bytes_read;
int i;
printf("Enter a line of text (max %d chars): ", MAX_INPUT_SIZE - 1);
// 从标准输入 (STDIN_FILENO=0) 读取数据
bytes_read = read(STDIN_FILENO, input_buffer, MAX_INPUT_SIZE - 1);
if (bytes_read == -1) {
perror("Error reading from stdin");
exit(EXIT_FAILURE);
} else if (bytes_read == 0) {
printf("No data read (EOF on stdin?)\n");
} else {
// 寻找换行符 \n,将其替换为字符串结束符 \0
// 这样可以方便地将输入作为 C 字符串处理
for (i = 0; i < bytes_read; ++i) {
if (input_buffer[i] == '\n') {
input_buffer[i] = '\0';
break;
}
}
// 如果没有找到 \n (例如输入达到缓冲区最大限制),手动添加 \0
if (i == bytes_read) {
input_buffer[bytes_read] = '\0';
}
printf("You entered: %s\n", input_buffer);
// 注意:实际读取的字符数可能比 strlen(input_buffer) 多1(因为 \n 被替换了)
printf("Number of bytes read (including \\n if present): %zd\n", bytes_read);
}
return 0;
}
代码解释:
- 提示用户输入。
- 调用
read(STDIN_FILENO, ...)
从键盘读取最多MAX_INPUT_SIZE - 1
个字符(留一个位置给可能添加的\0
)。 - 检查返回值:-1 表示错误,0 表示 EOF,正数表示读取的字节数。
- 为了方便打印,代码查找读入的缓冲区中的换行符
\n
并将其替换为字符串结束符\0
。 - 使用
printf
打印处理后的字符串和实际读取的字节数。
理解 read
函数的关键在于掌握文件描述符的概念以及如何处理其返回值(特别是区分读到数据、到达文件末尾和发生错误的情况)。