news 2026/10/17 14:29:57

【基础IO】-4-再谈重定向

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【基础IO】-4-再谈重定向

再谈重定向

  • 1、实验中的有趣现象
  • 2、重新理解重定向
  • 3、直接完成重定向的系统调用——dup
  • 4、完善自主shell——加入重定向操作

1、实验中的有趣现象

还记得我们验证文件描述符fd分配规则的实验吗?我们当时是关闭了0号文件流stdin,然后新建文件,发现新建文件的fd是0。

假如我们关闭fd == 1的文件呢?

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>intmain(){close(1);// 关闭stdoutintfd=open("log.txt",O_CREAT|O_WRONLY,0666);printf("log.txt: %d\n",fd);fflush(stdout);close(fd);return0;}






本该写入到屏幕上的信息,却写入到了新建的log.txt中——这不就是输出重定向吗?

所以,我们通过关闭fd == 1的文件,再新建文件,然后用printf企图向标准输出流中做写入,结果内容写入到了文件log.txt,从而间接地完成了输出重定向的工作。

2、重新理解重定向

操作系统是只认文件描述符fd的,文件操作相关的系统调用几乎都需要fd的参与。

标准输出流stdout,底层封装的文件描述符fd是fd == 1。未来我们想要对标准输出流做写入,默认是写入到文件描述符为fd == 1的文件中的。







现在关闭了fd == 1的文件,即关闭了stdout标准输出流。然后我们新建文件log.txt,打开log.txt返回的文件描述符就是fd == 1。

而想要继续向标准输出流做写入的时候,由于操作系统只认fd == 1的文件流为标准输出流,所以就会去打开文件表fd_array中找下标为1的文件,结果找到了log.txt。

以上就是输出重定向的全过程。输出重定向的本质是:

  • 做标准输出的时候,进程只会写入到文件描述符fd == 1的文件当中,默认情况下是stdout标准输出流;
  • (打开文件表)fd_array中下标为 1 的文件,由stdout变为了用户指定的文件。

同理,追加输出重定向的本质与输出重定向的非常相似。它们的唯一的区别是:

  • 输出重定向,会清空历史内容;
  • 追加输出重定向,不会清空,只会在文件内容的末尾继续写入。

类似的,输入重定向的本质是:

  • 做标准读取的时候,进程只会读取文件描述符fd == 0的文件,默认情况下是stdin标准输入流;
  • (打开文件表)fd_array中下标为 1 的文件,由stdin变为了用户指定的文件。

3、直接完成重定向的系统调用——dup

重定向操作,本质上是(打开文件表)fd_array中file*指针的交换,即文件描述符fd作为fd_array数组的下标,对应的file*指针,的交换。

如果我们采用上面的“关闭文件再打开”的方式,显得比较麻烦。Linux为我们提供了一个操作系统——dup。




intdup2(intoldfd,intnewfd);



系统调用dup的作用是:

  • 关闭当前newfd指向的文件;
  • 将oldfd上的file*指针,拷贝给newfd。

所以输出重定向,可以这样用dup实现:

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>intmain(intargc,char*argv[]){intfd=open("log.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);// 不追加是O_TRUNC(清空)dup2(fd,1);printf("%s\n",argv[1]);close(fd);return0;}



追加输出重定向,可以这样用dup实现:

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>intmain(intargc,char*argv[]){intfd=open("log.txt",O_CREAT|O_WRONLY|O_APPEND,0666);// 追加是O_APPENDdup2(fd,1);printf("%s\n",argv[1]);close(fd);return0;}



输入重定向,可以这样用dup实现:

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>intmain(){intfd=open("log.txt",O_RDONLY,0666);dup2(fd,0);inta=0;floatf=0.0f;charc=0;scanf("%d %f %c",&a,&f,&c);printf("%d %f %c\n",a,f,c);close(fd);return0;}



学会使用dup之后,我们就可以继续完善我们自主实现的shell,即加上重定向操作。

4、完善自主shell——加入重定向操作

注意我们当前加入的重定向操作,是只针对普通命令,即需要bash创建子进程执行的命令,的重定向操作。

往自主shell中加入重定向操作,需要经过以下两个步骤:

  1. 完善执行命令函数Execute。
    • 定义全局的标识符redir_type,表示重定向操作的具体选项:
      • 不重定向#define NONE_REDIR 0
      • 输入重定向#define IN_REDIR 1
      • 输出重定向#define OUT_REDIR 2
      • 追加输出重定向#define NONE_REDIR 3
    • 以后在Execute中,检测redir_type发生的变化:
      • 若redir_type == 0,不做任何操作
      • 若redir_type == 1,检测到需要做输入重定向,stdin流换成指定文件流dup2(fd, 0)
      • 若redir_type == 2,检测到需要做输出重定向,stdout流换成指定文件流dup2(fd, 1)
      • 若redir_type == 3,检测到需要做追加输出重定向,stdout流换成指定文件流dup2(fd, 1)
  2. 添加函数Redir,对从用户方读取的命令字符串做预处理:
    • 看看字符串中有没有< > >>,以设置redir_type
    • 定义全局的重定向文件名redir_filename,提取字符串中重定向操作符右侧的文件名写入redir_filename



// Execute中创建子进程的部分elseif(0==id){intfd=0;if(IN_REDIR==redir_type){fd=open(redir_filename,O_RDONLY);dup2(fd,0);}elseif(OUT_REDIR==redir_type){fd=open(redir_filename,O_CREAT|O_WRONLY|O_TRUNC,0666);dup2(fd,1);}elseif(APP_REDIR==redir_type){fd=open(redir_filename,O_CREAT|O_WRONLY|O_APPEND,0666);dup2(fd,1);}else{// do nothing}execvp(argv[0],argv);exit(12);}



voidBash(){staticchar*env[256];_environ=env;LoadEnv();while(1){PrintPrompt();GetCommandLine();if(0==strlen(commandline))continue;Redir();// 获取用户输入后,先对可能包含的重定向操作做处理,再分析命令ParseCommandLine();if(CheckBuildinAndExecute())continue;Execute();}}



staticvoidRedir(){char*start=commandline;char*end=&commandline[strlen(commandline)+1];// 左闭右开while(start!=end){if('>'==*start){if('>'==*(start+1)){redir_type=APP_REDIR;*start='\0';start+=2;}else{redir_type=OUT_REDIR;*start='\0';start++;}Clear_Left_Space(start);// 宏替换的技巧:1. 直接写不利于观察。2. 写成函数,要传二级指针// 过滤空格,找到重定向的文件名redir_filename=start;break;}elseif('<'==*start){redir_type=IN_REDIR;*start='\0';start++;Clear_Left_Space(start);redir_filename=start;break;}else{++start;}}}



处理重定向操作的函数Redir就很有讲究。

首先是寻找重定向操作符,设置redir_type。定义char*指针start遍历输入字符串,

  • 遇到'<',就是输入重定向
  • 遇到'>',
    • 如果start下一位也是'>',那么就是追加输出重定向
    • 否则就只是输出重定向

设置完redir_type之后,我们可以将重定向操作符改写成'\0',这样做是对用户输入字符串做“切割”,切出操作符之前的命令(这样后续在解析函数ParseCommandLine中,strtok遇到\0就会停下)。

再然后,就要提取redir_filename。我们可以使start跳过操作符,继续向后找文件名。而操作符之后,

  • 可能直接就是文件名
  • 也可能是许多前导空格

所以我们需要做一个循环操作,过滤掉空格,

  • 直接写循环吗?太冗余了,代码看起来不太简洁。而且这个循环操作以后可能还要用
  • 外部设计成一个函数吗?那就要传递二级指针,才不会因为传值拷贝而不能对start进行修改

这里就涉及到一个编程技巧:将此处的循环操作设计成一个宏(替换)。




#defineClear_Left_Space(pos)do{\while(isspace(*pos))\++pos;\}while(0)



宏的本质就是替换,代码在预处理阶段就会用这段循环操作替换Clear_Left_Space;同时do{}while(0)中套while循环,可以防止宏函数直接跟;造成的实际循环未执行的情况发生。

当start找到了一个非空的字符,意味着找到了文件名。就可以设置redir_filename。

添加重定向操作,还有一个重要的一点:一定要在下一次输入命令之前,重置redir_type redir_filename!







不然会发生意想不到的问题。

代码演示

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 14:24:45

软考数据库系统工程师大纲:职业能力的三维体检报告

简介&#xff1a;本资源为软考中级「数据库系统工程师」考试官方大纲文档&#xff0c;专为备考第四版教材的考生设计&#xff0c;覆盖数据库系统设计、开发与运维全能力维度。文件为单个110KB的Word文档&#xff08;.docx&#xff09;&#xff0c;结构清晰、层级完整&#xff0…

作者头像 李华
网站建设 2026/10/17 14:24:21

多智能体系统六大工程流派拆解:从群聊游戏到生产级协作架构

1. 从“群聊游戏”到工程化落地&#xff1a;多智能体到底在解决什么问题过去一年里&#xff0c;我身边不少做AI应用的朋友都经历过一个相似的阶段&#xff1a;兴致勃勃地拉起几个大模型&#xff0c;给它们分配角色&#xff0c;让它们在一个对话框里互相聊天&#xff0c;试图用“…

作者头像 李华
网站建设 2026/10/17 14:24:12

无经验转行大模型:QLoRA微调+vLLM部署全流程实战拆解

无经验转行大模型&#xff0c;我只刷了一个行业落地项目就拿了3家offer&#xff0c;QLoRAvLLM全流程拆给你看 先交代一下背景&#xff1a;我转行前不是做AI的&#xff0c;代码底子一般&#xff0c;深度学习更是只听过名字。决定转大模型方向的时候&#xff0c;身边人给的建议基…

作者头像 李华
网站建设 2026/10/17 14:23:58

算子级仿真如何统一训练与推理:Charon 的共享底座设计

1. 为什么训练和推理的仿真总是各管各的做系统方向的人大概都有过这种体验&#xff1a;手头有一个训练性能模型&#xff0c;又有一个推理性能模型&#xff0c;两者用的抽象层级不一样&#xff0c;参数标定方式不一样&#xff0c;甚至连"一个算子耗时多久"这种最基础的…

作者头像 李华
网站建设 2026/10/17 14:22:58

多模态信号与迁移学习驱动的柱塞泵故障诊断实战

柱塞泵这东西&#xff0c;听起来像是机械课本里才会出现的名词&#xff0c;但干过液压设备维保的人都知道&#xff0c;它就是整个液压系统的“心脏”。泵头一坏&#xff0c;常见的故障表现就是压力上不去、噪声变大、系统抖动&#xff0c;严重的时候直接憋坏整条液压回路&#…

作者头像 李华
网站建设 2026/10/17 14:22:27

从AlexNet到ResNet:用服装分类理清CNN进化史与PyTorch实现对比

1. 先把这个项目的主线理清楚&#xff1a;为什么是AlexNet到ResNet 1.1 我为什么选择用服装分类来串联CNN进化史 如果你刚开始学深度学习&#xff0c;你一定会被各种名词轰炸&#xff1a;卷积核、池化、感受野、残差连接、BatchNorm……这些概念单独拎出来每一个都能讲半小时&…

作者头像 李华