【Linux】文件描述符、文件操作、重定向的模拟实习

news2024/12/23 0:14:13

目录

一、重温C语言文件操作

1.1 文件打开方式

1.2 文件写操作

1.3 文件读操作

1.3 标准输入输出

二、系统接口的使用

2.1 open 函数

2.2 close 函数

2.3 write 函数

2.4 read 函数

三、文件描述符

3.1 如何管理文件

3.2 0 & 1 & 2

3.3 文件描述符的分配规则

3.4 FILE

3.5 重定向

3.6 dup2 函数


 

一、重温C语言文件操作

本节我们的内容是学习Linux中的系统调用——相关的文件操作

首先我们来回忆一下C语言中的文件操作。

为什么学习系统的文件操作我们要先重温一下C语言的文件操作呢?

答: 因为当我们编写程序访问磁盘上的文件,本质上的过程其实是这样的:

620e8b392d5e4878a24d366655dbbebe.png

 

本质上文件并不是我们访问的,而是进程访问的。因为当我们调用语言的文件操作,其内部封装了对应的系统调用,当程序运行起来时,就会去调用系统调用,从而访问文件。

所有的跨平台编程语言,内部的文件操作都是封装了对应系统的文件操作系统调用,所以我们先复习一下C语言的文件操作,然后再来复习对应的文件系统调用操作。

1.1 文件打开方式

看一下这三种常见的打开方式。8ecc067d7cd144a9b3e2c80462f72fe8.png

 当前路径:即一个进程运行起来时,其当前所处的工作路径。

1.2 文件写操作

//fwrite
//第三个参数为要写出数据 基本单元 的个数
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
//fprintf
int fprintf(FILE *stream, const char *format, ...);
//fputs
int fputs ( const char * str, FILE * stream );
//fputc
int fputc ( char character, FILE * stream );

接下来看一下使用示例:

855b30cfa3e74d7cb6673441ff3e3d90.png

 运行效果如下:

888bd01851144fe4995d8b5d44888b6c.png

 这里有一个问题:写文件时,要不要将'\0'进行写入?

不用,因为字符串结尾带上 '\0' 是C语言的字符串规定,而不是操作系统中文件的规定,所以我们不需要将'\0'写入,而 '\n' 可以写入,因为这可以使文本进行换行。

再重温一下如何使用shell脚本向文本文件中写入内容(重定向)

93442faa136d4b5f8da4a3db4a3bb985.png

 发现,之前写入的内容都被删除了,那我们可以这样操作来实现清空文本内容。

d867e2780e7d4d0cbd498bfcd6a0c6fa.png

1.3 文件读操作

重温了写操作后,接下来实现一下读操作。

代码如下,就是将打开方式从 w 变成 r 。

 f31e8f15ced8465eb5868215f59f9dc8.png

 结果如下:

e2375ab8b09f417e95fc32bea488729e.png

 有了读操作,其实我们就可以实现一下Linux中的 cat 命令。

实现方式就是将传入的命令行参数作为参数以读的方式打开,然后进行打印。

代码如下:

53ac0de824634b1a90e33fb24260051f.png

这样,cat命令就实现了。 效果如下:

9683116588b547d38e26c50f98af78a6.png

1.3 标准输入输出

不难发现,我上面使用了这样的代码

fprintf(stdout,"%s",line);

这其中的 stdout 是什么呢?

我们平时使用的 printf 函数要包含头文件<stdio>,其实 std 表示 standard(标准),而io则是读写。所以我们平时经常引入的该头文件就是用于我们进行文件读写的。而stdin、stdout、stderr是C语言默认打开的三个文件,称为标准输入输出流,其对应的是硬件设施。

后面会进行更详细的讲解。

  • stdin ---> 键盘
  • stdout ---> 显示器
  • stderr ---> 显示器

二、系统接口的使用

其实上面使用的fopen、fclose、fwrite、fgets都是调用的系统接口。

8630df4995504ff6876d3d2b9c6ab63a.png

2.1 open 函数

首先我们来看看 open 接口 

4dc5cb9e5bba4dd0a095a3b494f240cc.png

第一个参数是文件路径;

第二个参数是以为位图形式传入标志位作为其函数参数,一个 int 有32位,所以理论上最多可以传入32个选项,每一个选项对应一格比特位。

光是文字的讲解非常费劲,我们直接用代码来形象地解释位图的形式。

#include <stdio.h>
#include <unistd.h>
#include <string.h>

#define ONE 0x1
#define TWO 0x2
#define THREE 0x4

void show(int flags) //0000 0111;
{
	if (flags & ONE) printf("hello one\n");
	if (flags & TWO) printf("hello two \n");
	if (flags & THREE) printf("hello three\n");
}
int main()
{
	show(ONE);
	show(TWO);
	show(ONE | TWO);
	show(ONE | TWO | THREE);
	return 0;
}

让我们来观察结果:

9aee079681994ca9b6a79b8759292a8d.png

 有了上面位图的理解,接下来我们来看一下 open 函数第二个参数的选项。

Flags:

    O_RDONLY 只读打开         O_WRONLY 只写打开         O_RDWR  可读可写打开

    当我们附带了权限后,打开的文件就只能按照这种权限来操作。

    以上这三个常数中应当只指定一 个。下列常数是可选择的:     

  • O_CREAT--->如果目标文件不存在则创建文件,存在则无影响。
  • O_TRUNC---->清空内容
  • O_APPEND---->追加

fopen函数中的 a 和 w 打开方式的区别就在于一个清空(trunc),一个追加(append)。

返回值:
成功:新打开的 文件描述符
失败:-1

上面就是其中常用的一些选项,我们来尝试使用 open 函数以写的形式打开文件 log2.txt。

2db982cd25a646c6a96f198dcd401292.png

 结果运行如下:

7fe1f655f9284c7d9a5b66889839e2f4.png

为什么打开文件失败了呢?

因为 fopen 是C语言的文件访问操作,虽然它本质是调用的open函数,但是其内部封装了许多的选项,但是我们没有感知。所以,如果我们想模拟 fopen 函数中的只写的方式打开,应该再添加选项,即文件存在则创建文件的 O_CREAT.如下图:

369de5795a4c41ae97e9e6cae2aee2df.png

打开结果:

 a7aceb4a55ab4880a62f51c9076ce88e.png

 好的,文件就成功创建出来了,但是我们发现创建出的文件权限有点问题。

接下来我们看一下 open 的第三个参数——mode。 mode 是被创建文件的权限,我们可以对其进行设置。

e8cd992b6c2949ad850039fb3bfda787.png

 文件权限如下:

ea8ac490c0674957b76b11c243a0ece3.png

 发现文件的权限发生的变化,但是不是我们想要的0666,即rw-rw-rw-。因为系统中存在默认的umask,所以我们想在创建文件时,将umaks清空,这时我们可以调用系统接口 umask。

9e5dc6fc12cb42c286288727555ae814.png

 umask接口使用方式如下:

2a1afe3b244346e5a3092f4cfb640acc.png

但是,这还不够,在C语言中,w方式打开文件会进行删除文件原本的内容,而a方式打开文件会在文本的末尾进行写入。这两种方式的区别就在于我们还差一个选项没有加入:

a和w的区别就在于一个清空(O_TRUNC)原本内容,和一个进行追加(O_APPEND)内容。

2.2 close 函数

上面介绍了 open 函数,接下来就是 close 函数的介绍

0eabcf8ca6114153bc87c938d4809a87.png

使用非常简单,将 fd 传入即可。

c9f880eaa90e45648a8773e4936beca9.png

2.3 write 函数

介绍了打开、关闭文件,接下来就是 write 函数,其对应就是C语言中的fwirite、fprintf、fputs、fputc等等。

354097caa3e646be93872d4b5772bf1f.png

使用举例:

3512232be35b4cd38624045f7794a9b6.png

效果如下:

cde688ffd9064bcda28242a31987f93b.png

2.4 read 函数

接下来就是以读的方式打开,然后我们进行读文件。

6e437e7815f747f8956051f6d79ca199.png

参数:

  • 第一个参数为读入文件的fd。
  • 将读入的数据存放到 buf 中
  • count 则为期望多少个字符

返回值:

        实际读到的字符个数

使用举例如下:(因为read不会为读入的字符串末尾添加\0,所以我们使用memset先将字符串全初始化为'\0')

066609b529414d4bb8ba88d2b416063b.png

 效果如下:

d9e44a278e324cd5b62404a6172446fb.png

三、文件描述符

3.1 如何管理文件

在linux认为,一切皆文件,站在系统的角度,能够被input读取,或者能够被output写出的设备就叫做文件。

  • 侠义的文件:普通的磁盘文件
  • 广义的文件:显示器、键盘、网卡、声卡、显卡、磁盘,几乎所有的外设,都可以称之为文件

上面我们使用open、write、close、read,都传入了 fd( flie descriptor ) 这个参数,

通过上面的学习,我们知道了文件描述符就是一个小整数。

文件本质是被进程进行访问的,进程访问文件必须先打开文件,所以:

一个进程:打卡的文件= 1:n  ---> 如果多个进程都打开了自己的文件,则系统中会存在大量被打开的文件。所以,OS要将各个进程所打开的文件进行管理,创建struct file的对象,该对象中存放着文件的所有内容(不仅仅包含属性),如果存在多个被打开的文件,再使用双链表进行连接起来。

d77ace0f3813423cb6b98a58f8fe0a19.png

9234d2d71d3149b390c9eb4c87d49bcc.png

 

其中进程控制块 task_struct 是这样控制对应的文件

ffd3f899ef244a05913cbf529381f85b.png

3.2 0 & 1 & 2

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0(stdin),标准输出1(stdout),标准错误2(stderr)。
  • 0,1,2对应的物理设备一般是:键盘、显示器、显示器。

所以我们其实可以直接从使用 fd 这个整数,在默认打开的文件 1 (屏幕)上进行输出。

ea2c2bc062e9492f9334b611b05881c0.png

 运行结果如下:

a1c84067908d4ca099dbe2bcc4eba6f2.png

3.3 文件描述符的分配规则

文件描述符的规则是有规律的,因为其默认会打开0、1、2三个文件。

其分配规则是优先最小正整数进行分配,我们来看代码:

47af98a9461044dbacc1c929478f7ea2.png

 运行结果:

13eb961fb192475da7259c1d2feee639.png

3.4 FILE

学习了文件描述符,那我们再想一下C语言中的 FILE 是什么呢?

FILE是一个结构体,其内部封装了文件描述符 fd ,所以我们在C语言中使用FILE同样能访问文件,本质就是其内部封装了文件描述符。

所以,像stdin、stdout、strerr这些C语言定义的文件结构体,内部就封装了文件描述符fd,接下来我们就来验证一下,直接打印出FILE结构体内的文件描述符是多少。

4d52454ac0be44f982e4a87e225b3bad.png

结果如下:

71699d30637542ac89b24803790f7134.png

3.5 重定向

我们已知道 stdout 的 fd 为 1,如果我们将本来要输出到显示器上的内容输出到了文件中,是不是就是输出重定向的原理?

重定向的本质,其实是在OS内部,更改fd对应的内容的指向!

d6aa17979b62433eb894a50f1894923f.png

接下来让我们看一段代码(关闭为什么使用fflush后面会详聊):

539de160a6a141b6a4a74c8a229700fa.png

结果如下:

b9a850c1474f43d1b75ab953dec14306.png

 即:输入重定向的实现就是将原本从键盘输入的内容从文件中进行输入。

73c2d5764844439cb37c88c865b2ac35.png

结果如下:

f7288be2df44458183068c825bc46227.png

 所以,追加重定向就是将open函数中的选项O_TRUNC(内容清空)改为O_APPEND(追加)。

fa33a286c484434b8eb49cbd7ee643bf.png

效果如下:

0a8a670bbae940f6ae90c1d5e8954452.png

虽说我们实现了重定向,但是,上面这种写法,并不是实现实现重定向的主流写法,操作系统早为我们提供了一个系统调用来实现 fd 的替换。

3.6 dup2 函数

bd91effe5d464ebbb92b9240ed1c0205.png

这个函数的大概功能是:在文件描述符表,将 old 文件的 fd 拷贝到 new 文件的fd处,是将文件描述符表中 new 处拷贝为 old 的信息,即 new 和 old 都指向 old 指向的文件。

113be769519c4f7d8b42d8ee25372200.png

即,我们已经打开了log.txt,其文件描述符表中对应下标为3。我们想让下标为1处存放的结构体指针指向的不再是显示器,而是指向下标为3指向的文件。所以我们下标为3处的内容拷贝到下标为1数组中。即dup2(3,1).

  • oldfd  copy to newfd ---> 最后与oldfd一致
  • 3  的内容拷贝到  1   ---> 最后与3一致

 92a8901a59d3448bacf0f77a005115e0.png

 好的,接下来举例使用一下dup2接口(因为dup2是系统调用接口,不需再用fflush)。

d3337a38ebfe4cdb91830aadda6b2e65.png

 效果如下:

cbda7fa23cb24a53931b75356c2cce0d.png

好的,本篇博客就到此结束了,这里留了一个坑,为什么我们对文件描述符表进行替换时,什么时候使用fflush,什么时候不用使用呢?

因为篇幅过长,而且这个问题比较复杂,所以这个问题将在下篇博客进行详细讲解。但是这个问题不太影响本篇博客的思路与实现,大家放心观看。

这篇博客介绍的文件描述符以及文件操作在Linux和C语言中非常重要,也希望大家认真阅读。下期再见!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

种草!超好用的PDF转换器上线啦~

宝子们 重磅福利来啦 你还在为每次转换文件头疼吗 老铁&#xff0c;大拿版万能转换器正式上线啦 以前的文件转换器&#xff0c;不是充会员就是收费高 最坑的是花钱还解决不了问题 每次转换文件内容有误.... 特殊符号或者公式更是无法有效转换 为了整顿这种局面&#xff0c…

KKT条件理解

我们知道拉格朗日函数是用于等式约束的优化问题求解的&#xff0c;然KKT条件是针对含有不等式约束的优化问题的。 首先&#xff0c;我们先给出优化目标&#xff1a; 因此&#xff0c;根据优化目标&#xff0c;我们同样可以构造处拉格朗日函数&#xff0c;并对其进行优化&#…

ssh免密登录

准备两台linux主机 主机A&#xff1a;192.168.92.131 主机B&#xff1a;192.168.92.132 使用主机B去免密访问主机A 在主机B上执行 ssh-keygen -t rsa ssh-keygen 生成密码对 -t rsa 指定生成 rsa 密钥对 密钥对文件默认放在 家目录下面的 .ssh 目录下 root 用户默认放在 /ro…

一、数据库开发与实战专栏导学及数据库基础概念入门

文章目录一、专栏导学1.1 课程内容1.2 学习安排1.3 适合人群1.4 学习方法二、认识数据库2.1 生活中的数据库2.2 数据管理技术的3个发展阶段2.3 数据库、关系型数据库、非关系型数据库概念2.4 为什么要使用数据库2.5 数据库系统及其组成部分2.6 常用数据库访问接口简介2.7 数据库…

【2】Go语言的语法

一、Go语言基础组成 Go语言基础组成&#xff1a; 包申明引入包函数变量语句&表达式注释实例&#xff1a; package main //申明包 import "fmt" /* 这是一个朴实无华的注释 */ func main() { fmt.Printf("mogu") } 实现流程&#xff1a; 第一行&#…

kafka简介

目录 partition和consumer group offset的管理 kafka的事务 幂等producer 事务producer 怎么理解trasactional.id 两阶段2pc简介 kafka的消息传输保证 producer端 broker端 consumer端 消息挤压 kafak的存储 kafka的高性能 附录-kafka demo kafaka的发布-订阅模…

Rust之常用集合(三):哈希映射(Hash Map)

开发环境 Windows 10Rust 1.66.0VS Code 1.74.2项目工程 这里继续沿用上次工程rust-demo 在哈希图中存储带有关联值的键 我们常见的集合中的最后一个是哈希映射。HashMap<K, V>类型使用散列函数存储K类型的键到V类型的值的映射&#xff0c;这决定了它如何将这些键和值…

Vue2.0

JavaScript&#xff08;JS 教程&#xff09; - JavaScript | MDN Vue是构建用户界面的 JavaScript 框架 安装 — 2.0 Vue.jsGitHub - vuejs/devtools: ⚙️ Browser devtools extension for debugging Vue.js applications.Installation | Vue DevtoolsVSCode插件&#xff1a…

【SpringMVC】SpringMVC整合Mybatis

1.整合思路 第一步&#xff1a;整合dao层 mybatis和spring整合&#xff0c;通过spring管理mapper接口使用mapper的扫描自动扫描mapper接口在spring中进行注册 第二步&#xff1a;整合service层 通过spring管理service接口使用配置方式将service接口配置在spring配置文件中实现…

如何能有兴趣的编代码,而不是畏难?

如果我告诉你&#xff0c;成功做出一道代码题拿下5万美元&#xff0c;做出四道代码题20万美金的年薪到手&#xff0c;你是不是会立刻发愤图强呢&#xff1f;这不是个段子&#xff0c;是北美程序员的面试情况&#xff1a;有小伙伴刷题的过程中觉得刷不下去了&#xff0c;就以此来…

【Javassist】快速入门系列10 当检测到instanceof表达式时用代码块替换

系列文章目录 01 在方法体的开头或结尾插入代码 02 使用Javassist实现方法执行时间统计 03 使用Javassist实现方法异常处理 04 使用Javassist更改整个方法体 05 当有指定方法调用时替换方法调用的内容 06 当有构造方法调用时替换方法调用的内容 07 当检测到字段被访问时使用语…

Android核心技术——Jetpack Hilt依赖注入

依赖注入是什么 个人理解&#xff1a;把有依赖关系的类放在容器中&#xff0c;解析这些类的实例&#xff0c;并在运行时注入到对应的字段中&#xff0c;就是依赖注入&#xff0c;目的是为了类的解耦 例子&#xff1a;A 类 中用到了 B 类&#xff0c;一般情况下需要在 A 类中 …

Promise:工作流程、常见API、使用方法、手撕Promise、async/await

Promise和axios一、Promise的常见骚操作0.初体验1.使用Promise封装原生AJAX2.Promise实例对象的两个属性&#xff08;1&#xff09;状态属性PromiseState&#xff08;2&#xff09;结果值属性PromiseResult3.Promise的工作流程4.Promise的API&#xff08;1&#xff09;.then和.…

ceph--理论

分布式存储--------Ceph 前言&#xff1a;随着OpenStack的快速发展&#xff0c;给Ceph的发展注入了强心剂&#xff0c;越来越多的人使用Ceph作为OpenStack的底层共享存储&#xff0c;Ceph在中国的社区也蓬勃发展起来。近两年OpenStack火爆度不及当年&#xff0c;借助于云原生尤…

SoringBoot+VUE前后端分离项目学习笔记 - 【01 环境配置以及VUE2集成ElementUI】

技术栈一览 SpringBoot2 Vue2 ElementUI Axios Hutool Mysql Echarts 所需软件环境 版本一览 JDK 1.8Mysql5.7Node 14.16.0navicatIdea 2021 Vue-cli 安装 npm install -g vue/cli 查看版本 创建VUE工程 初始化工程 vue create vue 选择Manually select feature…

【MySQL】数据库索引 - 浅谈索引类型

索引类型可以分为哈希表、有序数组和 N 叉树 不管是哈希还是有序数组&#xff0c;或者 N 叉树&#xff0c;它们都是基于其自身数据结构的特性来提高读写速度。在 NoSQL 里面还运用到了 LSM 树&#xff0c;来提高写的速度&#xff0c;还有跳表等数据结构来进行优化。 不过需要…

数据结构与算法-java

什么是数组&#xff1f; &#xff08;1&#xff09;数组是计算机中最基本的数据结构之一&#xff0c;我们会用一些名为索引的数字来标识每项数据在数组中的位置。 &#xff08;2&#xff09;大多数编程语言中索引是从0开始的。 &#xff08;3&#xff09;数组在内存中是存在连续…

如何打造一个流式数据湖

Flink将数据写入到 hudi 准备阶段 启动hadoop集群&#xff08;单机模式&#xff09; ./sbin/start-all.shhdfs离开安全模式 hdfs dfsadmin -safemode leave启动hive 后台启动元数据 ./hive --service metastore &启动hiveserver2 ./hiveserver2 &执行sql语句之前…

fpga实操训练(ip rom)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 altera的fpga本身自带了rom的ip&#xff0c;使用起来也十分方便。实际开发中&#xff0c;使用rom的场景也很多&#xff0c;比如一些默认的配置文件…

TensorFlow之回归模型-2

1 基本概念 回归模型 线性 线性模型 非线性模型 线性回归 逻辑回归 Log Loss&#xff08;损失函数&#xff09; 分类临界值 2 效率预测 回归问题是预测一个持续的值&#xff0c;主要是用于解决不确定性的问题&#xff0c;例如&#xff0c;一个商品在未来可能的价格或…