文件描述符fd与重定向

发布时间:2026/7/13 14:12:01

文件描述符fd与重定向 一、文件描述符基础概念在Linux/Unix系统中每个进程都有一个文件描述符表它是一个数组用于管理进程打开的所有文件。这个数组的索引就是文件描述符File Descriptor简称fd。默认分配的三个标准文件描述符c// 标准文件描述符 0 - STDIN_FILENO // 标准输入默认是键盘 1 - STDOUT_FILENO // 标准输出默认是屏幕 2 - STDERR_FILENO // 标准错误默认是屏幕文件描述符表结构示意图进程文件描述符表 ┌─────────────────────────────────────────────┐ │ fd 0 │ → 标准输入设备 (键盘) │ ├─────────────────────────────────────────────┤ │ fd 1 │ → 标准输出设备 (屏幕) │ ├─────────────────────────────────────────────┤ │ fd 2 │ → 标准错误设备 (屏幕) │ ├─────────────────────────────────────────────┤ │ fd 3 │ → 打开的文件1 (例如: test.txt) │ ├─────────────────────────────────────────────┤ │ fd 4 │ → 打开的文件2 (例如: data.dat) │ ├─────────────────────────────────────────────┤ │ fd 5 │ → 打开的文件3 (例如: log.log) │ ├─────────────────────────────────────────────┤ │ ... │ → 更多打开的文件 │ └─────────────────────────────────────────────┘二、文件描述符分配规则核心规则最小可用原则Allocate the smallest available fd当进程打开一个新文件时系统会从文件描述符表中从小到大扫描找到第一个未被使用的位置分配给新文件。c // 演示文件描述符分配规则 #include stdio.h #include fcntl.h #include unistd.h int main() { // 显示三个标准文件描述符 printf(标准输入 fd: 0\n); printf(标准输出 fd: 1\n); printf(标准错误 fd: 2\n); // 打开第一个文件 int fd1 open(file1.txt, O_CREAT | O_RDWR, 0644); printf(打开 file1.txt分配 fd: %d\n, fd1); // 输出: 3 // 打开第二个文件 int fd2 open(file2.txt, O_CREAT | O_RDWR, 0644); printf(打开 file2.txt分配 fd: %d\n, fd2); // 输出: 4 // 关闭 fd 3 close(fd1); printf(关闭 fd: %d\n, fd1); // 再打开一个新文件会复用 fd 3 int fd3 open(file3.txt, O_CREAT | O_RDWR, 0644); printf(打开 file3.txt分配 fd: %d\n, fd3); // 输出: 3复用 return 0; }分配规则示意图text初始状态 ┌────────────┐ │ fd 0 │ 使用中 │ ← 标准输入 ├────────────┤ │ fd 1 │ 使用中 │ ← 标准输出 ├────────────┤ │ fd 2 │ 使用中 │ ← 标准错误 ├────────────┤ │ fd 3 │ 空闲 │ ← 第一个可用位置 ├────────────┤ │ fd 4 │ 空闲 │ └────────────┘ ↓ 打开新文件 ┌────────────┐ │ fd 0 │ 使用中 │ ├────────────┤ │ fd 1 │ 使用中 │ ├────────────┤ │ fd 2 │ 使用中 │ ├────────────┤ │ fd 3 │ file1 │ ← 分配到第一个空闲位置 ├────────────┤ │ fd 4 │ 空闲 │ └────────────┘ ↓ 关闭 fd 1 ┌────────────┐ │ fd 0 │ 使用中 │ ├────────────┤ │ fd 1 │ 空闲 │ ← 出现空缺 ├────────────┤ │ fd 2 │ 使用中 │ ├────────────┤ │ fd 3 │ file1 │ ├────────────┤ │ fd 4 │ 空闲 │ └────────────┘ ↓ 再打开新文件 ┌────────────┐ │ fd 0 │ 使用中 │ ├────────────┤ │ fd 1 │ file2 │ ← 填补最小的空缺 ├────────────┤ │ fd 2 │ 使用中 │ ├────────────┤ │ fd 3 │ file1 │ ├────────────┤ │ fd 4 │ 空闲 │ └────────────┘三、重定向的实现原理1. 输出重定向// 输出重定向示例 #include stdio.h #include fcntl.h #include unistd.h int main() { // 关闭标准输出fd 1 close(1); // 打开一个新文件由于 fd 1 空闲会分配到 fd 1 int fd open(output.txt, O_CREAT | O_WRONLY | O_TRUNC, 0644); printf(新文件分配到 fd: %d\n, fd); // 输出: 1 // 此时 printf 输出会写入到 output.txt printf(这条消息会写入到 output.txt而不是屏幕\n); // 刷新缓冲区 fflush(stdout); return 0; }2. 输入重定向// 输入重定向示例#include stdio.h #include fcntl.h #include unistd.h int main() { // 关闭标准输入fd 0 close(0); // 打开一个新文件会分配到 fd 0 int fd open(input.txt, O_RDONLY); printf(新文件分配到 fd: %d\n, fd); // 输出: 0 // 现在 scanf 会从 input.txt 读取数据 char buffer[100]; scanf(%s, buffer); // 从 input.txt 读取 printf(读取到: %s\n, buffer); return 0; }3. 使用 dup2 实现重定向dup2(oldfd, newfd)函数的作用将oldfd复制到newfd如果newfd已经被占用会先关闭它。#include stdio.h #include fcntl.h #include unistd.h int main() { // 打开一个文件 int fd open(output.txt, O_CREAT | O_WRONLY | O_TRUNC, 0644); printf(原文件描述符: %d\n, fd); // 使用 dup2 将 fd 复制到标准输出fd 1 dup2(fd, 1); // 现在所有写入标准输出的内容都会写入到 output.txt printf(这条消息会写入到 output.txt\n); fprintf(stdout, 这也写入到 output.txt\n); close(fd); return 0; }四、在 自己的Shell 中实现重定向代码如下#include string.h #include ctype.h // 重定向类型枚举 typedef enum { REDIR_NONE 0, // 无重定向 REDIR_OUTPUT, // 输出重定向 () REDIR_APPEND, // 追加重定向 () REDIR_INPUT // 输入重定向 () } RedirType; // 全局变量保存重定向的文件名 char *filename NULL; // 全局变量保存重定向的类型 RedirType redir_type REDIR_NONE; /** * TrimSpace - 去除字符串前导空格的宏定义 * param start: 字符指针会被修改指向第一个非空格字符 * * 说明使用 do-while(0) 包装确保宏在任何上下文中安全执行 * 宏是文本替换直接操作传入的指针变量本身 */ #define TrimSpace(start) do { \ /* 循环跳过所有前导空格包括空格、制表符、换行等 */ \ while (isspace((unsigned char)*(start))) { \ (start); \ } \ } while(0) /** * ParserRedir - 解析命令行中的重定向符号 * param commandline: 从屏幕获取的命令行字符串会被修改 * * 功能识别命令中的 、、 重定向符号 * 提取文件名并保存到全局变量 filename * 将命令部分截断重定向符号处设为字符串结束符 * * 示例 * ls -la output.txt - 命令变为 ls -la filenameoutput.txt * cat input.txt - 命令变为 cat filenameinput.txt */ void ParserRedir(char commandline[]) { // start: 当前扫描位置的指针 char *start commandline; // end: 命令行字符串的末尾指针 char *end commandline strlen(commandline); // 初始化全局变量 redir_type REDIR_NONE; filename NULL; // 遍历整个命令行字符串 while (start end) { /* 检查追加重定向 */ if (*start *(start 1) ) { // 将第一个 替换为字符串结束符截断命令部分 *start \0; start; // 移动到第二个 *start \0; // 将第二个 也替换为结束符 start; // 移动到文件名开始位置 // 跳过文件名前的空格宏会直接修改 start 指针 TrimSpace(start); // 保存文件名此时 start 指向第一个非空格字符 filename start; // 设置重定向类型为追加重定向 redir_type REDIR_APPEND; break; // 找到重定向后退出循环 } /* 检查输出重定向 */ else if (*start ) { // 将 替换为字符串结束符截断命令部分 *start \0; start; // 移动到文件名开始位置 // 跳过文件名前的空格宏会直接修改 start 指针 TrimSpace(start); // 保存文件名此时 start 指向第一个非空格字符 filename start; // 设置重定向类型为输出重定向 redir_type REDIR_OUTPUT; break; // 找到重定向后退出循环 } /* 检查输入重定向 */ else if (*start ) { // 将 替换为字符串结束符截断命令部分 *start \0; start; // 移动到文件名开始位置 // 跳过文件名前的空格宏会直接修改 start 指针 TrimSpace(start); // 保存文件名此时 start 指向第一个非空格字符 filename start; // 设置重定向类型为输入重定向 redir_type REDIR_INPUT; break; // 找到重定向后退出循环 } // 当前字符不是重定向符号继续向后扫描 start; } // 执行完函数后 // 1. commandline 已被截断只包含命令部分不含重定向和文件名 // 2. filename 指向重定向后的文件名不含前导空格 // 3. redir_type 表示重定向类型 // 4. 如果没有找到重定向符号redir_type 保持 REDIR_NONE }

相关新闻