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

[Linux]----文件操作(复习C语言+文件描述符)

文章目录

  • 前言
  • 一、基础概念
  • 二、回顾C语言
    • 2.1 对文件进行写操作
    • 2.2 追加写文件
    • 2.3 读文件
    • 2.4 简易cat功能
    • 总结
      • stdin&stdout&stderr
      • 打开文件的方式
  • 三、系统文件I/O
    • 接口介绍
      • open介绍
      • 使用open接口
      • close
      • write
      • read
  • 四、文件描述符
    • 先验证0,1,2就是标准的IO
      • 标准输入流
      • 标准输出流
      • 标准错误流
    • 验证0,1,2和stdin,stdout,stderr的对应关系
    • 文件描述符的分配规则
  • 总结


前言

今天这个小结节,我来大家来了解Linux下的文件操作。首先我们来复习一下C语言的文件操作,基于C语言的文件操作我们对Linux的学习就会方便很多了!我带大家首先来了解文件相关系统的接口和文件描述符,并且理解重定向!
最后在基于重定向,在下一小节将我上节写的myshell完善一下!



正文开始!

一、基础概念

  1. 文件=文件内容+文件属性(属性也是数据,即便你创建一个空文件,也要占据磁盘空间)
  2. 文件操作=文件内容的操作+文件属性的操作(有可能再操作文件的时候,即改变内容,又改变属性)
  3. 对于文件操作,我们首先要打开文件。所谓"打开"文件,究竟在干什么?将文件的属性或者内容加载到内存中!(冯诺依曼体系结构决定!)
  4. 是不是所有的文件,都会被处于打开的状态呢?没有被打开的文件,在哪里呢?(只在磁盘上存储!)
  5. 打开的文件(内存文件)和磁盘文件
  6. 通常我们打开文件,访问文件,关闭文件,是谁在进行相关操作?fopen,fwrite,fread,fclose…->代码->程序->当我们文件程序,运行起来的时候,才会执行对应的代码,然后才是真正的对文件进行相关的操作(进程在做相关操作!!!)
  7. 进程和打开文件的关系!

二、回顾C语言

2.1 对文件进行写操作

在这里插入图片描述
当我们以w方式打开文件,准备写入的时候,其实文件已经先被清空了!

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

int main()
{
    FILE* fp=fopen("log.txt","w");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg="hello rose!";
    int cnt=0;
    while(cnt<10)
    {
        fprintf(fp,"%s %d\n",msg,cnt);
        cnt++;
    }
    fclose(fp);
    return 0;
}

在这里插入图片描述

默认这个"log.txt"文件会在哪里形成呢?—>当前路径

那么什么是当前路径呢?–>进程当前的路径

接下来带大家查看进程的信息

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

int main()
{
    FILE* fp=fopen("log.txt","w");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    printf("%d\n",getpid());
    while(1)
    {
        sleep(1);
    }//在这里会一直休眠下去,直到我们杀掉这个进程
    const char* msg="hello rose!";
    int cnt=0;
    while(cnt<10)
    {
        fprintf(fp,"%s %d\n",msg,cnt);
        cnt++;
    }
    fclose(fp);
    return 0;
}

ll /proc/进程id

在这里插入图片描述
在这里我们就可以看到"log.txt"就在当前cwd,也就是进程所处的路径了。

接下来我们有意识的更改当前路径,这里需要用到系统接口chdir();
在这里插入图片描述

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

int main()
{
    chdir("/home/hulu");
    FILE* fp=fopen("log.txt","w");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    printf("%d\n",getpid());
    while(1)
    {
        sleep(1);
    }
}

在这里插入图片描述

在这里插入图片描述

2.2 追加写文件

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

int main()
{
    FILE* fp=fopen("log.txt","a");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg="hello rose!";
    int cnt=0;
    while(cnt<5)
    {
        fprintf(fp,"%s %d\n",msg,cnt);
        cnt++;
    }
    fclose(fp);
    return 0;
}

在这里插入图片描述

追加写入,不断的往文件中新增内容—>追加重定向!

在这里插入图片描述

2.3 读文件

在这里插入图片描述

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

int main()
{
    FILE* fp=fopen("log.txt","r");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    char buffer[64];
    while(fgets(buffer,sizeof(buffer),fp)!=NULL)
    {
        printf("echo: %s",buffer);
    }
    fclose(fp);
    return 0;
}

在这里插入图片描述

2.4 简易cat功能

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

//myfile filename
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        printf("Usage: %s filename\n",argv[0]);
        return 1;
    }
    FILE* fp=fopen(argv[1],"r");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    char buffer[64];
    while(fgets(buffer,sizeof(buffer),fp)!=NULL)
    {
        printf("%s",buffer);
    }
}

在这里插入图片描述
在这里插入图片描述

当我们向文件写入的时候,最终是不是向磁盘写入?

因为磁盘是硬件,所以只有OS有资格向硬件写入!

那么能绕开操作系统吗?

答:不能!那么所有上层的访问文件的操作,都必须贯穿操作系统!

操作系统是如何被上层使用的呢?

因为操作系统不相信任何人,所以必须使用操作系统提供的相关系统调用!

那么为什么要进行封装文件操作接口呢?

  • 原生系统接口,使用成本比较高!
  • 语言不具备跨平台性!

那么封装是如何解决跨平台性的问题呢?

  • 穷举所有底层的接口+条件编译!

C库提供的文件访问接口是来自于系统调用!
那么就能解释不同的语言有不同的文件访问接口!!
所以无论什么语言的底层的接口是不变的!

所以这就要求我们必须学习文件级别的系统接口!

总结

stdin&stdout&stderr

  • C默认会打开三个输入输出流,分别是stdin,stdout,stderr
  • 仔细观察发现,这三个流的类型都是FILE*,fopen返回值类型,文件指针

打开文件的方式

在这里插入图片描述

三、系统文件I/O

接口介绍

open介绍

在这里插入图片描述

返回值

在这里插入图片描述

打开文件的选项

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对于O_RDONLY,O_WRONLY,O_RDWR,O_APPEND,O_CREAT!这些都是宏!

系统传递标记位,使用位图结构来进行传递的!

每一个宏标记,一般只需要有一个比特位为1,并且和其他宏对于的值不能重叠。

代码模拟实现

#include<stdio.h>

#define PRINT_A 0x1
#define PRINT_B 0x2
#define PRINT_C 0x4
#define PRINT_D 0x8
#define PRINT_DFL 0x0


void Show(int flags)
{
    if(flags&PRINT_A)
        printf("hello A\n");
    
    if(flags&PRINT_B)
    printf("hello B\n");
    
    if(flags&PRINT_C)
    printf("hello C\n");
    
    if(flags&PRINT_D)
    printf("hello D\n");

    if(flags==PRINT_DFL)
        printf("hello Default\n");

}

int main()
{
    Show(PRINT_DFL);
    Show(PRINT_A);
    Show(PRINT_B);
    Show(PRINT_A|PRINT_B);
    Show(PRINT_C|PRINT_D);
    Show(PRINT_A|PRINT_B|PRINT_C|PRINT_D);
	return 0;
}

我们通过传入不同的选项,打印出不同的语句。

在这里插入图片描述

使用open接口

#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<0)
  {
    perror("open error");
    return 1;
  }
  printf("fd=%d\n",fd);

      return 0;
}

在这里插入图片描述
在这里插入图片描述
所以我们要打开曾经不存在的文件,我们要用到第二个open函数,带有权限的设置!

int open(const char *pathname, int flags, mode_t mode);
int fd = open(“log.txt”,O_WRONLY|O_CREAT,0666);

在这里插入图片描述
可以看到我们创建文件的权限是0666,可是实际显示的是0664呢?

这就和我们之前学的掩码umask有联系了!

在这里插入图片描述

不清楚的话可以去看看这篇博客权限的理解!

close

在这里插入图片描述

write

在这里插入图片描述

#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,0666);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    int cnt=0;
    const char* str="hello file!\n";
    while(cnt<5)
    {
      write(fd,str,strlen(str));
      cnt++;
    }

    close(fd);
    return 0;
}

在这里插入图片描述
C在w方式打开文件的时候,会清空的!

int main()
{
    int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    int cnt=0;
    const char* str="aaaa";
    //const char* str="hello file!\n";
    while(cnt<5)
    {
      write(fd,str,strlen(str));
      cnt++;
    }

    close(fd);
    return 0;
}

修改我们的代码后

在这里插入图片描述
我们发现此处直接覆盖曾经的数据,但是曾经的数据为什么保留呢了?

因为我们没有带有截断选项O_TRUNC

在这里插入图片描述

接下来我们带上这个选项后

int main()
{
    int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    int cnt=0;
    const char* str="hello rose!\n";
    while(cnt<5)
    {
      write(fd,str,strlen(str));
      cnt++;
    }

    close(fd);
    return 0;
}

在这里插入图片描述
现在我们就发现之前的数据被截断了!

所以我们现在可以类比与C语言的fopen,底层的open的选项就是"O_WRONLY|O_CREAT|O_TRUNC"!!!

接下来我们验证追加选项"O_APPEND"
代码如下

int main()
{
    int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    int cnt=0;
    const char* str="hello hulu!\n";
    while(cnt<5)
    {
      write(fd,str,strlen(str));
      cnt++;
    }

    close(fd);
    return 0;
}

在这里插入图片描述

read

我们打开文件就默认他是存在的,不需要携带"O_CREAT"选项
如果文件不存在会返回-1;
在这里插入图片描述

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define NUM 1024
int main()
{
    int fd = open("log.txt",O_RDONLY);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    char buffer[NUM];
    while(1)
    {
      ssize_t s=read(fd,buffer,sizeof(buffer)-1);
      if(s>0)
      {
        buffer[s]='\0';
        printf("%s",buffer);
      }
      else
        break;
    }

    close(fd);
    return 0;
}

在这里插入图片描述

四、文件描述符

在上面的实验中我们了解到打开文件后返回给文件描述符fd=3,这是为什么?

接下来先来看代码,让我们去了解文件描述符

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    int fda=open("loga.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    int fdb=open("logb.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    int fdc=open("logc.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    int fdd=open("logd.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    int fde=open("loge.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    printf("fda=%d\n",fda);
    printf("fdb=%d\n",fdb);
    printf("fdc=%d\n",fdc);
    printf("fdd=%d\n",fdd);
    printf("fde=%d\n",fde);
    return 0;
}

在这里插入图片描述

  1. 为什么文件描述符默认是从3开始的呢?那么0,1,2去哪了?
    因为0,1,2被默认打开了
  • 0:标准输入,键盘

  • 1:标准输出,显示器

  • 2:标准错误,显示器
    在这里插入图片描述
    这就与我们C语言联系起来了!因为C语言封装的系统接口。

    首先我们之前在C语言中学到FILE*–>文件指针—>FILE是什么呢?—>C语言提供的结构体!–>封装了多个成员

    因为对于文件操作而言,系统接口只认识fd;(FILE内部必定封装了fd)

  1. 0,1,2,3,4…,我们之前见过什么样的数据是这个样子的呢?

    这和我们之前学习的C/C++的数组下标相似

    进程:内存文件的关系—>内存—>被打开的文件是存在内存里面的!!!

    一个进程可不可以打开多个文件?–>当然可以,所以在内核中,进程:打开的文件=1:n–>所以系统在运行中,有可能会存在大量的被打开的文件!—>OS要不要对这些被打开的文件进行管理呢??—>操作系统如何管理这些被打开的文件呢??—>答案是先描述,在组织。

    一个文件被打开,在内核中,要创建该被打开的文件的内核数据结构—先描述

    在这里插入图片描述
    在这里插入图片描述
    那么进程如何和打开的文件建立映射关系呢??

在这里插入图片描述

先验证0,1,2就是标准的IO

标准输入流

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

int main()
{
  char buffer[1024];
  ssize_t s=read(0,buffer,sizeof(buffer)-1);
  if(s>0)
  {
    buffer[s]='\0';
    printf("echo:%s",buffer);
  }
  return 0;
} 

在这里插入图片描述

标准输出流

int main()
{
  const char* s="hello write!\n";
  write(1,s,strlen(s));
} 

在这里插入图片描述

标准错误流

int main()
{
  const char* s="hello write!\n";
  write(2,s,strlen(s));
} 

在这里插入图片描述
我们看出标准错误流也打印到了显示器上面。

至于标准输出和标准错误的区别,我们稍后带大家了解。

验证0,1,2和stdin,stdout,stderr的对应关系


int main()
{
    printf("stdin: %d\n",stdin->_fileno);
    printf("stdout: %d\n",stdout->_fileno);
    printf("stderr: %d\n",stderr->_fileno);
} 

在这里插入图片描述

由上面的实验我们可以得出,FILE结构体中的fileno就是封装了文件描述符fd!!!

在这里插入图片描述

0,1,2—>stdin,stdout,stderr—>键盘,显示器,显示器(这些都是硬件呀!)也用你上面的struct file来标识对应的文件吗??
在这里插入图片描述
如何证明呢?

我来带大家看看LInux下的内核结构!!!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

文件描述符的分配规则

int main()
{
    close(0);
    //close(1);
    //close(2);
    int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    printf("fd=%d\n",fd);
} 

在这里插入图片描述
我们发现把1关掉后什么都没有了!

因为printf->stdout->1虽然不在指向对应的显示器了,但是已经指向了log.txt的底层struct_file对象!

在这里插入图片描述

遍历fd_array[],找到最小的没有被使用的下标,分配给新的文件!!


总结

下小节我来给大家讲述关于重定向的本质,和缓冲区的概念,等讲完重定向后,我们再把myshell完善!
(本章完!)

相关文章:

  • 【笔记:模拟MOS集成电路】二级运算放大器频率响应与品质因子Q和阻尼因子ξ的讨论
  • 通关 MySQL获奖名单已公布
  • Python数据类型 ——— 元组
  • jmeter模拟多IP访问
  • 【微服务】Docker的基本操作
  • 蓝桥杯国奖一等奖,经历回顾
  • 云IDE介绍——CSDN开发云
  • rocketmq-console-1.0.0启动报错
  • Python获取 小黑子 弹幕数据+制作词云分析.........
  • Mysql重要知识点详解
  • python经典小游戏:24速算(案例)
  • 用Python来表白,把情书写进她的照片里
  • 【K8S系列】有状态服务 VS 无状态服务
  • Android通知监听服务之NotificationListenerService使用篇
  • java编程计算机网页项目jsp高校体育竞技赛系统myeclipse开发Mysql数据库web结构
  • 【159天】尚学堂高琪Java300集视频精华笔记(128)
  • echarts的各种常用效果展示
  • JAVA之继承和多态
  • java中具有继承关系的类及其对象初始化顺序
  • laravel5.5 视图共享数据
  • Promise面试题,控制异步流程
  • Redis在Web项目中的应用与实践
  • tensorflow学习笔记3——MNIST应用篇
  • vue学习系列(二)vue-cli
  • Web标准制定过程
  • 关于Java中分层中遇到的一些问题
  • 机器学习中为什么要做归一化normalization
  • 基于遗传算法的优化问题求解
  • 简单实现一个textarea自适应高度
  • 让你的分享飞起来——极光推出社会化分享组件
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 学习笔记:对象,原型和继承(1)
  • 最简单的无缝轮播
  • 2017年360最后一道编程题
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​Linux·i2c驱动架构​
  • ​第20课 在Android Native开发中加入新的C++类
  • ​人工智能书单(数学基础篇)
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • (4)STL算法之比较
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (一)Neo4j下载安装以及初次使用
  • (译)计算距离、方位和更多经纬度之间的点
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (转)http-server应用
  • *2 echo、printf、mkdir命令的应用
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .NET/C# 获取一个正在运行的进程的命令行参数