linux文件与重定向

目录

一、共识原理

二、回顾C语言文件函数

1.fopen

2.fwrite

 3.fclose

三、文件系统调用

1.open

2.write 

3.访问文件的本质

4.stdin&&stdout&&stderror

5.文件的引用计数 

 四、重定向

1.文件描述符的分配规则

2. 输出重定向

3.重定向系统调用

4.追加重定向

5.输入重定向 

6.1号VS2号


一、共识原理

1.文件=内容 + 属性

我们关注文件,不仅要关注它的内容,也要关心它的属性,一个文件即使没有内容,它的大小也不是空的,因为该文件的属性也是会占用空间的。

2.文件分为打开的文件和没打开的文件

3.打开的文件

谁在打开文件?我们在代码中写一个fopen,fwrite,最终都会变成进程,因此是进程在打开文件。

研究打开的文件---本质是研究进程和文件的关系!文件被打开,必须被加载到内存!内容和属性都被加载到内存。

进程:打开的文件 = 1 : n。一个进程是可以打开多个文件的,因此进程和打开的文件关系是1:n的

操作系统内部,一定存在大量的被打开的文件!OS要不要管理这些打开的文件呢?-怎么管理???-先描述在组织,在内核中,一个被打开的文件都必须有自己的文件打开对象,包含文件的很多属性。

4.没打开的文件:在哪里放着呢?在磁盘上。我们最关注什么问题?没有被打开的文件非常多,我怎么找到我要打开的文件?因此文件必须被分门别类的归置好---方便我们快速的进行增删查改---快速找到文件。

二、回顾C语言文件函数

1.fopen

fopen的第一个参数是文件名,如果不带路径,默认就在当前路径下打开,如果带了绝对路径,就在绝对路径下打开,第二个参数是以什么方式打开,是读呢?还是写呢?还是追加写?下面我们打开了"log.txt"这个文件,在当前路径下是没有log.txt这个文件的,fopen如果打开不存在的文件会新建在打开,是以读方式打开。果然在我们的当前路径下创建了一个log.txt文件。

下面的问题是,当前路径,什么是当前路径呢?当前路径就是进程的当前路径,如果我们把进程的当前路径修改了,是不是就可以把文件新建到其它目录下呢?那怎么修改进程的当前路径呢?chdir。

可以看到,当把 进程的当前工作目录修改了之后,创建的文件log.txt的路径也随之发生了变化。

2.fwrite

fwrite的第一个参数是写入内容的起始地址,第二个参数是写多少个,第三个是当做一个几部分写入,第四个是要写入那个文件里。

 

这里我们直接把message当做一个整体写入到fp里,也就是log.txt文件里,这里有一个问题就是strlen(message)需要+1吗?strlen求字符串长度,求到'\0'就结束了,也就是说如果+1就是把'\0'也写入到文件里,这里需要吗?答案是不需要,因为'\0'是C语言的要求,C语言不知道字符串从哪里结束才要'\0',但是和我文件有啥关系? 

运行一下,在查看log.txt里的内容,发现果然hello world被写入到文件中了。

下面我们给log.txt里面多加几个2字符,然后在运行一下。

我们发现原来的内容全部没有了,这说明"w"方式,在写入之前,都会对文件进行清空处理!然后再从头开始写。

这个>重定向,是不是就是把log.txt打开,然后以w的方式把"hello world" 写进去呀?因此,我们>前面不加任何东西就是以w方式打开log.txt文件但是不写任何内容,此时就把log.txt清空了。

那如果我们就想要追加写呢?可以以"a"的方式打开,a在文件结尾,追加写

 3.fclose

如果我们把一个文件使用完毕了,就需要使用fclose关闭一下这个文件。它的使用非常简单,把打开的文件指针传进去即可。

下面就有一个问题了,文件是在磁盘上的,磁盘是外部设备,我们上述的fwrite,fopen,包括fclose,其实实在访问硬件,那我们用户能直接访问硬件吗?不能,操作系统不相信任何人,我们要访问硬件,必须通过操作系统提供的系统调用,因此,我们上述写的库函数,一定要封装系统调用!什么printf/fscanf/fwrite/fread......这些库函数,都是封装了系统调用的。下面我们就学习一下这些文件相关的系统调用。

三、文件系统调用

1.open

给我们提供了两个打开文件的系统调用,我们只要学会下面那个参数多的即可,参数少的是参数多的一子集。

第一个参数是要打开的文件路径,如果没有带路径,默认就是进程的当前路径。第二个参数是以什么方式打开,第三个参数是文件的权限,可以设置创建文件的权限。

返回值如果失败返回<0的数,成功,返回>0的数。

我们先来以只读方式打开,只读方式打开传递O_WRONLY这个宏即可。

我们发现我们直接打开文件失败了,这是为啥呢?这是因为如果打开的文件不存在,并不会给新建,因此此时要在传递一个宏O_CREAT, 表示如果文件不存在就创建。此时我们可以看到就创建了log.txt文件。

但是这里细心的同学会发现有问题,就是,我明明文件权限设置的是666呀,文件权限应该是rw-rw-rw,但这里却是rw-rw-r--,不是666而是664,这是因为权限掩码。如果你说我就要创建权限是666的文件呢?可以,有设置掩码的函数umask,直接把掩码设成0即可。 

题外话:open的第二给参数是一个整数呀,可是我们给他传递了O_WRONLYO_CREAT两个选项,是咋做到的呢? 其实就是简单的位运算。

#include "stdio.h"
#include "string.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define ONE (1 << 0) //1
#define TWO (1 << 1) //2
#define THREE (1 << 2)//4
#define FOUR (1 << 3) // 8

void show(int flags)
{
    if(flags & ONE) printf("hello funcion1\n");
    if(flags & TWO) printf("hello funcion2\n");
    if(flags & THREE) printf("hello funcion3\n");
    if(flags & FOUR) printf("hello funcion4\n");
}


int main()
{

    show(ONE);
    printf("----------------------------\n");

    show(TWO);
    printf("----------------------------\n");

    show(ONE | TWO);
    printf("----------------------------\n");

    show(TWO | THREE);
    printf("----------------------------\n");

    show(ONE | THREE | FOUR);
    printf("----------------------------\n");

    return 0;
}

通过位运算,我们就通过给1给标记位传递不同的值,让它表示多种信息。

2.write 

第一个参数,就是对应文件的id,就是open的返回值,第二个参数是要写入的起始位置,第三个参数是写入的长度。

 下面运行一下,看下结果。我们运行了两次,打印出来的都是一样的,之后我手动往log.txt里面加了一串b,在运行,发现,那一串b并没有被清空,因此,我们可以发现写入的时候是从头覆盖写的,但是并不会对文件做清空处理。

那如果我也想做到清空呢? 我们需要在打开文件的时候在加一个宏,O_TRUNC,截断的意思,就是每次打开文件都做清空。

如果我想做到如同C语言中的a一样,追加写呢?还有一个宏,O_APPEND。如果带了O_APPEND就要把O_TRUNC去掉追加和清空是冲突的。

我们现在用的都是系统调用,fopen,就是用open封装的,"w"方式就会被转化成O_WRONLY | O_CAEAT  | O_TRUNC.

这都没问题,但是有一个东西我们一直没谈,就是open的返回值可是一个int啊,但是fopen的返回值是一个FILE类型的指针,这两个玩意八竿子都打不着,有啥关系呢?下面我们就要谈谈文件的管理了。

3.访问文件的本质

操作系统里会有很多个进程,每个进程可能要打开很多个文件,这些被打开的文件要不要被被管理起来呢?要,先描述在组织。操作系统用struct file描述一个被打开的文件信息,struct file里应该包含什么呢?1.文件在磁盘的什么位置 2.文件的基本属性(权限,大小,读写位置,谁打开的...)3.文件的内核缓冲区,总之,这个结构体里包含了文件的大部分信息,类似的,struct file里还有一个strcut* next指针,每打开一个文件,内核创建一个struct file,然后用strcut* next指针链接到一起,此时操作系统要对文件进行增删查改就是对文件链表的增删查改,如果要添加一个文件,就在文件链表里插入,如果要关闭文件,就是把文件的所有属性释放掉,从链表删除,再把数据刷新到磁盘上。

一个进程可能打开多个文件,那些文件是被那个进程打开的呢?我怎么知道,所以,必然要建立进程PCB和打开文件struct file的对应关系。

那怎么建立的呢?在进程PCB里会存在一个指针,struct file_struct *f;这个指针指向struct file_struct结构体这个结构体里面会包含一个数组,数组的名字叫做 struct file* fd_arrdy[],这个数组显然是个指针数组,这个数组的下标从0开始,数组每个元素的类型是 struct file*,所以,当我们打开一个文件的时候,操作系统会创建好struct file,然后在这个数组里分配一个下标,把创建好的struct file的地址,填到这个下标上,以后,每个进程就可以根据这个文件描述符表,就能把打开的文件找到。

所以,为啥open的返回值是个整数呢?open会创建一个struct file,然后在当前进程的文件描述符表里找一个没有用过的下标,把创建的struct file的地址填进去,然后把这个数组下标返回给用户,因此,这个int本质就是一个数组的下标

所以,在我们写的时候,必须得把这个数组的下标传进去,进程通过指针找到文件描述符表,然后在通过这个数组下标,索引到文件的地址,从而往该文件写入。

文件和进程产生关联是通过数组下标关联的,这样就可以做到文件和进程的解耦。

这个文件描述符可还没见过呢,下面我们看看文件描述符是几呢?

我们可以看到是3, 下面我们多打开几个文件看看。

可以看到,是连续的整数。但是问题来了,0、1、2哪里去了呢?

4.stdin&&stdout&&stderror

C程序在默认启动的时候,会打开三个标准输入输出流(文件):stdin(键盘文件),stdout(显示器文件),stderr(显示器文件)。所以我们在打印的时候为啥要包含stdio.h呢?std就是标准的意思,io就是输入输出,是C语言会打开吗?任何语言都会打开这三个文件,这不是C语言的特性,是操作系统的特性,电脑在打开的时候,键盘和显示器文件默认就会打开,进程只需要把打开键盘,显示器文件的地址填入即可。因此0、1、2是被这3个家伙占着呢?怎么验证呢?

我们直接用write往1和2里面写入。1和2是显示器文件哦,直接打印出来了。

下面用read接口验证一下0号,键盘文件。 

为啥卡住了呢?因为0是键盘文件,在等待键盘就绪!

现在问题又来了哦,可C语言的返回值是FILE指针啊,这和int有啥关系呢?FILE是C语言的内置类型吗?不是,FILE是C库里封装的一个结构体,这个结构体里面一定包含了该文件的数组下标,下面验证一下。 

5.文件的引用计数 

关闭文件的系统调用是close,现在我们把下标为1的文件stdout关闭。然后打印了stdout和stderr的fileno,fprintf的用法和printf基本一致,只不过前面加了一个文件描述符而已。我们可以看到,printf没有打印在显示器上打印出来,这肯定和我们关闭close有关,因此,printf底层肯定访问了stdout显示器文件,然后我们把stdout关闭,因此在屏幕上就打印不出来了。但是为啥stderr文件能打印出来呢?stdout和stderr都指向显示器文件,因此显示器文件的引用计数就是2,如果再来一个指向显示器,引用计数就会继续增加。当把stdout关闭,引用计数--变成了1,因此stderr还是能打印出来,关闭文件是把该下标的地址填成NULL。

printf也是有返回值的,其实stdout已经关闭了,但printf以为自己打印成功了,因此就把打印的字符个数13返回了过来。

 四、重定向

1.文件描述符的分配规则

我们把2号文件描述符关闭之后,新创建的文件描述符就是2,因此我们可以得知,文件描述符的分配规则就是从0下标开始,寻找最小的没有被使用的数组位置,它的下标就是新的文件描述符

2. 输出重定向

下面我们把2号文件描述符关闭,然后创建一个文件fd,之后调用write往1号文件里写入5次。

1号文件描述符对应的显示器文件,因此我们把内容写入到了显示器上。

下面我们把1号文件描述符关闭,然后创建一个文件fd,之后调用write往1号文件里写入5次。

我们发现,本来应该向1号文件描述符也就是显示器写入的信息,居然写到了 log.txt里,这是因为我们把1号文件描述符关闭了,然后又创建了一个文件,这个文件根据分配规则,就分配到了1号描述符,然后我们往1号描述符里面写,就写到了文件里。本来应该往显示器写却写入到了文件里,这就叫输出重定向。这里我们可以画张图理解一下。

这样写不是不可以,但是要先关一次,然后在打开一个文件,当别人问你为啥这么做的时候,你就要和别人解释半天,有没有一写系统调用能帮我们做这件事呢?打开文件就行了,然后重定向调用函数就行,有这样的接口吗?是有的。

3.重定向系统调用

是有dup,dup2,dup3系统调用的,常用的就dup2,因此我们详细谈谈dup2,dup就是duplicate,复制的意思,参数是2个文件描述符,一个旧的文件描述符,一个新的文件描述符。

那么问题来了,是把旧的文件描述符内容拷贝给新的文件描述符内容,还是新的文件描述符内容拷贝给旧的文件描述符内容呢?这样说吧,dup2之后,2个文件描述符内容全都变成newfd的还是oldfd的?常理来看应该是全都变成newfd吧,但实际结果是全都变成oldfd,这里挺奇怪的是吧,也不懂老外为啥这样起名字。因此如果我们要让1号文件描述符内容是新建的文件描述符fd的内容,要怎么传参呢?就要dup2(fd, 1)这样传参。下面我们来使用一下。

我们用dup2就实现了同样的效果。 上面的代码忘记close了,要记得close文件。

4.追加重定向

上面的代码中,文件是O_APPEND方式打开,我们多运行几次,这个log.txt就会越来越大,这就叫追加重定向。

5

5.输入重定向 

read的第一个参数是文件描述符,要读哪个文件,第二个参数是读到哪,第三个是读多少个字节,返回值是实际读了多少个字节。

下面我们读取文件方式改为只读方式,然后读取0号文件,也就是键盘文件,阻塞住了,我们往键盘输入内容,然后回显出来了。

下面我们把0号重定向一下。 

我们可以看到本来应该从键盘文件标准输入变读取,变为了从指定的文件读取,这就叫输入重定向

重定向的本质,就是在内核里对文件的地址做拷贝。

6.1号VS2号

我们直接往1号和2号文件描述符里打印,运行,可以看到没问题,都打印出来了。但是当 ./myfile > normal.txt,也就是把1、2号文件的打印输出重定向到normal.txt文件的时候,为啥2号文件的内容没有重定向到nomral.txt里呢?为啥cat只能看到1号文件描述符重定向的内容呢?这是因为>是把1号显示器的内容重定向到了文件里,和我2号文件描述符有啥关系?

 

如果我想把正常消息打印到一个文件,错误消息打印到一个文件,该咋办呢?可以进行下图中的操作,这里其实非常直观,就是把1号文件的内容重定向到normal.log,2号文件的内容重定向到err.log里。默认不写的话是把1号重定向到文件。下面就多出来了err.log文件。

那如果我要把1、2的内容重定向到一个文件里咋办呢? 

默认不写就是1号文件重定向到all.log文件,指令是从左往右执行的,&1(取地址1)的意思就是把1号文件的内容写入到2号文件里,因为左边的指令已经执行完了,1号文件的地址已经是all.log的地址了,然后把1号文件内容拷贝给2,此时1和2都指向了all.log文件。最后就能都写入到all.log里了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【微服务】SpringBoot 整合ELK使用详解

目录 一、前言 二、为什么需要ELK 三、ELK介绍 3.1 什么是elk 3.2 elk工作原理 四、ELK搭建 4.1 搭建es环境 4.1.1 获取es镜像 4.1.2 启动es容器 4.1.3 配置es参数 4.1.4 重启es容器并访问 4.2 搭建kibana 4.2.1 拉取kibana镜像 4.2.2 启动kibana容器 4.2.3 修改…

基于YOLOv8深度学习的汽车车身车损检测系统研究与实现(PyQt5界面+数据集+训练代码)

本文研究并实现了一种基于YOLOV8深度学习模型的汽车车身车损检测系统&#xff0c;旨在解决传统车损检测中效率低、精度不高的问题。该系统利用YOLOV8的目标检测能力&#xff0c;在单张图像上实现了车身损坏区域的精确识别和分类&#xff0c;尤其是在车身凹痕、车身裂纹和车身划…

ui->tableView升序

亮点 //设置可排序ui->tableView->setSortingEnabled(true);ui->tableView->sortByColumn(0,Qt::AscendingOrder); //排序void Widget::initTable() {//设置焦点策略:ui->tableView->setFocusPolicy(Qt::NoFocus);//显示网格线:ui->tableView->se…

Android Framework AMS(16)进程管理

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读AMS 进程方面的知识。关注思维导图中左上侧部分即可。 我们本章节主要是对Android进程管理相关知识有一个基本的了解。先来了解下L…

QT_CONFIG宏使用

时常在Qt代码中看到QT_CONFIG宏&#xff0c;之前以为和#define、DEFINES 差不多&#xff0c;看了定义才发现不是那么回事&#xff0c;定义如下&#xff1a; 看注释就知道了QT_CONFIG宏&#xff0c;其实是&#xff1a;实现了一个在编译时期安全检查&#xff0c;检查指定的Qt特性…

Spring Boot教程之Spring Boot简介

Spring Boot 简介 接下来一段时间&#xff0c;我会持续发布并完成Spring Boot教程 Spring 被广泛用于创建可扩展的应用程序。对于 Web 应用程序&#xff0c;Spring 提供了 Spring MVC&#xff0c;它是 Spring 的一个广泛使用的模块&#xff0c;用于创建可扩展的 Web 应用程序。…

Vue2教程001:初识Vue

文章目录 1、初识Vue1.1、Vue2前言1.2、创建Vue实例1.3、插值表达式1.4 Vue响应式特性 1、初识Vue 1.1、Vue2前言 Vue是什么&#xff1f; 概念&#xff1a;Vue是一个用于构建用户界面的渐进式框架。 Vue的两种使用方式&#xff1a; Vue核心包开发 场景&#xff1a;局部模块…

Linux手动安装nginx

本次以安装nginx-1.12.2为例 1、首先说明一下&#xff0c;安装nginx之前需要安装如下素材&#xff1a; 2、开始安装 第一步&#xff0c;安装依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel第二步&#xff0c;下载并安装nginx安装包&#xff08;n…

Element-ui Select选择器自定义搜索方法

效果图 具体实现 <template><div class"home"><el-selectref"currencySelect"v-model"currency"filterable:spellcheck"false"placeholder"请选择":filter-method"handleCurrencyFilter"change&q…

leetcode-44-通配符匹配

题解&#xff1a; 代码&#xff1a; 参考&#xff1a; (1)牛客华为机试HJ71字符串通配符 (2)leetcode-10-正则表达式匹配

C/C++中使用MYSQL

首先要保证下载好mysql的库和头文件&#xff0c;头文件在/usr/include/mysql/目录下&#xff0c;库在/usr/lib64/mysql/目录下&#xff1a; 一般情况下&#xff0c;在我们安装mysql的时候&#xff0c;这些都提前配置好了&#xff0c;如果没有就重装一下mysql。如果重装mysql还是…

Tryhackme练习-Wonderland

基本信息 由于tryhackme是在线靶场&#xff0c;所以这里的IP均为对方的内网IP 攻击机器&#xff1a;10.10.242.186 靶机&#xff1a;10.10.173.3 目标&#xff1a;获取2个flagroot权限 具体流程 信息收集 首先我们使用fscan进行端口扫描&#xff0c;fscan -h 10.10.173.…

SQL笔试题笔记(1)

下列选项中关于数据库事务的特性描述正确的是&#xff08;&#xff09; A.事务允许继续分割B.多个事务在执行事务前后对同一个数据读取的结果是不同的C.一个事务对数据库中数据的改变是暂时的D.并发访问数据库时&#xff0c;各并发事务之间数据库是独立的 答案解析&#xff1a…

vue3 如何调用第三方npm包内部的 pinia 状态管理库方法

抛砖引玉: 如果在开发vue3项目是, 引用了npm第三方包 ,而且这个包内使用了Pinia 状态管理库,那我们如何去调用 npm内部的 Pinia 状态管理库呢? 实际遇到的问题: 今天在制作npm包时遇到的问题,之前Vue2版本的时候状态管理库用的Vuex ,当时调用npm包内的状态管理库很简单,直接引…

麒麟KylinServer的网站,并部署一套主从DNS服务器提供域名解析服务

一、KylinServer网站搭建 ifconfig Copy 注意:根据实际网卡设备名称情况调整代码!不同环境下网卡名称略有不同! 获取本机IP地址,记住IP地址用于之后的配置填写。 ifconfig enp0s2 Copy 下载nginx源码包,并解压缩 wget http://10.44.16.102:60000/allfiles/Kylin/ng…

Python数据分析NumPy和pandas(三十五、时间序列数据基础)

时间序列数据是许多不同领域的结构化数据的重要形式&#xff0c;例如金融、经济、生态学、神经科学和物理学。在许多时间点重复记录的任何内容都会形成一个时间序列。许多时间序列是固定频率的&#xff0c;也就是说&#xff0c;数据点根据某些规则定期出现&#xff0c;例如每 1…

大数据常见面试题及答案(Linux、Zookeeper、Hadoop、Hive)

技术问答题目 一、Linux 1.如何给⽂件(⽂件夹)分配读r、w、x的操作权限&#xff1f; 2. vi 编辑器的常⽤命令有哪些&#xff1f; 3.Linux 中⽂件的操作权限分为⼏种&#xff1f; 4.Linux 中实时查看日志的方法 5. Linux查看内存、磁盘存储、io 读写、端口占用、进程等命…

【软件工程】一篇入门UML建模图(类图)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;软件开发必练内功_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…

开源音乐分离器Audio Decomposition:可实现盲源音频分离,无需外部乐器分离库,从头开始制作。将音乐转换为五线谱的程序

今天给大家分析一个音频分解器&#xff0c;通过傅里叶变换和信封匹配分离音乐中的各个音符和乐器&#xff0c;实现音乐到乐谱的转换。将音乐开源分离为组成乐器。该方式是盲源分离&#xff0c;从头开始制作&#xff0c;无需外部乐器分离库。 相关链接 代码&#xff1a;https:…

微服务day10-Redis面试篇

Redis主从 搭建主从集群 建立集群时主节点会生成同一的replicationID,交给各个从节点。 集群中的缓冲区是一个环型数组&#xff0c;即若从节点宕机时间过长&#xff0c;可能导致命令被覆盖。 主从集群优化 哨兵原理 哨兵是一个集群来确保哨兵不出现问题。 服务状态监控 选举…