Linux基础之文件编程

文件编程篇

  • 一、文件的打开及创建
  • 二、文件写入操作
  • 三、文件读取操作和文件光标移动操作
  • 四、文件打开创建的补充
  • 五、文件操作原理概述
  • 六、文件操作小应用之实现cp指令
  • 七、文件编程小应用之修改程序的配置文件
  • 八、读写操作补充
  • 九、标准C库对文件操作引入
  • 十、标准C库打开创建文件读写文件光标移动
  • 十一、文件的其他函数补充

一、文件的打开及创建

文件打开函数原型:

 int open(const char *pathname, int flags);
 int open(const char *pathname, int flags, mode_t mode);

1)pthname:要打开的文件名(含路径,缺省为当前路径)

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

2)当我们附带了权限后,打开的文件就只能按照这种权限来操作。
以上这三个常数中应当只指定一个。下列常数是可选择的:

O_CREAT 若文件不存在则创建它。使用此选项时,需同时说明第三个参数mode,用其说明该新文件的存取许可权限。

O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。

O_APPEND 每次写时都加到文件尾端。

O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或者只写成功打开,则将其长度截短为0。

3)Mode:一定是在flags中使用了O_CREAT标志,mode记录待创建的文件的访问权限

#include 
#include 
#include 
#include

int main()
{
    int fd;
    fd = open("./file",O_RDWR);
    if(fd == -1){
         printf("打开文件失败!\n");
         fd = open("./file",O_RDWR|O_CREAT,0600);
         if(fd > 0){
              printf("创建文件成功!\n");
         }
    }

    return 0;
}

运行结果:
在这里插入图片描述

  • 定义了一个fd来接收open函数的返回值
  • open函数使用成功返回大于0的正数,失败返回-1**

二、文件写入操作

文件写入函数原型:

ssize_t write(int fd, const void *buf, size_t count);

第一个参数是文件标示符fd,将要写入到该文件中去
第二个参数是一个无类型的指针,也就是要写入的内容
第三个参数是要写入的大小

#include 
#include 
#include 
#include 
#include 
#include

int main()
{
        int fd;
        char *buf = "i am handsome!";
        fd = open("./file",O_RDWR);
        if(fd == -1){
                printf("打开文件失败!\n");
                fd = open("./file",O_RDWR|O_CREAT,0600);
                if(fd > 0){
                        printf("创建文件成功!\n");
                }
        }
        printf("fd的返回值是:%d\n",fd);

        write(fd,buf,strlen(buf));
        close(fd);
        return 0;
}

  • 很显然,我们成功的创建了file文件,并且把定义的buf字符串指针的内容写入到了file文件当中去了。

三、文件读取操作和文件光标移动操作

1.文件读取操作
文件读取函数原型:

ssize_t read(int fd, void *buf, size_t count);

第一个参数是需要读取的fd指向的文件
第二个参数是获取到读到数据的内容
第三个参数是需要读取数据的大小

#include 
#include 
#include 
#include 
#include 
#include
#include

int main()
{
        int fd;
        char *buf = "i am handsome!";
        fd = open("./file",O_RDWR);
        if(fd == -1){
                printf("打开文件失败!\n");
                fd = open("./file",O_RDWR|O_CREAT,0600);
                if(fd > 0){
                        printf("创建文件成功!\n");
                }
        }
        printf("fd的返回值是:%d\n",fd);

        int n_write = write(fd,buf,strlen(buf));
        if(n_write != -1){
                printf("write的返回值是:%d\n",n_write);
        }
        lseek(fd,0,SEEK_SET);//移动光标
        char *readbuf;
        readbuf = (char *)malloc(sizeof(readbuf) * n_write+1);
        int n_read = read(fd,readbuf,n_write);
        printf("read的返回值是:%d \n读取到的内容是readbuf:%s\n",n_read,readbuf);
        close(fd);
        return 0;
}

运行结果:
Linux基础之文件编程_第1张图片
1)write的返回值是写入数据的字节大小,写入失败返回-1

2)read的返回值是读取到数据字节的大小,读取失败返回-1

3)代码中读取数据之前使用了移动光标函数lseek,因为写完之后,光标的位置已经到数据的尾端了,读之前不把文件的光标移动到数据的最前面,就会读不到数据

2.文件光标移动操作

1)文件光标移动函数原型:

off_t lseek(int fd, off_t offset, int whence);

2)第一个参数是:需要移动的fd指向的文件
第二个参数是光标移动的偏移值
第三个参数是光标的位置

3)光标的位置有三种:SEEK_SET 文件头的位置
SEEK_END 文件尾巴的位置
SEEK_CUR 默认操作前光标的当前位置

#include
#include 
#include 
#include 
#include 


int main()
{
        int fd;
        fd = open("./file",O_RDWR);

        int fileSize = lseek(fd,0,SEEK_END);
        printf("光标移动函数lseek的返回值是:%d\n",fileSize);

        return 0;
}

运行结果:
在这里插入图片描述

  • 光标移动函数lseek的返回值是针对于文件头偏移了多少个字节,这也很巧妙的计算出了文件的大小

四、文件打开创建的补充

1.O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错

#include
#include 
#include 
#include 
#include 

int main()
{
        int fd;
        fd = open("./file",O_RDWR|O_CREAT|O_EXCL,0600);
        if(fd == -1){
                printf("文件存在!\n");
        }

        return 0;
}

  • 输出的结果是:文件存在,出错返回的是-1

2.O_APPEND 每次写时都加到文件尾端

#include 
#include 
#include 
#include 
#include 
#include

int main()
{
    int fd;
    char *buf = "i am handsome!";
    fd = open("./file",O_RDWR|O_APPEND);
    if(fd == -1){
         printf("打开文件失败!\n");
    }

    int n_write = write(fd,buf,strlen(buf));
    if(n_write != -1){
        printf("在文件尾端写入成功!\n写入的数据是:%s\n",buf);
    }

    close(fd);
    return 0;
}

运行结果:
在这里插入图片描述
Linux基础之文件编程_第2张图片

3.O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或者只写成功打开,则将其长度截短为0

#include 
#include 
#include 
#include 
#include 
#include

int main()
{
        int fd;
        char *buf = "i have never meet such a good girl";
        fd = open("./file",O_RDWR|O_TRUNC);
        if(fd == -1){
                printf("打开文件失败!\n");
        }

        int n_write = write(fd,buf,strlen(buf));
        if(n_write != -1){
                printf("把文件里原有的内容全覆盖\n写入的数据是:%s\n",buf);
        }

        close(fd);
        return 0;
}

运行结果:
在这里插入图片描述
Linux基础之文件编程_第3张图片

4.创建文件creat函数

int creat(const char *pathname, mode_t mode); 

第一个参数是:要创建的文件名(包括路径,缺省为当前路径)
第二个参数是:创建模式

常见的创建模式:
S_IRUSR 可读

S_IWUSR 可写

S_IXUSR 可执行

S_IRWXU 可读可写可执行

1)在linux中要操作一个文件,一般要先open打开一个文件,得到文件描述符,然后对文件进行读写操作(或其他操作),最后是close关闭文件即可

2)强调一点:我们对文件操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后面的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏

3)文件平时是存放在块设备中的文件系统文件中的,我们把这种文件叫做静态文件,当我们去open打开一个文件时,linux内核做的操作包括:内核在进程中建立一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(叫动态文件)

4)打开文件以后,以后对这个文件的读取操作,都是针对内存中的这一份动态文件,而并不是针对静态文件的。当然我们对动态文件进行读写以后,此时内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件

5)为什么这么设计,不直接对块设备直接操作?
块设备本身读写非常不灵活,是按块读写的,而内存是按字节单位操作的,而且可以随机操作,很灵活

五、文件操作原理概述

1.文件描述符

1)对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件获创建一个新文件时,内核向文件返回一个文件描述符。当读写一个文件时,用open和creat返回的文件描述符标识该文件,将其作为参数传递给read和write。
按照惯例,UNIX shell使用文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合,文件描述符2与标准错误输出相结合。STDIN_FILENO丶STDOUT_FILENO丶STDERR_FILENO这几个宏代替了0丶1丶2这几个数

2)文件描述符,这个数字在一个进程中表示一个特定含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果要操作这个动态文件,只需要用这个文件描述符区分

3)文件描述符的作用域就是当前进程,除了这个进程文件描述符就没有意义了
open函数打开文件,打开成功返回一个文件描述符,打开失败,返回-1

2.标准输入0和标准输出1

#include 
#include 
#include 
#include 
#include 
#include

int main()
{
    char readBuf[128];
    printf("请输入你的数据:\n");
    read(0,readBuf,13);
    write(1,readBuf,13);
    putchar('\n');
    return 0;
}

运行结果:
Linux基础之文件编程_第4张图片

六、文件操作小应用之实现cp指令

1.C语言参数

首先主函数 int main(int argc,char **argv)要补充完整
第一个参数表示参数的总个数
第二个参数是一个数组指针

举个例子:

#include
int main(int argc,char **argv)
{
    printf("参数的总个数为:%d\n",argc);
    printf("第一个参数为:%s\n",argv[0]);
    printf("第二个参数为:%s\n",argv[1]);
    printf("第三个参数为:%s\n",argv[2]);
    return 0;
}

运行结果:
Linux基础之文件编程_第5张图片

2.思路
1)打开原文件

3)读取原文件的内容

3)打开创建目标文件

4)把读取原文件的内容写入目标文件

5)关闭两个文件

3.自己实现cp指令:

#include 
#include 
#include 
#include 
#include 
#include
#include

int main(int argc,char **argv)
{
    int fdSrc;
    int fdDes;
    char *readBuf = NULL;
    if(argc != 3){
       printf("参数不为3,错误!\n");
       exit(-1);
    }
    fdSrc = open(argv[1],O_RDWR);
    int size = lseek(fdSrc,0,SEEK_END);
    lseek(fdSrc,0,SEEK_SET);
    
    readBuf = (char *)malloc(sizeof(char) * size +8);
    int n_read = read(fdSrc,readBuf,size);
    fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);
    int n_write = write(fdDes,readBuf,strlen(readBuf));

    close(fdSrc);
    close(fdDes);
    return 0;
}

运行结果:
Linux基础之文件编程_第6张图片

七、文件编程小应用之修改程序的配置文件

#include 
#include 
#include 
#include 
#include 
#include
#include

int main(int argc,char **argv)
{
    int fdSrc;
    char *readBuf;

    if(argc != 2){
       printf("参数数据不正确!\n");
       exit(-1);
    }
    
    fdSrc = open(argv[1],O_RDWR);
    int size = lseek(fdSrc,0,SEEK_END);
    lseek(fdSrc,0,SEEK_SET);
    
    readBuf = (char *)malloc(sizeof(char) * size +8);
    int n_read = read(fdSrc,readBuf,size);

    char *p = strstr(readBuf,"SHI=");//指针p接收到的是查到字符串的地址的最前面字符的位置
    if(p == NULL){
      printf("没找到!\n");
      exit(-1);
    }
    p = p + strlen("SHI=");//p为字符最前面的位置,加上该字符的字节数,相当于指针移到了该字符的尾端
    *p = '5';
    
    lseek(fdSrc,0,SEEK_SET);
    int n_write = write(fdSrc,readBuf,strlen(readBuf));
    
    close(fdSrc);

    return 0;
}

运行结果:
Linux基础之文件编程_第7张图片

八、读写操作补充

写入操作函数原型:

ssize_t write(int fd, const void *buf, size_t count);

读取操作函数原型:

ssize_t read(int fd, void *buf, size_t count);
  • 注意他们的第二个参数,是无类型的指针,所以不局限于只读写字符的操作,还可以是读写,整型数,结构体,结构体数组等操作。

九、标准C库对文件操作引入

1.先总结open与fopen的区别

对于这两个名字很类似的函数,对于很多初学者来说,不容易搞清楚它们有什么不同,只知道按照函数用法使用。如果能很好的区分两者,相信大家对于C语言和UNIX系统(包括LINUX)有更深入的了解。

在网上查找了一些资料,但是感觉不够全面,一些答案只是从某个角度阐述,所以让人觉得,这个也对,那个也对。但到底谁的表述更正确呢?其实都是对的,只是解释的视角不同罢了。下面结合个人的理解做一些梳理。

来源
从来源的角度看,两者能很好的区分开,这也是两者最显而易见的区别:open是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。

fopen是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api。返回的是一个指向文件结构的指针。

PS:从来源来看,两者是有千丝万缕的联系的,毕竟C语言的库函数还是需要调用系统API实现的。

2. 移植性
这一点从上面的来源就可以推断出来,fopen是C标准函数,因此拥有良好的移植性;

open是UNIX系统调用,移植性有限。如windows下相似的功能使用API函数CreateFile

3. 适用范围
open返回文件描述符,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作。如网络套接字、硬件设备等。当然包括操作普通正规文件(Regular File)。
fopen是用来操纵普通正规文件(Regular File)的。

4. 文件IO层次
如果从文件IO的角度来看,前者属于低级IO函数,后者属于高级IO函数。低级和高级的简单区分标准是:谁离系统内核更近。低级文件IO运行在内核态,高级文件IO运行在用户态。

5. 缓冲

缓冲文件系统:
缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用;当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读出需要的数据。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器 而定。fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等。

非缓冲文件系统:
缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数据。非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度快,由于ANSI标准不再包括非缓冲文件系统,因此建议大家最好不要选择它。open, close, read, write, getc, getchar, putc, putchar等。

一句话总结一下,就是open无缓冲,fopen有缓冲。前者与read, write等配合使用, 后者与fread,fwrite等配合使用。
使用fopen函数,由于在用户态下就有了缓冲,因此进行文件读写操作的时候就减少了用户态和内核态的切换(切换到内核态调用还是需要调用系统调用API:read,write);而使用open函数,在文件读写时则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列的函数快;如果随机访问文件则相反。

  • 这样一总结梳理,相信大家对于两个函数及系列函数有了一个更全面清晰的认识,也应该知道在什么场合下使用什么样的函数更合适,效率更高。

十、标准C库打开创建文件读写文件光标移动

1.标准C库打开文件操作函数原型:

FILE *fopen(const char *pathname, const char *mode);

第一个参数是:要打开的文件名
第二个参数是:打开文件的权限

2.标准C库读取操作函数原型:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

第一个参数是:接收读取的内容
第二个参数是:读取的字节大小
第三个参数是:读取的次数
第四个参数是:文件描述符

3.标准C库写入操作函数原型:

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

第一个参数是:接收读取的内容
第二个参数是:读取的字节大小
第三个参数是:读取的次数
第四个参数是:文件描述符


#include
#include

int main()
{
    FILE *fp;
    char *str = "i am handsome";
    char readBuf[128] = {0};
   
    fp = fopen("./file","w+");
    //int nwrite = fwrite(str,sizeof(char),strlen(str),fp);
    int nwrite = fwrite(str,sizeof(char) * strlen(str),3,fp);
    fseek(fp,0,SEEK_SET);
    //int nread = fread(readBuf,sizeof(char),strlen(str),fp);
    int nread = fread(readBuf,sizeof(char) * strlen(str),4,fp);
    printf("读取到的数据是:%s\n",readBuf);
    printf("fwrite的返回值是:%d\n",nwrite);
    printf("fread的返回值是:%d\n",nread);

    fclose(fp);
    
    return 0;
}

运行结果:
Linux基础之文件编程_第8张图片
注意:

  • fwrite的返回值是取决于第三个参数,而fread不一样,当写的次数要大于读的次数的时候,fread返回的是第三个参数的值。反之,返回写入的第三个参数的值。

十一、文件的其他函数补充

1)写入文件操作函数原型

int fputc(int c, FILE *stream);

第一个参数是要写入的内容
第二个参数是文件描述符

2)取出文件操作函数原型:

int fgetc(FILE *stream);

参数是文件描述符,返回值是取出文件的内容

3)判断是否到达文件尾巴函数原型:

int feof(FILE *stream);

你可能感兴趣的:(Linux基础,linux,运维)