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/stderrC 标准 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)
    手动打开的文件,fd3 开始分配(最小空闲下标)
  • 失败 :返回 -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 里给 openflags 标志位,底层就是标准的位图传参思想 ,也叫位掩码传参
  • 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_array[1] 从显示器 → log.txt,fd=2 没有任何修改:依旧指向显示器

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

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

为什么要存在标准错误?

1. 重定向隔离

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

2. 日志分离

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

示例:

相关推荐
阳光九叶草LXGZXJ2 小时前
达梦数据库-堆栈看问题-01-asmapi_asm_extent_load
linux·运维·数据库·sql·学习
tedcloud1232 小时前
agent-skills部署教程:打造工程化AI Agent系统
服务器·人工智能·系统架构·powerpoint·dreamweaver
Ujimatsu2 小时前
虚拟机安装openSUSE 16.0及其常用软件(2026.5)
linux·运维·服务器
你的保护色2 小时前
ensp之STP、RSTP、MSTP协议实验
java·服务器·数据库
minji...2 小时前
Linux 网络基础之网络IP层(十)IP 协议,网段划分,IP地址相关问题
linux·运维·服务器·网络·tcp/ip·智能路由器·php
IT瑞先生2 小时前
运维专题3——业务进程排查方法论
运维·网络
枳实-叶2 小时前
【Linux驱动开发】第10天:设备树零基础入门——DTS/DTB/DTC全解+编译流程
linux·运维·驱动开发
枳实-叶3 小时前
【Linux驱动开发】第11天:设备树(Device Tree)超详细全解:从诞生背景到工作原理
linux·运维·驱动开发
IceSugarJJ3 小时前
Windows下VSCode+ WSL项目启动流程
linux·windows·vscode·ubuntu·wsl