当前位置: 首页 > news >正文

Linux第18节 --- 重定向与文件IO的基本认识

一、引入一些文件的共识原理:

  1. 文件 = 内容 + 属性;
  2. 文件分为打开的文件和没被打开的文件;
  3. 打开的文件是谁打开的?(进程!)因此本质上是研究进程和文件之间的关系!
  4. 没打开的文件在哪里放着?(磁盘上!)此时我们应该关注什么问题?(没有被打开的文件非常多,文件如何被分门别类的放置好,即文件应该如何存储?---> 这样做的目的是方便我们快速的找到文件并进行 增删查改的工作!)

文件被打开,首要的是必须被加载到对应的内存当中(理想情况是内容和属性都加载,但是属性是必须的,内容如果不进行读取等操作可以不加载);

一个进程可以打开多个文件;

对文件的管理即:先描述再组织!

默认情况下,C/C++会打开三个标准流(输入,输出,错误) --- > 一个进程可以打开多个文件;

当以"w"的形式读取一个文件的时候,如果该文件不存在,会创建一个新文件;

此时创建文件的时候,默认是在当前路径下创建文件!(什么是当前路径?)

即当前该进程运行的时候,所对应的路径!

这里我们可以通过proc目录下的cwd查看当前进程的当前路径!

当前路径:当前进程的cwd路径!

如果我们想要改变当前的工作路径,可以通过系统调用接口chdir来实现!

如下所示:

此时新创建的文件就会在我们chdir指定的路径中!

接下来我们介绍两个C语言中关于文件的函数:

1、fopen函数:打开文件

fopen用于打开文件并建立文件流,其原型为:

FILE *fopen(const char *filename, const char *mode);  
  • 参数
    • filename:文件名(含路径),需注意路径中的转义字符(如C:\\file.txt需写成"C:\\\\file.txt") 。
       
    • mode:文件打开模式,决定读写权限和操作方式(见下表)。
  • 返回值:成功返回FILE指针,失败返回NULL,需通过判断返回值确保文件打开成功 。

2. 文件打开模式

  • 注意事项
    • "r"模式下文件不存在会失败,而"w""a"会自动创建文件 。
       
    • 二进制模式(b)用于非文本数据(如图片、结构体),避免编码转换问题 。

总结:w方法在写入前会对文件进行清空!

类似的,在之前我们使用的输出重定向也是以 "w"的形式进行写入的!

2、fwrite函数:写入数据 

1. 功能与原型

fwrite用于将数据块写入文件,支持二进制操作,其原型为:

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);  
  • 参数
    • ptr:待写入数据的指针(如数组、结构体地址)。
    • size:单个数据块的字节数(如sizeof(int))。
    • nmemb:数据块数量。
    • stream:由fopen返回的文件指针 。
  • 返回值:实际写入的数据块数目,若与nmemb不同可能表示错误 。

写入字符串的时候,如果写入的字符包含\0,此时就会以乱码的形式显示!

因此我们写入字符串的时候不要带\0!!!

带\0是C语言的规定,但是对于写入到文件的内容来说,是需要其他人或软件或语言来读取的,这种读取和C语言的规定没有关系!

 a方式写入是在文本末尾追加的形式写入!如果该文件不存在此时也会创建文件!

对于重定向:>是以w的方式打开         >>是以a的方式打开!

二、认识文件的系统调用

        这里,当我们的文件还没有被加载的时候,是存放在磁盘当中的,因此访问磁盘文件,实际上就是访问硬件!

        而用户如果需要访问硬件,那么只能经过操作系统;

        而操作系统不相信用户,因此只能通过系统调用接口来进行访问;

        此时的printf/fprintf/fscanf/fwrite等库函数实际上底层调用的就是系统调用接口!

上面一个函数经常用于当前文件已经存在,我们要对其进行写入;

下面一个函数经常用于当前文件不存在,此时我们要进行写入的时候需要调整其权限!

使用open的示例代码:

注意点一:如果该文件不存在,此时我们还需要 + O_CREAT这个选项; 

这时候显示的代码权限完全不正确!

这是因为在Linux中,如果我们新建文件,必须要指定文件的权限!

所以此时我们还需要第三个参数!

但是这里当我们指定权限为666的时候,最后的权限结果却为664!

这是因为还存在权限掩码!

因为我们可以调用一个系统调用接口为umask来改变当前进行的掩码!

该设置不会影响这个系统,但是会影响该进程!

open成功后返回的整数是文件描述符!

如果我们想覆盖式写入:可以采用下面这个宏:O_TRUNC;

如果我们想追加写入,可以采用下面这种形式:O_APPEND

因此此时我们可以推断出:对于上面的C语言类型的fopen,封装的系统调用一定是按照下面的形式进行的!

当一个进程打开多个文件的时候,此时需要对该文件进行组织!

即先描述再组织,这里也就是struct_file(描述已打开的文件的信息);

struct_file里面包含了该的大部分信息;

deepseek解释如下:

在Linux内核中,struct file 是表示 已打开文件 的核心数据结构,用于管理文件在进程中的动态状态。以下是其关键特性的详细解析:

  1. 基本定义
    • struct file 定义在 include/linux/fs.h 中,内核在调用 open() 打开文件时创建该结构体,关闭文件后释放 。
       
    • 核心作用
      • 跟踪文件的 打开状态(如读写位置、权限模式等)。
      • 关联文件操作函数(如 readwrite),通过 file_operations 结构体实现。
      • 存储进程与文件交互的上下文信息(如缓冲区、私有数据) 。

在进程task_struct结构体里面。包含了下面这个指针:struct files_struct *files(包含的是打开文件的信息)

 

这里的 struct files_struct *files是一个指向指针数组的指针!

struct files_struct *files指向一个表,该表称为文件描述表;

里面存放的是已经打开的文件的信息,即struct_file;

当我们调用open的时候,返回的对象是一个int类型;该返回值实际上就是文件描述符表的下标!

结论:fd的返回值本质上是数组的下标!

此时无论是write / close实际上都是对这个下标对应的空间进行管理!

右边的struct file也需要用双链表进行联系!

用户自己用的时候,默认的第一个文件描述符是3!(失败返回-1)

0,1,2在系统打开的时候,已经被默认占用了!

操作系统中,只认fd!而其他的例如stdin,stdout,stderr都是C语言规定的!本质上只是再封装!

这里我们直接往1里面进行写入,实际上效果与printf没有区别!

(直接往2里面写效果也一样!)

接下来我们再尝试fd = 0的情况:

这里我们按照读取字符串的形式来读取,保险起见将字符数组最后一个元素设置为\0,方便操作系统能识别到是字符串!(最终效果和scanf一样!)

结论:stdin,stdout,stderr不是系统的特性!是操作系统的特性!系统会默认打开键盘和显示器!

问题:C语言中的FILE实际上是什么?

答:本质是C库自己封装的一个结构体!(这个结构体里面必须包含文件描述符!!!)

这里我们可以通过->来查看对应的文件描述符!!!

_fileno实际上就是我们对应的文件描述符 

运行结果如下所示:

问题:当我们用close(1)将显示器关闭,此时再输出会出现什么结果?

此时printf无法打印正常的结果!(但是fprint会打印结果!)

printf返回值为打印的字符的个数!

fprint能打印出来的是因为其文件描述符指定的是2号!1号被关闭与它没影响!

struct file里面为了记录该文件被几个进程调用,此时会有一个变量count!

count是为了实现引用计数!

此时有几个文件描述符指向,对应的count就会有多少;

因此此时每close一个,对应的count --;

当count = 0;此时文件描述符表就会对该空间进行回收 + 数组下标置空! 

三、重定向和缓冲区

当我们尝试创建一个文件并往里面写入东西时,如果我们先将close(0),即将键盘输入关闭,此时printf输出的fd即为0!

若close(1),此时printf无法在显示器上打印!

 如果我们将2关闭,即close(2),此时printf会正常打印,且打印结果为2!

因此此时我们可以得到一下结论:

文件描述符对应的分配规则是什么?

从0下标开始,依次寻找从0开始的最小数组下标位置,它的下标就是新文件的文件描述符;

接下来我们做一个测试:close(1),然后依次往显示器中写入,看最终的结果是什么?

此时我们可以发现:最终应该显示在显示器中的文件,却写入到文件中!

本来应该写入到显示器中的文件,最后却写入到文件中!这个现象叫做输出重定向!

解释现象:因为刚开始我们就进行了close(1),因此此时创建的fd就是1!然后我们在write在1中写入也就是在fd中写入!最终写入到文件中!

整体的流程如上所示:主要是对应的文件描述符表的1号下标中的显示器替换为此时我们的log.txt!

重定向的系统调用接口 

共有dup1,dup2和dup3,其中最常用的是dup2!

 思路:此时我们仅需要将三号的对应的文件的地址覆盖到一号即可;

dup2函数

newfd最终会被oldfd所覆盖! (这里的newfd为1,oldfd为log.txt)

实际上是文件描述符对应的指针内容进行了拷贝!

这里我们通过重定向可以实现和之前一样的效果!

这里我们再将对应的fd设置为O_APPEND,即为追加重定向!

这里对于read系统调用接口:count指的是期望读取的字节格式;

返回值为实际上读取的字节个数!

输入重定向:默认从键盘读取数据,然后改成从文本中读取数据;

我们通过dup2将fd重定向到键盘中,然后此时通过read要从键盘当中读取,直接变成了从文件当中读取!

效果与我们之前通过cat实现的输入重定向一样:

问题:重定向会影响程序替换吗?

当我们进行重定向得到时候,实际上改变的都是左侧的内核数据结构!和右边的对应的进程的结构无关! 

问题:标准输出stdout和标准错误stderr有什么区别?

假设当前我们有如下所示的代码:

如果我们正常进行打印:

在显示器上两个结果都能显示;

如果我们进行重定向:

结果为:只有stdout的内容会被重定向到文件中;stderr的内容打印在显示器中; 

这是因为这里只有显示器1被重定向!显示器2没有!

接下来我们可以尝试这样子进行重定向:

./mytest 1>normal.log 2>err.log

实际上是分别对1和2进行了重定向!

我们还可以下面这种方式进行写入:

 2>&1(把1号文件描述符对应的内容写入到2号文件描述符当中!)

刚开始1已经写入到all.log当中,然后此时对应的内容写入到2当中!(2和1一样!)

2>&1 的语法解析(deepseek)

  • >:表示输出重定向。例如 > file 默认将 stdout(fd=1)重定向到文件。
  • 2>:表示将 stderr(fd=2)重定向到指定目标。
  • &1& 符号表示后面的 1 是文件描述符(而非普通文件名)。&1 指代当前 stdout 指向的位置。
     

四、如何理解一下Linux中的一切皆文件?

我们从下面往上面看:

  • 当前我们的计算机有各种不同的零件,操作系统可以对这些硬件进行描述,即struct_device,在这些结构体中主要包括硬件的输入(read)和输出(write) --- 如果有哪些硬件没有对应的方法,我们可以将其设置为空!;
  • 而struct device这一层,实际上就是驱动层!
  • 对于已打开的文件,会有一个对应的结构体struct file,而在这个结构体当中,有一个指向操作函数表的指针ops;
  • 操作函数表operation_func中包含了一些文件和设备的具体操作,与struct_device对应!

        因此当我们调用read这样的一个系统调用函数的时候,实际上是从task_struct结构体内获取到files的地址,即文件描述符表,根据对应的fd获取到对应数组位置上的指针,通过指针找到对应的struct file,而在struct file这个结构体中包含了调用函数的指针,再通过调用函数的指针ops找到对应驱动的读写方法!

相关文章:

  • 接口自动化入门 —— swagger/word/excelpdf等不同种类的接口文档理解!
  • std::any详解
  • Java实现死锁
  • 【沐渥科技】氮气柜日常如何维护?
  • nginx 打造高性能 API 网关(‌Building a High-Performance API Gateway with Nginx)
  • Deepin通过二进制方式升级部署高版本 Docker
  • 数据库系统概论(四)关系模型的数据结构及形象化
  • 【eNSP实战】交换机配置端口隔离
  • 软件IIC和硬件IIC的主要区别,用标准库举例!
  • wpf label 内容绑定界面不显示
  • MVCC实现原理
  • 【JavaWeb学习Day24】
  • 基于python下载ERA5小时尺度和月尺度的数据
  • 【Java开发指南 | 第三十四篇】IDEA没有Java Enterprise——解决方法
  • [从零开始学习JAVA] 新版本idea的数据库图形化界面
  • JVM RuntimeDataArea 成分
  • libwebsockets实现异步websocket客户端,服务端异常断开可重连
  • LangChain教程 - Agent -之 ZERO_SHOT_REACT_DESCRIPTION
  • Aliyun CTF 2025 web ezoj
  • 云原生容器编排:Kubernetes的架构演进与实践
  • 关税互降后的外贸企业:之前暂停的订单加紧发货,后续订单考验沟通谈判能力
  • 中国情怀:时代记录与家国镜相|澎湃·镜相第三届非虚构写作大赛征稿启事
  • 阿联酋与美国达成超过2000亿美元协议
  • 今年有望投产里程已近3000公里,高铁冲刺谁在“狂飙”?
  • 习近平复信中国丹麦商会负责人
  • 体坛联播|博洛尼亚时隔51年再夺意杯,皇马逆转马洛卡