秒懂Linux之文件

news2025/2/5 7:51:39

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

前言

一. C文件接口

二. 文件系统调用接口

 open接口​编辑

文件fd的分配规则

利用规则实现重定向


前言

在我们学习文件之前得先明白文件是什么?

——文件 = 内容 + 属性——

文件是谁打开的呢?

——文件由进程调度打开,当然前提是文件必须先加载入内存中~我们在访问文件之前必须得先打开,修改文件就是通过执行代码的方式进行修改——

文件都是呈现被进程打开的状态吗?

——虽然一个进程可以打开很多个文件,但并不是会全部打开,不去访问的文件会存储在磁盘中等待——

一. C文件接口

我们先来进行一个简单的文件写入操作~

#include <stdio.h>

int main()
{
  FILE* fp = fopen("./log.txt","w");
  if(fp==NULL)
  {
    perror("fopen");
    return 1;
  }
  //写入操作
  const char* str = "hello\n";
  fputs(str,fp);
  fclose(fp);
  return 0;
}

我们以写方式打开文件~若文件不存在则会在原路径下创建文件用fputs函数把写入的内容传到文件中~ 

如果我们尝试注释掉写入操作,那么原来的文件内容还会存在吗?

之前的文件内容消失了~明明我们已经往里面写入内容了且文件没有被删除,为什么下一次打开内容会消失呢?

总结:以W方式打开的文件每一次打开里面的内容都会自动清空~

我们接着换追加的方式进行打开文件~

#include <stdio.h>

int main()
{
  FILE* fp = fopen("./log.txt","a");
  if(fp==NULL)
  {
    perror("fopen");
    return 1;
  }
  //写入操作
  const char* str = "hello\n";
  fputs(str,fp);
  fclose(fp);
  return 0;
}

这时候就可以发现我们每一次打开文件都可以保留上一次的内容了~ 

ps:echo " " > 文件名 与echo " " >> 文件名这两条指令本质就是以w方式打开并写入与以a方式打开并写入~

下面我们再来学习一个写入函数fwrite

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

#define FILENAME "log.txt"

int main()
{
  FILE* fp = fopen(FILENAME,"w");
  if(fp==NULL)
  {
    perror("fopen");
    return 1;
  }
  //写入操作
  const char* str = "hello\n";
  int cnt = 5;
  while(cnt)
  {
   int n = fwrite(str,strlen(str),1,fp);
   printf("write %d block\n",n);
   cnt--;
  }
  fclose(fp);
  return 0;
}

fwrite写入成功时的返回值为1;而传递的参数分别为:

写入的内容;多少个基本单位;基本单位的大小(字节);写入的方向~

我们利用fget函数来读取在文件中的内容~

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

#define FILENAME "log.txt"

int main()
{
  FILE* fp = fopen(FILENAME,"r");
  if(fp==NULL)
  {
    perror("fopen");
    return 1;
  }

  //读取操作
  char buffer[64];
  while(1)
  {
    char * r = fgets(buffer,sizeof(buffer),fp);
    if(!r) break;

    printf("%s", buffer);
  }
  return 0;
}

下面为大家介绍一下程序默认打开的三个文件流:

  • stdin     标准输入   键盘设备
  • stdout   标准输出   显示器设备
  • stderr    标准错误   显示器设备

难道我们就没有怀疑过吗?我们从键盘上获取输入的信息,从显示器上读取输出的信息本质上都是去访问硬件设备的~而这些设备一定会有对应的文件来方便操作系统进行管理~

所以每次程序启动的时候这三个文件系统会帮我们打开,节省我们的时间成本~我们总不能每写一个程序都要手动去打开这三个必须要执行的文件吧?

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

#define FILENAME "log.txt"

int main()
{

  printf("hello printf\n");
  fputs("hello fputs\n",stdout);
  const char* str = "hello,fwrite\n";
  fwrite(str,strlen(str),1,stdout);
  fprintf(stdout,"hello fprintf\n");
  return 0;
}

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

#define FILENAME "log.txt"

int main()
{
  
  char buffer[64];
  fscanf(stdin,"%s", buffer);

  printf("%s", buffer);
  return 0;
}

二. 文件系统调用接口

访问文件不仅仅有c语言上的文件接口,OS也必须提供对应的访问文件的系统调用

 open接口

我们先来了解一下参数flags的使用~

#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)

void Print(int flag)
{
    if(flag & ONE) printf("1\n");
    if(flag & TWO) printf("2\n");
    if(flag & THREE) printf("3\n");
    if(flag & FOUR) printf("4\n");
    if(flag & FIVE) printf("5\n");
}

int main()
{
    Print(ONE);
    printf("----------------------\n");
    Print(TWO);
    printf("----------------------\n");
    Print(ONE|TWO);
    printf("----------------------\n");
    Print(THREE|FOUR|FIVE);
    printf("----------------------\n");
    Print(ONE|TWO|THREE|FOUR|FIVE);
}

flag可以传递多个参数,不再局限于参数的数量~ 本质上传递的参数为宏~

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

int main()
{
  int fd = open("log.txt",O_WRONLY|O_CREAT);
  if(fd==-1)
  {
    perror("open");
    return 1;
  }
}

我们使用系统调用接口open对文件进行写操作方式打开,如果没有文件则在该路径下创建文件~ 

我们发现创建好的文件权限出现了问题?而这时候就需要我们的另一个参数mode上场了~

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

int main()
{
  umask(0);
  int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
  if(fd==-1)
  {
    perror("open");
    return 1;
  }
}

我们手工给文件赋予权限~

下面我们来介绍一下open返回值fd(文件描述符)的特点~

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



int main()
{
  umask(0);
  int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
  if(fd==-1)
  {
    perror("open");
    return 1;
  }

  const char* str = "hello\n";
  write(fd,str,strlen(str));

  close(fd);
  return 0;
}

使用系统调用接口时,我们进行写入操作(write)需要fd ,关闭文件(close)也需要fd。

我们再把字符串改为aa且不带换行~

 这时候发现新的内容直接和旧内容重叠了~

我们新添加的O_TRUNC宏定义可以在每次打开文件时清空原有的内容~这样就相当于c语言当中的w方式了

若我们想要进行追加内容,可以用宏定义:O_APPEND,这样整体就相当于c语言中的a方式了

我们回归主题来继续讨论返回值fd的用途~

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



int main()
{
  umask(0);
  int fd1 = open("log1.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  int fd2 = open("log2.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  int fd3 = open("log3.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  int fd4 = open("log4.txt",O_WRONLY|O_CREAT|O_APPEND,0666);

  printf("fd1: %d\n", fd1);
  printf("fd2: %d\n", fd2);
  printf("fd3: %d\n", fd3);
  printf("fd4: %d\n", fd4);

  return 0;
}

 

我们发现每个文件返回的fd都不一样的,并且还是以递增的顺序~那么最前面的三个又是谁呢?

int main()
{

  printf("%d\n", stdin->_fileno);
  printf("%d\n", stdout->_fileno);
  printf("%d\n", stderr->_fileno);

  return 0;
}

 

其实就是系统默认帮我们打开的文件流~

它们的fd被c语言封装到_fileno的成员变量内~

 通过这张图我们可以看到所接触的c语言接口其实本质都是封装了系统调用

而在c语言接口中之所以用FILE* 而不是fd的原因就是因为fd这个类型也被c语言封装了~

之所以封装都是为了可移植性,如果我们用系统调用那就得考虑是在Linux环境下还是Windows环境下,但如果是用c标准库这些封装系统调用接口的函数就会去自行匹配环境差异,我们去交给操作系统干~

为什么fd文件描述符是数字呢?这其中还代表了什么含义吗?

打开多个文件需要管理,因此有了操作系统的文件管理~为了让进程管理与文件管理之间出现低耦合,我们在它们之间添加了一个结构体媒介~该媒介专门存储对应文件管理的结构体指针并放到数组内进行标号,而进程则存储指向该媒介的指针间接去管理这些文件~

所以fd本质上就是数组的标号,它代表着相对应文件的存放顺序~

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



int main()
{

  int fd = open("log.txt", O_RDONLY);
  if(fd == -1)
  {
      perror("open");
      return 1;
  }
  char buffer[1024];
  ssize_t s = read(fd, buffer, sizeof(buffer));
  if(s>0)
  {
      buffer[s] = 0;
      printf("%s", buffer);
  }
  close(fd);
}

我们用read系统调用来读取文件内容~ 

 

我们拿系统调用举例,当我们获取到该文件的fd后操作系统就可以通过PCB中找到文件描述表,再通过获取的fd下标找到关于该文件的对象描述,后面无论是对文件的修改还是访问都可以正常去执行与管理~

拓展:

一切皆为文件,都可以被操作系统所管理~

我们在对描述文件的结构体中存储读写的函数指针,这些函数指针分别指向对应文件的读写系统调用,这样我们面对各种文件不同的操作方法时就有了消除访问硬件的差异性,实现对所以文件的管理。可以看到,即使是硬件设备我们也可以通过文件管理的方式进行整合,不用亲自去访问硬件,而是间接去访问~所以才有一切皆文件的说法~

文件fd的分配规则

    int main()
   {  close(0);
      int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666); 
      printf("fd: %d\n", fd);
   }

当我们关闭fd为0的文件时(关闭stdin),我们新创建的文件会占领前面空缺的下标位置~

说明:最小的没有被使用的数组下标,会分配给最新打开的文件~


利用规则实现重定向

如果我们尝试关闭文件(stdout:显示器)就会发生有趣的事情~ 本来应该打印在显示器上的内容结果打印到我们新创建的文件里面去了~

首先我们得知道close函数是系统调用的,可是printf不知道啊,它位于上层是C语言标准库里的,Printf默认就是向fd为1即显示器打印的,不关他的事~

可是新创建的文件已经是下标为1了,那么printf本该向显示器写入的变成向文件写入了~而这就是输出重定向~

我们再来测试一组~

int main()
{
  close(0);
  int fd = open("log.txt",O_RDONLY,0666);
  int a = 0;
  scanf("%d", &a);//  stdin->_fileno = 0
  printf("%d\n",a);
}

这一次我们把stdin(键盘)文件关闭了,一开始执行程序时我们已经无法通过键盘来获取内容了,当我们在新创建的log文件中写入内容,再执行一次发现获取到最新写入的a了~

把本该键盘获取的内容转为在新文件中获取,这是输入重定向~

这些C语言接口都会出现上述的情况~

当然如果我们想要实现重定向也不用这么麻烦,这里有专门的函数供我们调用~

dup2的功能就是把newfd拷贝成oldfd,最后只会剩下oldfd

最后我们再来探讨一下指令>,>>与open文件之间的关系~

int main()
{

    //int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
    //int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);
    int fd = open("log.txt", O_RDONLY);
    //dup2(fd, 0);
    char buffer[1024];
    while(1)
    {
        // 默认情况,stdin键盘中读取,默认是要阻塞
        char * s = fgets(buffer, sizeof(buffer), stdin); // stdin -> FILE * -> _fileno
        if(s == NULL) break;
        printf("file content: %s", buffer);
    }
}

如果我们不使用dup2,那么该进程就会等待我们键盘的输入~

若是使用dup2,那么进程就会直接获取到log文件的内容并向显示器输出打印~

其实关于重定向的指令本质上也是属于进程~而指令后面的指定文件名就是其要打开的文件,至于打开的方式就取决与是>指令还是>>指令,对应的指令会有对应的打开方式,它们会用自己的打开方式来进行进程替换~

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

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

相关文章

keepalived保活nginx1,nginx2

1 下载两个小玩意 yum -y install keepalived yum install psmisc -y 2 配置nginx1&#xff0c;2自启脚本 vim /root/shell/check-nginx.sh 我的脚本放在root/shell里 #!/bin/bash #获取nginx正在运行的进程数 npsnumps -C nginx --no-header | wc -lif [ $n…

Unity AB包

AB包比对文件内容: ab包名 包大小 包内容md5字符串 编辑器功能-设置默认资源: 可以选择资源作为默认资源保存到StreamingAssets文件夹并且为他们生成资源对比文件1. 在Unity菜单中添加一个功能按钮触发该功能(MenuItem特性)2. 获取在Project窗口选择的资源信息(Selection类)3. …

【js引擎】如何使用 quickjs 把一个 js 值转换成 c 字符串

开发 js 运行时时&#xff0c;最重要的模块莫过于 console.log 了。有了它&#xff0c;才可以在 js 代码中打印日志。那么如何用 quickjs 引擎实现该模块呢&#xff1f; 实际上 quickjs 已经实现了一个 console 的模块 js_print 实现 其中使用了接口 str JS_ToCStringLen(c…

【Linux】Linux(centos7)安装jdk1.8

第一步&#xff1a;卸载系统自带的JDK rpm -qa|grep java # xxx yyy zzz为你要卸载的插件&#xff0c;插件之间以空格隔开 rpm -e --nodeps xxx yyy zzz 先卸载JDK 第二步&#xff1a;安装JDK1.8 安装JDK rpm -ivh jdk-8u172-linux-x64.rpm 第三步&#xff1a;查看是否安…

基于x86 平台opencv的图像采集和seetaface6的人脸检测功能

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境三、开发流程3.1 编写测试3.2 配置资源文件3.2 验证功能一、概述 本文档是针对x86 平台opencv的图像采集和seetaface6的人脸检测功能,opencv通过摄像头采集视频图像,将采集的视频图像送给seetaface6的人脸检测模块从而实现…

特斯拉FSD软件进化史

特斯拉FSD软件进化史 01前言 特斯拉FSD自动驾驶是以摄像头为核心的纯视觉解决方案。 纯视觉方案的最初设计灵感来自对人类视觉的研究&#xff1b;即人眼睛搜集的信息到达视网膜后&#xff0c;经过大脑皮层的多个区域、神经层&#xff0c;最终形成生物视觉&#xff0c;并在脑…

Wyn商业智能助力零售行业数字化决策高效驱动

最新技术资源&#xff08;建议收藏&#xff09; https://www.grapecity.com.cn/resources/ 项目背景及痛点 百利商业的业务覆盖赛格、 SKP、奥莱、王府井等多地区具有代表性的商场&#xff0c;并创立了多个自有品牌。随着新零售模式的兴起&#xff0c;百利商业紧跟时代步伐&am…

集团数字化转型方案(六)

集团数字化转型方案旨在通过引入前沿技术&#xff0c;如人工智能&#xff08;AI&#xff09;、大数据分析、云计算和物联网&#xff08;IoT&#xff09;&#xff0c;全面提升业务运营效率和市场竞争力。该方案首先实现业务流程的自动化&#xff0c;减少人工干预&#xff0c;通过…

python-求距离(赛氪OJ)

[题目描述] 给你一个 1−>n 的排列&#xff0c;现在有一次机会可以交换两个数的位置&#xff0c;求交换后最小值和最大值之间的最大距离是多少&#xff1f;输入格式&#xff1a; 输入共两行。 第一行一个数 n 。 第二行 n 个数表示这个排列。输出格式&#xff1a; 输出一行一…

嵌入式day28

线程退出 ---pthread_exit&#xff08;&#xff09; 线程结束方式&#xff1a; 1.pthread_exit //pthread_join 2.从线程执行函数中return //此时等价于1 3.pthread_cancel //线程可以被取消 4.任何一个线程调用了exit 或者 主线程main函数return…

浮点数的使用

浮点运算 浮点,英文float point,其字面意义就是可以漂移的小数点(浮动的小数点),来表示含有小数的数值。 我们在数学运算中,经常会遇到无限小数,如1/3=0.333333…无限循环,然而计算机存储容量是有限的,需要舍弃掉一些精度,存储近似值。 讨论浮点精度的目的也是在于程…

Python酷库之旅-第三方库Pandas(090)

目录 一、用法精讲 381、pandas.Series.plot方法 381-1、语法 381-2、参数 381-3、功能 381-4、返回值 381-5、说明 381-6、用法 381-6-1、数据准备 381-6-2、代码示例 381-6-3、结果输出 382、 pandas.Series.plot.area方法 382-1、语法 382-2、参数 382-3、功…

嵌入式软件开发学习二:GPIO

Tips&#xff1a; 本文全部的TTL肖特基触发器应该均为施密特触发器&#xff0c;有些忘记改了。 资料来源&#xff1a;[3-1] GPIO输出_哔哩哔哩_bilibili 一、GPIO简介&#xff1a; GPIO&#xff08;General Purpose Input Output&#xff09;是指通用输入输出接口&#xff0c;…

GitLab Merge Request流水线

GitLab Merge Request 流程文档 为了提升代码质量&#xff0c;让开发人员参与代码review&#xff0c;现在输出Merge Request的流程文档&#xff1a; 1.项目创建各自开发者的分支&#xff0c;命名规则是dev_名字首字母&#xff0c;比如我是dev_cwq.然后把本地分支推到远端orgin…

数据库管理-第232期 初探OpenTenBase(20240819)

数据库管理232期 2024-08-19 数据库管理-第232期 初探OpenTenBase&#xff08;20240819&#xff09;1 OpenTenBase简介2 架构介绍3 集群规划4 部署集群4.1 操作系统配置4.2 数据库软件下载与安装4.3 配置集群4.4 分发软件4.5 启动数据库4.6 查看集群状态 5 访问数据库总结 数据…

Java并发常见面试题总结

文章目录 1 多线程1.1 进程、线程和协程的区别&#xff1f;&#x1f525;1.2 Java 线程和操作系统的线程有啥区别&#xff1f;1.3 如何创建线程&#xff1f;&#x1f525;1.4 runnable 和 callable 两个接口创建线程有什么不同呢&#xff1f;&#x1f525;1.5 如何停止一个正在…

2024.8.20

思维导图 C语言中指针数组和数组指针的区别 数组指针本质上是一个指针变量&#xff0c;用于存储整个数组的起始地址&#xff0c;指针每偏移一个单位&#xff0c;内存就会偏移整个数组的容量的大小&#xff0c;一般用于二维数组&#xff0c;二维数组的数组名&#xff0c;本质上是…

【可视化】封装滚动菜单列表组件

源码及源码分析 <template><!-- 滚动菜单的主要容器 --><div class"scrolling-menu"><!-- 如果headers数组有内容&#xff0c;就渲染表头 --><divclass"table-header"v-if"headers.length":style"{ backgroundC…

LLM向量嵌入知多少

向量嵌入是机器学习领域中一项引人入胜且极具实用性的技术。它们构成了众多自然语言处理&#xff08;NLP&#xff09;、推荐系统和搜索算法的基础。如果您曾经使用过推荐引擎、语音助手或语言翻译工具&#xff0c;那么您已经体验过嵌入技术的强大功能。 机器学习算法&#xff0…

如何为您的专用IP地址选择正确的IP SSL证书

随着互联网的不断发展&#xff0c;网站安全变得越来越重要。SSL证书不仅为网站提供了加密通道&#xff0c;还增强了用户对网站的信任感。对于那些使用专用IP地址的网站来说&#xff0c;选择合适的IP SSL证书至关重要。本文将为您详细介绍如何挑选最适合您网站需求的IP SSL证书。…