Linux基础I/O(1)

理解"文件"

狭义理解

狭义的文件,就是我们日常直观认知的、存储在磁盘等持久化存储设备上的普通文件,包含文本文件、二进制文件、图片、视频、可执行程序、压缩包等。

  • 磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的。数据写入之后,断电、重启系统数据不会丢失
  • 磁盘上的文件本质是对文件的所有操作,都是对外设的输入和输出简称 IO

广义理解

广义的文件 是Linux内核的核心抽象:Linux下一切皆文件。在系统视角中,所有能够进行「读、写、打开、关闭」操作的硬件设备、内核资源、进程通道,全部被抽象为文件,不局限于磁盘文件。(如:键盘、显示器、硬盘、网卡...)

文件操作的归类认知

  • 对于 0KB 的空文件是占用磁盘空间的
  • 文件是文件属性(元数据)和文件内容的集合(文件 = 属性(元数据)+ 内容)
  • 所有Linux文件操作,本质是操作文件的:文件内容 + 文件属性

系统角度

  • 对文件的操作,本质就是进程对文件的操作
  • 磁盘的管理者是操作系统
  • 文件的读写本质不是通过 C 语言 / C++ 的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的

回顾C 语言标准文件接口

C 文件接口是 <stdio.h> 标准库提供的用户层文件操作函数 ,是编程中最常用的文件 IO 方式,底层封装了 Linux 系统调用 ,自带用户层缓冲区,操作对象是 FILE* 文件流指针

打开文件:fopen

1. 函数原型

c 复制代码
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);

2. 功能: 打开 / 创建指定路径的文件,建立进程与文件的关联
3. 参数

  • path:文件路径
    相对路径(test.txt)/ 绝对路径(/home/test.txt)
  • mode:打开模式(字符串,决定文件的读写权限、创建 / 清空 / 追加规则)
模式 含义 文件不存在 文件已存在 读写权限
r 只读打开 报错 正常打开 只读
w 只写打开 创建新文件 清空内容 只写
a 追加只写 创建新文件 末尾追加 只写
r+ 读写打开 报错 正常打开 读写
w+ 读写打开 创建新文件 清空内容 读写
a+ 读写追加 创建新文件 末尾追加 读写

4. 返回值

  • 成功:返回 FILE* 类型的文件流指针
  • 失败:返回 NULL(必须做判断!)

示例:

c 复制代码
#include <stdio.h>
int main() {
    // 以只写方式打开文件,不存在则创建
    FILE* fp = fopen("test.txt", "w");
    // 必须判断打开是否失败
    if (fp == NULL) {
        perror("fopen error"); // 打印系统错误信息
        return 1;
    }
    // 后续读写操作...
    fclose(fp);
    return 0;
}

关闭文件:fclose

1. 函数原型

c 复制代码
#include <stdio.h>
int fclose(FILE *stream);

2. 核心功能

  • 刷新用户层缓冲区 :把 FILE 结构体中缓存的未写入数据,强制同步到文件 / 硬件
  • 释放 FILE 结构体占用的内存;
  • 底层自动调用系统调用 close,释放文件描述符;
  • 断开进程与文件的关联。

3. 返回值

  • 成功:返回 0
  • 失败:返回 EOF(-1),并设置错误码

示例:

c 复制代码
#include <stdio.h>
int main() {
    FILE* fp = fopen("test.txt", "w");
    if(fp == NULL) {
        perror("fopen failed");
        return 1;
    }

    // 写入数据(先存入缓冲区)
    fprintf(fp, "Hello World");

    // 必须关闭:刷新缓冲区 + 释放资源
    fclose(fp);
    // 关闭后,fp 成为野指针,不能再使用!
    return 0;
}

写文件

依赖头文件:#include <stdio.h>

1. fprintf:格式化写入

c 复制代码
int fprintf(FILE *stream, const char *format, ...);
  • 功能 :按照指定格式(和printf完全一致)将数据写入文件流
  • 参数 :
    stream:目标文件流(FILE*,可以是文件 /stdout/stderr)
    format:格式化字符串(%d/%s/%f等)
  • 特点:支持格式化输出,适合文本、日志、配置文件写入
  • 代码示例
c 复制代码
// 写入数字、字符串到文件
fprintf(fp, "ID: %d, Name: %s\n", 1001, "Linux");
// 写入屏幕(标准输出)
fprintf(stdout, "Hello World\n");

2. fputs:纯字符串写入

c 复制代码
int fputs(const char *str, FILE *stream);
  • 功能 :将纯字符串写入文件流,不自动添加换行符
  • 代码示例
c 复制代码
fputs("I/O Study\n", fp);
fputs("Print to Screen\n", stdout);

3. fwrite:二进制批量写入

c 复制代码
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能 :批量写入二进制数据(结构体、数组、图片、音频等),不做数据转换
  • 参数 :
    ptr:数据内存地址
    size:单个数据大小
    nmemb:数据个数
  • 特点:效率最高,适合二进制文件、批量数据
  • 代码示例
c 复制代码
// 写入结构体
struct Student { int id; char name[10]; };
struct Student s = {1001, "Tom"};
fwrite(&s, sizeof(struct Student), 1, fp);

读文件

依赖头文件:#include <stdio.h>

1. fgets:按行读取

c 复制代码
char *fgets(char *buf, int size, FILE *stream);
  • 功能 :从文件流中一次读取一行数据 ,自动在末尾补 \0(字符串结束符)
  • 参数 :
    buf:存放读取数据的内存缓冲区
    size:缓冲区最大容量(最多读 size-1 字节)
    stream:文件流(文件 /stdin)
  • 返回值 :
    成功:返回 buf 地址
    读到文件末尾 / 失败:返回 NULL
  • 代码示例
c 复制代码
char buf[1024];
// 按行读取文件
while(fgets(buf, sizeof(buf), fp) != NULL) {
    printf("%s", buf); // 打印读取内容
}

2. fscanf:格式化读取(对应 fprintf)

c 复制代码
int fscanf(FILE *stream, const char *format, ...);
  • 功能 :按照指定格式(和 scanf 一致)从文件中解析数据
  • 适用场景:读取结构化文本(日志、配置、数字 + 字符串组合)
  • 返回值 :成功返回匹配到的参数个数 ,读到末尾返回 EOF
  • 代码示例
c 复制代码
int id;
char name[20];
// 从文件读取格式化数据:ID:1001 Name:Tom
fscanf(fp, "ID:%d Name:%s", &id, name);

3. fread:二进制批量读取(对应 fwrite)

c 复制代码
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能 :批量读取二进制数据(结构体、图片、音频、数组),不做任何格式转换;
  • 参数 :
    ptr:存储数据的内存地址;
    size:单个数据单元大小;
    nmemb:期望读取的单元数量;
  • 返回值 :返回实际读取的单元个数
  • 代码示例
c 复制代码
struct Student { int id; char name[10]; };
struct Student s;
// 读取二进制结构体
fread(&s, sizeof(struct Student), 1, fp);

标准输入输出 stdin、stdout、stderr

  • 本质 :stdin/stdout/stderr 是 C 标准 IO 提供的 FILE* 类型指针 ,对应 Linux 内核的 3 个默认文件描述符
  • 来源:任何进程启动时,操作系统内核会自动为进程打开这 3 个广义文件 ,无需手动 open/fopen
  • 作用 :进程与外界(键盘 / 显示器)交互的默认 IO 通道
  • 底层 :所有标准输入输出,最终都通过 read/write 系统调用实现
C 标准流指针 文件描述符 fd 名称 默认硬件设备 读写权限 缓冲类型 核心作用
stdin 0 标准输入 键盘 只读 行缓冲 进程从键盘读取数据
stdout 1 标准输出 显示器 只写 行缓冲 进程向屏幕打印正常信息
stderr 2 标准错误 显示器(和 stdout 同一个硬件,但独立通道) 只写 无缓冲 进程向屏幕打印错误信息

系统文件 IO

系统文件 IO 是 Linux 操作系统提供的原生底层文件操作接口 ,也叫 Linux 系统调用IO,是所有文件操作的最终实现者

系统 IO 只有 4 个基础函数,对应打开、关闭、读、写,所有文件操作都围绕它们展开

打开文件:open函数

两个函数原型

1. 不创建文件(仅打开已有文件)

c 复制代码
int open(const char *pathname, int flags);

2. 创建新文件(文件不存在则创建)

c 复制代码
int open(const char *pathname, int flags, mode_t mode);

必须包含的头文件

c 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>  // open 专属头文件

参数详解
参数 1:pathname → 文件路径

  • 作用:指定要打开 / 创建的文件路径
  • 支持格式:
    相对路径 :"test.txt"(当前程序所在目录)
    绝对路径 :"/home/study/test.txt"(系统完整路径)

参数 2:flags → 打开标志位
flags 是位掩码 ,用 按位或 | 拼接多个功能,分为两类:

第一类:必选标志

定义文件基础读写权限,必须选且只能选 1 个:

宏定义 含义 说明
O_RDONLY 只读打开 只能读,不能写
O_WRONLY 只写打开 只能写,不能读
O_RDWR 读写打开 可读可写

第二类:可选标志(任意叠加,用 | 拼接)

宏定义 含义 核心场景
O_CREAT 文件不存在则创建 新建文件必备
O_TRUNC 打开时清空文件原有内容 覆盖写入(对应 w 模式)
O_APPEND 追加写入(永远在文件末尾写) 日志追加(对应 a 模式)

参数 3:mode → 文件权限(仅创建文件时必填)

  • 触发条件 :只有 flags 包含 O_CREAT,才需要传这个参数
  • 格式要求 :必须是 8 进制数字 (以 0 开头!)
  • 最常用权限 :0664
  • 权限规则 :实际权限 = 指定的 mode & ~系统umask(默认 umask=0002)

返回值:文件描述符 fd

  • 成功 :返回 非负整数
    内核默认分配:0(stdin)、1(stdout)、2(stderr)
    手动打开的文件,fd 从 3 开始分配(最小空闲下标)
  • 失败 :返回 -1,同时自动设置错误码

示例 1:创建新文件

c 复制代码
#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>                     
#include <sys/stat.h>                      
#include <fcntl.h>                         
                                           
int main()                                 
{              
    int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);      
    if(fd < 0)                             
    {                                      
        perror("open");                    
        return 1;                          
    }                                      
                                           
    printf("fd: %d\n", fd);                
    
    close(fd);                                                                                                                     
    return 0;    
}

示例 2:覆盖写入(清空文件)

c 复制代码
int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);

补充:传递标志位flags的方法

  • Linux 里给 open 传 flags 标志位,底层就是标准的位图传参思想 ,也叫位掩码传参
  • flags = 一个 32 位的整数位图
  • 位图 = 用一个整数的每一个二进制位,单独代表一个独立的开关状态
    某一位为 1 → 开启对应功能
    某一位为 0 → 关闭对应功能

代码示例:

c 复制代码
#include <stdio.h>    
    
#define ONE_FLAG (1<<0) // 0000 0000 0000...0000 0001    
#define TWO_FLAG (1<<1) // 0000 0000 0000...0000 0010    
#define THREE_FLAG (1<<2) // 0000 0000 0000...0000 0100    
#define FOUR_FLAG (1<<3) // 0000 0000 0000...0000 1000    
    
void Print(int flags)    
{    
    if(flags & ONE_FLAG)    
    {    
        printf("One!\n");    
    }    
    if(flags & TWO_FLAG)    
    {    
        printf("Two!\n");    
    }    
    if(flags & THREE_FLAG)    
    {    
        printf("Three!\n");    
    }    
    if(flags & FOUR_FLAG)    
    {    
        printf("Four!\n");    
    }    
}    
    
int main()    
{    
    Print(ONE_FLAG);    
    printf("\n");    
    Print(ONE_FLAG | TWO_FLAG);    
    printf("\n");    
    Print(ONE_FLAG | TWO_FLAG | THREE_FLAG);    
    printf("\n");    
    Print(ONE_FLAG | TWO_FLAG | THREE_FLAG | FOUR_FLAG);    
    printf("\n");    
    Print(ONE_FLAG | FOUR_FLAG);                                                                                                                     
    printf("\n");    
    return 0;
}

关闭文件:close函数

必须包含的头文件

c 复制代码
#include <unistd.h>  // close 系统调用专属头文件

函数原型

c 复制代码
int close(int fd);
  • 参数 :int fd → 要关闭的文件描述符 (open 返回的整数)
  • 返回值 :int 类型(成功 / 失败标识)
  • 作用:释放文件描述符,减少内核资源引用计数,断开进程与文件的绑定

写文件:write

依赖头文件

c 复制代码
#include <unistd.h>   // write / read / close 系统调用专属

函数原型

c 复制代码
ssize_t write(int fd, const void *buf, size_t count);
  • 返回值:ssize_t(有符号整型,代表实际写入的字节数)
  • 参数:文件描述符、数据缓冲区、期望写入长度

参数详解

1. int fd → 文件描述符

  • 来源:open 函数成功打开文件后返回的整数
  • 作用:进程操作文件的唯一凭证 ,内核通过 fd 找到目标文件

2. const void *buf → 数据缓冲区

  • 作用:要写入的数据在进程内存中的起始地址
  • 类型:void* 通用指针,支持任意类型数据(字符串、结构体、二进制数据)

3. size_t count → 期望写入的字节数

  • 作用:你想写入多少字节的数据
  • 限制:不能超过缓冲区的实际大小,否则会导致内存越界

返回值
write 的返回值不是固定等于 count

  • > 0 :成功写入的字节数(可能 < count)
  • = -1 :写入失败(权限不足、fd 无效、磁盘满等)
  • = 0 :未写入任何数据(极少出现)

示例 1:覆盖写入(清空文件,从头写)

c 复制代码
#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
int main()    
{    
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);      
    if(fd < 0)    
    {    
        perror("open");    
        return 1;    
    }    
    
    printf("fd: %d\n", fd);    
    
    const char *msg = "hello world\n";    
    int cnt = 5;    
    while(cnt)    
    {    
        // 当作字符来写    
        write(fd, msg, strlen(msg));                                                                                                           
        cnt--;    
    }    
    
    close(fd);    
    return 0;    
}

示例 2:追加写入(不清空,末尾写)

只需要修改open的标志:

c 复制代码
int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);

读文件:read函数

必须包含的头文件

c 复制代码
#include <unistd.h>   // read 系统调用专属

函数原型

c 复制代码
ssize_t read(int fd, void *buf, size_t count);
  • 返回值 :ssize_t(有符号整型,代表实际读取到的字节数)
  • 参数:文件描述符、数据存储缓冲区、期望读取的最大长度

参数详解

1. int fd → 文件描述符

  • 来源:open 函数成功打开文件后返回的整数
  • 硬性要求 :必须以 O_RDONLY / O_RDWR 模式打开

2. void *buf → 读取缓冲区

  • 作用:进程内存的一块空间,用来存放从文件读取到的数据
  • 类型:void* 通用指针,支持存储字符串、结构体、二进制数据

3. size_t count → 期望读取的最大字节数

  • 作用:你最多想读多少字节的数据
  • 限制:不能超过缓冲区的实际大小,否则会造成内存越界

返回值

  • > 0 :实际读取到的字节数(成功)
  • = 0 :读到文件末尾(EOF)
  • = -1 :读取失败(无效 fd、权限不足、磁盘错误)

示例:

c 复制代码
#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
int main()    
{    
    int fd = open("log.txt", O_RDONLY);    
    if(fd < 0)    
    {    
        perror("open");    
        return 1;    
    }    
    
    printf("fd: %d\n", fd);    
    
    while(1)    
    {    
        char buffer[64];    
        int n = read(fd, buffer, sizeof(buffer)-1);    
        if(n > 0)    
        {    
            buffer[n] = 0;    
            printf("%s", buffer);    
        }    
        else if(n == 0)    
        {    
            break;    
        }    
    }
    close(fd);
    return 0;
}

文件描述符

文件描述符 = 一个非负整数 = 进程内核空间中「文件描述符表」(struct files_struct)的数组下标

默认分配的 3 个 fd:0,1,2

任何进程启动时,内核都会自动打开 3 个标准文件,分配固定 fd:

文件描述符 fd 名称 宏定义 默认设备 作用
0 标准输入 stdin 键盘 读取数据
1 标准输出 stdout 显示器 打印正常信息
2 标准错误 stderr 显示器 打印错误信息

示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    printf("stdin: %d\n", stdin->_fileno);
    printf("stdout: %d\n", stdout->_fileno);
    printf("stderr: %d\n", stderr->_fileno);

    printf("\n\n");

    umask(0);
    int fd1 = open("log1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    int fd2 = open("log2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    int fd3 = open("log3.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    int fd4 = open("log4.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if(fd1 < 0) exit(1);
    if(fd2 < 0) exit(1);
    if(fd3 < 0) exit(1);
    if(fd4 < 0) exit(1);

    printf("fd1: %d\n", fd1); 
    printf("fd2: %d\n", fd2); 
    printf("fd3: %d\n", fd3); 
    printf("fd4: %d\n", fd4); 

    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    
    return 0;
}

完整串联:task_struct → files_struct → fd_array → fd → struct file → 文件缓冲区

整体层级关系(从上到下)

bash 复制代码
进程控制块 task_struct
        ↓ 内部成员:struct files_struct *files
文件描述符表 struct files_struct
        ↓ 内部成员:struct file *fd_array[]
文件描述符数组 fd_array[]
        ↓ 数组下标 = 文件描述符 fd
数组元素:struct file * 指针
        ↓ 指向
打开文件实例 struct file
        ↓ 关联
内核文件缓冲区 / inode 磁盘文件

1. 最顶层:task_struct 进程结构体

Linux 内核中,每一个进程都唯一对应一个 task_struct,它是进程的全部描述信息。里面记录进程 PID、运行状态、内存资源、信号、文件资源等所有属性。

核心关联成员:

c 复制代码
struct task_struct {
    // ... 其他大量成员
    struct files_struct *files;  // 指向该进程专属的文件描述符表
};

含义:每个进程通过 files 指针,找到自己独有的文件管理表,进程之间文件资源完全隔离

2. 第二层:struct files_struct 文件描述符表
files_struct 是专门用来统一管理当前进程所有已打开文件的结构体,一个进程仅有一份。

核心内部成员:

c 复制代码
struct files_struct {
    // ...
    struct file *fd_array[];  // 核心:文件指针数组
    // ...
};

它的作用就是托管 fd_array 数组,统筹该进程所有文件描述符

3. 第三层:fd_array \[\] 文件指针数组
fd_array 本质是一个存放 struct file* 类型的指针数组

  • 数组下标 :就是我们代码里使用的 文件描述符 fd
  • 数组元素 :存放指向内核 struct file 结构体的地址

4. 第四层:struct file 打开文件结构体
进程每调用一次 open 打开文件,Linux 内核就会在内核内存中,创建一个全新独立的 struct file 结构体

核心意义

  1. 同一个磁盘文件,多次 open 就会创建多个 struct file
  2. 每一个 struct file 代表一次独立的打开状态,相互独立互不干扰

struct file 核心成员

c 复制代码
struct file {
    loff_t f_pos;         // 文件读写偏移量(最核心)
    unsigned int f_flags; // 保存open传入的flags标志位
    atomic_t f_count;     // 引用计数
    struct inode *f_inode;// 指向磁盘真实文件inode
    // 关联内核读写缓冲区
};

同时该结构体直接对接内核文件缓冲区

5. 文件缓冲区 关联关系

  1. 用户进程调用 read/write 时,不会直接读写磁盘
  2. 依靠 struct file 找到对应的内核页高速缓冲区
  3. 写流程:用户空间数据 → 内核文件缓冲区 → 异步刷入磁盘
  4. 读流程:磁盘数据预读到内核缓冲区 → 拷贝到用户进程缓冲区
  5. struct file 负责维护本次打开操作对应的缓冲区读写位置与权限

文件描述符的分配规则

文件描述符的分配原则:最小未被使用的下标优先分配

示例:

c 复制代码
#include <stdio.h>    
#include <stdlib.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>

int main()    
{
	close(0);
	//close(2);    
    
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);    
    printf("fd: %d\n", fd);
	return 0;
}

发现是结果是: fd: 0 或者 fd: 2 ,可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

如果我们将代码改为 close(1),我们会发现本来输出到显示器上的内容,输出到了文件log.txt当中。其中,fd=1。这种现象叫做输出重定向。

重定向的本质

重定向的本质 = 偷换标准文件描述符的指向

重定向类型 操作 fd 本质(指针替换)
输出重定向 > 1 fd=1:显示器 → 文件
输入重定向 < 0 fd=0:键盘 → 文件

追加重定向的本质 = 标准输出重定向 + 追加写入模式(O_APPEND),fd 指向修改逻辑完全相同,仅文件写入规则不同

dup2 系统调用

dup2 ------ 它是 Linux 实现重定向最标准、最底层的系统调用 ,也是命令行 >/>> 重定向的真正底层实现 (shell 内部就是用 dup2 做重定向,而非手动 close+open)

核心作用:强制让 newfd 成为 oldfd 的副本,两个文件描述符指向同一个内核 struct file 结构体;如果 newfd 已被占用,内核会自动先关闭它 ,全程一步完成

函数原型 & 头文件

c 复制代码
#include <unistd.h>

// 函数原型
int dup2(int oldfd, int newfd);

参数解释

  • oldfd:源文件描述符 (必须是有效、已打开的 fd,比如我们打开文件得到的 3)
  • newfd:目标文件描述符 (我们强制指定的 fd,比如标准输出 1、标准输入 0)

返回值

  • 成功 :返回 newfd(和你指定的目标 fd 一致)
  • 失败 :返回 -1(比如 oldfd 无效)

示例1:用 dup2 实现标准输出重定向

c 复制代码
#include <stdio.h>    
#include <stdlib.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
int main()    
{    
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);    
    //int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if(fd < 0) exit(1);    
    
    dup2(fd, 1);    
    
    // 默认向显示器输出    
    printf("fd: %d\n", fd);    
    printf("hello world\n");      
    fprintf(stdout, "hello stdout\n");    
    close(fd);
    return 0;
}

示例2:用 dup2 实现标准输入重定向

c 复制代码
#include <stdio.h>    
#include <stdlib.h>    
#include <string.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
int main()    
{    
    int fd = open("log.txt", O_RDONLY);    
    if(fd < 0) exit(1);    
    
    dup2(fd, 0);    
    close(fd);    
    
    while(1)    
    {    
        char buffer[64];    
        if(!fgets(buffer,sizeof(buffer), stdin)) break;                                                                                              
    
        printf("%s", buffer);    
    }
    return 0;
}

在minishell中添加重定向功能

总流程

  1. 解析命令行:分离「命令参数」和「重定向信息(类型 + 文件名)」
  2. fork 子进程
  3. 子进程核心 :打开文件 → dup2 重定向 → 关闭多余 fd → exec 执行命令
  4. 父进程:等待子进程结束,不做任何重定向操作

完整代码:

略

补充:标准错误

前置回顾

  • 进程启动,内核自动打开 3 个独立文件描述符:
    fd=0(输入)、fd=1(标准输出)、fd=2(标准错误)
  • 重定向 > 的完整写法是 1>,只修改 fd=1 的指向

标准错误的本质

标准错误 = 文件描述符 fd=2

  • 它是进程 fd_array[] 数组的下标 2
  • 对应一个独立的 struct file 内核结构体(和 fd=1 完全分开)
  • 默认指向显示器(和 fd=1 终点相同,但管道独立)

只重定向标准错误用法

bash 复制代码
./a.out 2> log.error

先看一个现象:

我们可以发现:只重定向了 fd=1 :将 fd_array1 从显示器 → log.txt,fd=2 没有任何修改:依旧指向显示器

那如果我们要stderr和stdout打印到同一个文件呢?

bash 复制代码
./a.out 1> log.txt 2>&1
  • 1> log.txt:完整的标准输出重定向
  • 2>&1:把 fd=2 重定向到 fd=1 当前指向的位置

为什么要存在标准错误?

1. 重定向隔离

  • 正常输出(fd=1)可以重定向到文件
  • 错误信息(fd=2)必须留在屏幕,让你立刻看到报错

2. 日志分离

可以把正常日志 和错误日志 分开存:

示例:

相关推荐
且从容.42 分钟前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道1 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
讯捷蓝达3 小时前
戴尔R720xd服务器卡死在 “Configuring Memory...Done“,换了内存还是卡?问题根本不在内存
运维·服务器
且从容.4 小时前
ubuntu安装flink
linux·ubuntu·flink
数字新视界5 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
爱和冰阔落6 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨6 小时前
WSL2的安装与使用
linux·wsl
微小的鱼6 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng6 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
Android系统攻城狮7 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频