Linux之文件基础IO

目录

前言:各位老铁好,今天笔者来分享一个我们在c语言就学习过一部分的知识,相信各位老铁在学习c语言时一定学习过对文件的操作,例如fopen,fclose,fread,fwrite等接口,但是不知道各位老铁还记不记得这些接口得用法,不管各位老铁记不记得,笔者都会在后面内容给各位老铁讲述一下这些接口的使用,相信各位老铁看了一定会有所收获的。

1.在讲述c语言对文件操作接口之前,笔者先和各位老铁达成一些对文件的共识,这样有利于各位老铁对下面内容的理解。

1.各位老铁应该都知道文件是放在磁盘中的,那么空文件放在哪里呢???其实空文件也是放在磁盘中,也需要在磁盘 中占据空间的。

2.文件是等于内容+属性(这个笔者在前面Linux菜鸟级指令的博客中讲过),所以我们可以推出,对文件的操作=对文件内容的操作/对文件属性操作/对文件内容和文件属性的操作

3.标定一个文件,需要文件路径和文件名,如果不指定文件路径,默认是当前路径,举个例子,当我们写好一个.c程序,编译好程序,加载到磁盘中,我们在Linux下运行该程序时,是不是使用./可执行文件名,所以我们就可以得出访问磁盘的文件需要文件路径 +文件名。

4.一个文件如果没有打开,能被访问吗???答案肯定是不能,那么是由谁来打开文件呢?是不是用户的进程下达指令给OS,由OS去打开文件。所以笔者就可以给出一个结论了,我们研究文件操作的本质就是研究进程和被打开文件的关系

2.重谈C语言文件接口

问各位老铁一个问题,除了c语言 ,其他编程语言有对文件操作的接口吗,例如c++,python,java,php等等语言有对文件进行操作的接口吗,答案是肯定有的,那么这么多语言都有文件接口,如果我们去公司工作了需要转语言,那么还需要学习这些接口,学习成本是不是很大,那么我们该如何降低学习成本呢?,我给各位老铁画一幅图,各位老铁就明白了。

(1)文件的打开与关闭

1.fopen接口

2.fclose接口

笔者直接上代码

c 复制代码
#include <stdio.h>
#include <unistd.h>

#define FILE_NAME "log.txt"
int main()
{
    //1.打开文件
    //r:表示只读文件(文件必须存在)
    //w:表示只写文件
    //a:表示追加文件,但是不能读文件(本质还是写,只不过是在文件末尾写入)
    //r+:表示读写文件,如果文件不存在就出错
    //w+:表示读完文件,如果文件不存在就创建文件
    //a+:表示能读文件和追加文件
    //rb:表示只读二进制文件
    //wb:表示只写二进制文件
    //ab:表示追加二进制文件
    FILE* fp=fopen(FILE_NAME,"w");

    if(fp==NULL)
    {
        //打开文件失败
        //perro接口:输出最近一次发生系统调用或者库函数错误,将返回错误码转化为字符串
        perror("打开文件失败:");
        return 1;
    }

    fclose(fp);
    return 0;
}

2.文件的写入

fprintf接口:将特定的文件格式化到特定的文件流中

c 复制代码
#include <stdio.h>
#include <unistd.h>

#define FILE_NAME "log.txt"
int main()
{
    //1.打开文件
    //r:表示只读文件(文件必须存在)
    //w:表示只写文件
    //a:表示追加文件,但是不能读文件(本质还是写,只不过是在文件末尾写入)
    //r+:表示读写文件,如果文件不存在就出错
    //w+:表示读完文件,如果文件不存在就创建文件
    //a+:表示能读文件和追加文件
    //rb:表示只读二进制文件
    //wb:表示只写二进制文件
    //ab:表示追加二进制文件
    FILE* fp=fopen(FILE_NAME,"w");

    if(fp==NULL)
    {
        //打开文件失败
        //perro接口:输出最近一次发生系统调用或者库函数错误,将返回错误码转化为字符串
        perror("打开文件失败:");
        return 1;
    }

    int cnt=5;
    while(cnt)
    {
        fprintf(fp,"%s:%d\n","hello Linux",cnt--);
    }

    fclose(fp);
    return 0;
}

3.文件的读取

fgets:表示从特定的文件流中读取指定字节数的数据到指定的缓冲区中

c 复制代码
#include <stdio.h>
#include <unistd.h>

#define FILE_NAME "log.txt"
int main()
{
    //1.打开文件
    //r:表示只读文件(文件必须存在)
    //w:表示只写文件
    //a:表示追加文件,但是不能读文件(本质还是写,只不过是在文件末尾写入)
    //r+:表示读写文件,如果文件不存在就出错
    //w+:表示读完文件,如果文件不存在就创建文件
    //a+:表示能读文件和追加文件
    //rb:表示只读二进制文件
    //wb:表示只写二进制文件
    //ab:表示追加二进制文件
    FILE* fp=fopen(FILE_NAME,"r");

    if(fp==NULL)
    {
        //打开文件失败
        //perro接口:输出最近一次发生系统调用或者库函数错误,将返回错误码转化为字符串
        perror("打开文件失败:");
        return 1;
    }

    // //写文件
    // int cnt=5;
    // while(cnt)
    // {
    //     fprintf(fp,"%s:%d\n","hello Linux",cnt--);
    // }


    //读文件
    char buffer[64];
    //为什么减一:fgets是c语言接口,c语言字符串读取是以\0为结尾的,所以留一个位置给它
    while(fgets(buffer,sizeof(buffer)-1,fp)!=NULL)
    {
        printf("%s",buffer);
    }

    fclose(fp);
    return 0;
}

4.文件的追加

c 复制代码
#include <stdio.h>
#include <unistd.h>

#define FILE_NAME "log.txt"
int main()
{
    //1.打开文件
    //r:表示只读文件(文件必须存在)
    //w:表示只写文件
    //a:表示追加文件,但是不能读文件(本质还是写,只不过是在文件末尾写入)
    //r+:表示读写文件,如果文件不存在就出错
    //w+:表示读完文件,如果文件不存在就创建文件
    //a+:表示能读文件和追加文件
    //rb:表示只读二进制文件
    //wb:表示只写二进制文件
    //ab:表示追加二进制文件
    FILE* fp=fopen(FILE_NAME,"a");

    if(fp==NULL)
    {
        //打开文件失败
        //perro接口:输出最近一次发生系统调用或者库函数错误,将返回错误码转化为字符串
        perror("打开文件失败:");
        return 1;
    }

    //写文件
    int cnt=5;
    while(cnt)
    {
        fprintf(fp,"%s:%d\n","hello Linux",cnt--);
    }


    // //读文件
    // char buffer[64];
    // //为什么减一:fgets是c语言接口,c语言字符串读取是以\0为结尾的,所以留一个位置给它
    // while(fgets(buffer,sizeof(buffer)-1,fp)!=NULL)
    // {
    //     printf("%s",buffer);
    // }

    fclose(fp);
    return 0;
}

5.细节问题

(1)如果以w方式打开文件,那么每次打开文件都会清空文件

c 复制代码
#include <stdio.h>
#include <unistd.h>

#define FILE_NAME "log.txt"
int main()
{
    //1.打开文件
    //r:表示只读文件(文件必须存在)
    //w:表示只写文件
    //a:表示追加文件,但是不能读文件(本质还是写,只不过是在文件末尾写入)
    //r+:表示读写文件,如果文件不存在就出错
    //w+:表示读完文件,如果文件不存在就创建文件
    //a+:表示能读文件和追加文件
    //rb:表示只读二进制文件
    //wb:表示只写二进制文件
    //ab:表示追加二进制文件
    FILE* fp=fopen(FILE_NAME,"w");

    if(fp==NULL)
    {
        //打开文件失败
        //perro接口:输出最近一次发生系统调用或者库函数错误,将返回错误码转化为字符串
        perror("打开文件失败:");
        return 1;
    }

    // //写文件
    // int cnt=5;
    // while(cnt)
    // {
    //     fprintf(fp,"%s:%d\n","hello Linux",cnt--);
    // }


    // //读文件
    // char buffer[64];
    // //为什么减一:fgets是c语言接口,c语言字符串读取是以\0为结尾的,所以留一个位置给它
    // while(fgets(buffer,sizeof(buffer)-1,fp)!=NULL)
    // {
    //     printf("%s",buffer);
    // }

    fclose(fp);
    return 0;
}


(2)文件的默认权限是664

为什么呢?
Linux文件的默认权限由默认的权限掩码控制的,linux的默认权限掩码是0002

设置为664,去掉其他者的修改权限,其他者只能读文件,原因是为了不让其他者随意修改文件,保证文件的安全。

我们也可以临时修改一下默认的权限掩码

3.文件系统接口

在前面笔者已经讲过编程语言提供对文件的操作接口一定若不过系统调用接口,它的底层一定是系统调用接口,如果我们懂得了系统调用接口,那么我们就相当于掌握了屠龙宝刀了。

笔者在讲文件的系统调用接口之前,先给各位老铁铺垫一个小知识,不知道各位老铁在C语言有没有使用过标记位变量,相信各位老铁肯定会使用过标记位变量,那么各位老铁有没有想过一个问题,OS是如何传标记位来完成对应的动作的,下面笔者通过一段代码来帮助各位老铁理解一下os传标记位的过程。

cpp 复制代码
//1.先定义宏,1左移n位相当于乘以2的n次方
//每一个标记位占一个比特位
#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
void show(int flag)
{
  if(flag&ONE) printf("one\n");
  if(flag&TWO) printf("two\n");
  if(flag&THREE) printf("three\n");
  if(flag&FOUR) printf("four\n");
}
int main()
{
  show(ONE);
  printf("--------------------\n");
  show(TWO);
  printf("--------------------\n");
  show(ONE | TWO);//这里相当于把ONE和TWO对应的标记位同时传过去
  printf("--------------------\n");
  show(ONE | TWO | THREE | FOUR);
  printf("--------------------\n");
  return 0;
}

接下来看看结果是否和我们预期的相符合

结果没毛病,那么我们有了标志位传参的知识的铺垫,那么我们就可以来认识系统文件IO的接口了。

1.open接口

老规矩,先看文档

2.close接口

为了帮助各位老铁看懂open接口和close的使用,笔者上段open接口和close接口的代码给各位老铁看看

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fd=open("log.txt",O_WRONLY);//以只写的方式打开文件
    
    if(fd<0)
    {
        perror("open");
        return 1;
    }

    close(fd);
    return 

现在各位老铁可以看到我项目里面并没有log.txt文件,按照C语言文件接口的使用,只写方式打开文件,如果文件不存在则默认生成一个新的文件,那结果真是这样吗,我们来看看

编译能通过,但是根本不会生成新的文件,这个就证明了c语言文件以只读方式打开如果文件不存在,会默认生成新的文件是c语言提供的,并不是系统提供的。

那么怎么改呢?很简单,加个O_CREAT就行了

cpp 复制代码
int main()
{
    int fd=open("log.txt",O_WRONLY|O_CREAT);//以只写的方式打开文件
    
    if(fd<0)
    {
        perror("open");
        return 1;
    }

    close(fd);
    return 0;
}


这个文件就创建出来了,但是这个文件是乱码的,那么我们就需要回到open文档中了,细心的老铁就会看到文档中open接口还有一个参数,第三个参数就是控制文件创建时的权限。

cpp 复制代码
int main()
{
    int fd=open("log.txt",O_WRONLY|O_CREAT,0666);//以只写的方式打开文件,加上权限
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    close(fd);
    return 0;
}

3.write接口


返回值

可能有老铁看了上面笔者对接口的解释还不理解接口的使用,那没关系,笔者直接上一段简单的代码帮助各位老铁老铁理解上面的接口的使用。

再写代码之前,先为各位老铁介绍一个函数sprintf

sprintf:将特定内容格式化显示到指定字符串中

cpp 复制代码
int main()
{
    int fd=open("log.txt",O_WRONLY|O_CREAT,0666);//以只写的方式打开文件,加上权限
    
    if(fd<0)
    {
        perror("open");
        return 1;
    }

    char outbuffer[1024];//先定义一个缓冲区
    //现在假设我需要写入5行的hello linux
    int cnt=5;
    while(cnt)
    {
        sprintf(outbuffer,"%s:%d\n","hello linux",cnt--);
        //将缓冲区内容写入到文件中
        write(fd,outbuffer,strlen(outbuffer)+1);
    }

    close(fd);
    return 0;
}

这里问各位老铁一个问题,在这里使用strlen函数求字符串长度时,后面需不需要加1.

我们来看看代码结果

咋一看代码结果正确,但是当我们用记事本打开该文件看看呢?

每一行前面有乱码出现,那为什么呢?
其实strlen在c语言中确实是要在后面+1,但是笔者强调过了,现在是系统接口,你语言层面提供的
任何功能都不关系统的事,不信我们把+1删掉看看结果

cpp 复制代码
int main()
{
    int fd=open("log.txt",O_WRONLY|O_CREAT,0666);//以只写的方式打开文件,加上权限
    
    if(fd<0)
    {
        perror("open");
        return 1;
    }

    char outbuffer[1024];//先定义一个缓冲区
    //现在假设我需要写入5行的hello linux
    int cnt=5;
    while(cnt)
    {
        sprintf(outbuffer,"%s:%d\n","hello linux",cnt--);
        //将缓冲区内容写入到文件中
        write(fd,outbuffer,strlen(outbuffer));
    }

    close(fd);
    return 0;
}


再接上面代码,笔者在上面代码基础上再进行修该,然后我们再来看看代码结果

cpp 复制代码
int main()
{
    int fd=open("log.txt",O_WRONLY|O_CREAT,0666);//以只写的方式打开文件,加上权限
    
    if(fd<0)
    {
        perror("open");
        return 1;
    }

    char outbuffer[1024];//先定义一个缓冲区
    //现在假设我需要写入5行的hello linux
    int cnt=5;
    while(cnt)
    {
        sprintf(outbuffer,"%s:%d\n","aaaa",cnt--);//笔者只对这里进行了修改
        //将缓冲区内容写入到文件中
        write(fd,outbuffer,strlen(outbuffer));
    }

    close(fd);
    return 0;
}

代码结果

为什么没有清空文件呢?C语言不是说以w方式打开文件,会自动清空文件吗?那么你C语言是基于什么来说的呢?

其实系统也支持,只要我们加上O_TRUNC参数就可以了

cpp 复制代码
int main()
{
    int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);//以只写的方式打开文件,加上权限
    
    if(fd<0)
    {
        perror("open");
        return 1;
    }

    char outbuffer[1024];//先定义一个缓冲区
    //现在假设我需要写入5行的hello linux
    int cnt=5;
    while(cnt)
    {
        sprintf(outbuffer,"%s:%d\n","aaaa",cnt--);//笔者只对这里进行了修改
        //将缓冲区内容写入到文件中
        write(fd,outbuffer,strlen(outbuffer));
    }

    close(fd);
    return 0;
}

我们再来看看代码结果

c语言的以w方式打开文件对应系统是如何做的呢?

"w"--->O_WRONLY | O_CREAT | O_TRUNC , 0666

那么文件是如何追加呢?

很简单,加个O_APPEND参数就可以了

cpp 复制代码
int main()
{
    int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);//以只写的方式打开文件,加上权限
    
    if(fd<0)
    {
        perror("open");
        return 1;
    }

    char outbuffer[1024];//先定义一个缓冲区
    int cnt=5;
    while(cnt)
    {
        sprintf(outbuffer,"%s:%d\n","bbbb",cnt--);//笔者只对这里进行了修改
        //将缓冲区内容写入到文件中
        write(fd,outbuffer,strlen(outbuffer));
    }

    close(fd);
    return 0;
}

4.read接口

返回值

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    //O_RDONLY:表示读取
    int fd=open("log.txt",O_RDONLY,0666);//以只写的方式打开文件,加上权限
    
    if(fd<0)
    {
        perror("open");
        return 1;
    }

    char buffer[1024];
    ssize_t num=read(fd,buffer,sizeof(buffer)-1);//减一是为了留一个位置给\0
    //读取成功
    if(num>0) buffer[num]=0;//给读取到的字符串添加上\0
    printf("%s",buffer);

    // char outbuffer[1024];//先定义一个缓冲区
    // //现在假设我需要写入5行的hello linux
    // int cnt=5;
    // while(cnt)
    // {
    //     sprintf(outbuffer,"%s:%d\n","bbbb",cnt--);//笔者只对这里进行了修改
    //     //将缓冲区内容写入到文件中
    //     write(fd,outbuffer,strlen(outbuffer));
    // }

    close(fd);
    return 0;
}

4.C库对文件进行操作的接口和系统对文件操作的接口关系

5.文件描述符

细心的老铁会发现对于上面的接口都是用fd表示被打开的文件,那么为什么是用fd来表示被打开的文件呢?

我来给各位老铁做一个推导链
进程可以打开多个文件吗(可以)--->系统内一定会存在大量的被打开文件--->被打开的文件也一定要被OS进行管理--->OS通过先描述再组织的方法进行管理文件,那么OS一定会为文件创建对应的内核数据结构来标识文件--->也就是struct file {}--->这里面一定会包含文件的大部分属性,--->我们在上面看到的fd就是用来标识文件的(称为文件描述符)