Linux系统文件编程

Linux文件编程

1、关于文件编程

(1)文件的操作:

一般的操作步骤:打开/创建文件 -> 读取/写入文件 -> 关闭文件

  • 在Linux中要操作一个文件,一般是先open打开一个文件,得到文件描述符,然后对文件进行读写操作(或其它操作),最后是close关闭文件。
  • 强调一点:对文件进行操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后面的操作了,最后读写完成后,一定要关闭文件,否则可能造成文件损坏。
  • 文件平时是存放在块设备中的文件系统文件中的,这种文件叫静态文件。当我们去open打开一个文件时,Linux内核的操作包括:内核在进程中建立一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(叫动态文件)。
  • 打开文件以后,对文件的读写操作都是针对内存中的这份动态文件的,而非针对静态文件。当我们对动态文件进行读写以后,此时内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部将内存中的动态文件的内容去更新(同步)块设备中的静态文件。
  • 为什么不直接对块设备操作:块设备本身读写非常不灵活,是按照块读写的,而内存是按字节单位操作的,并且可以随机操作,相对来说很灵活。

关于文件操作,操作系统提供了一系列的API,如Linux系统:

编号作用API
1打开open
2关闭close
3读写write /read
4光标定位lseek

(2)文件描述符:fd(File descriptor)

我们知道在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。在操作这些所谓的文件的时候,我们每操作一次就找一次名字,这会耗费大量的时间和效率。所以Linux中规定每一个文件对应一个索引,这样要操作文件的时候,我们直接找到索引就可以对其进行操作了。文件描述符就是内核为了高效管理这些已经被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符来实现。

什么是文件描述符:

  • 文件描述符是一个非负整数,本质上是一个索引值(这句话非常重要)。在Linux操作系统中,文件描述符是一种用于访问文件或输入/输出资源的抽象概念,它是为了更有效地管理和操作文件、设备、套接字等资源而引入的。
  • 当打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符( open系统调用得到 ),后续 read、write这个文件时,则只需要用这个文件描述符来标识该文件,将其作为参数传入 read、write。
  • 在 POSIX 语义中,0,1,2 这三个 fd 值已经被赋予特殊含义,分别是标准输入( STDIN_FILENO ),标准输出( STDOUT_FILENO ),标准错误( STDERR_FILENO )。这意味着我们此时打开一个新的文件时,它的文件描述符会是3,再打开一个文件的文件描述符是4…

(3)打印文件描述符

根据上述描述,我们知道open一个文件后会返回一个文件描述符,那我们不妨将文件描述符打印出来看看规律:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    int fd1;
    int fd2;
    int fd3;

    fd1 = open("./test1.c",O_RDWR);
    fd2 = open("./test2.c",O_RDWR);
    fd3 = open("./test3.c",O_RDWR);

    printf("fd1 = %d\n",fd1);
    printf("fd2 = %d\n",fd2);
    printf("fd3 = %d\n",fd3);
    return 0;
}

/*运行结果
fd1 = 3
fd2 = 4
fd3 = 5*/

在这里插入图片描述
​ 从上述打印的结果观察出,fd的值是从3开始,依次往上加一递增的。那么问题来了:为什么从3开始呢?这就是在文件描述符的介绍中提到的:当我们运行程序时,系统会默认帮我们打开标准输入0、标准输出1、标准错误2。如果返回负数则说明打开文件失败。

2、Linux系统中操作文件的API介绍

(1)打开/创建文件

open():给定一个文件的路径名,open()返回一个文件描述符,一个小的非负整数,用于后续的系统调用(read(2), write(2), lseek(2), fcntl(2)等)。成功调用返回的文件描述符将是当前未为进程打开的编号最低的文件描述符。

/*头文件*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*open的函数原型,返回一个文件描述符:一个小的非负整数*/
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

参数说明:

  1. Pathname: 要打开的文件名(含路径,缺省为当前路径)
  2. Flags: O_RDONLY 只读打开 O_WRONLY 只写打开 O_RDWR 可读可写打开
    当我们附带了权限后,打开的文件就只能按照这种权限来操作。
    以上这三个常数中应当只指定一 个。下列常数是可选择的:
    • O_CREAT 若文件不存在则创建它。使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限。
    • O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。
    • O_APPEND 每次写时都加到文件的尾端。
    • O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0。
  3. Mode: 一定是在flags中使用了O_CREAT标志才能使用,mode记录待创建的文件的访问权限:只读、只写、可读可写。

代码示例1:打开文件

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

/*int open(const char *pathname, int flags);*/
int main()
{
        int fd;//函数描述符
   	 	/*以可读可写的权限打开当前路径下名为test.4的文件*/
        fd = open("./test4.c",O_RDWR);
        if(fd == -1){
            	/*如果函数描述符返回值小于0则说明打开文件失败*/
                printf("open file fail\n");
        }else{
                /*如果函数描述符返回值大于0则说明打开文件成功*/
                printf("open file success\n");
        }
        return 0;
}

在这里插入图片描述
代码示例2:在没有文件的情况下创建文件然后打开

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

/*int open(const char *pathname, int flags, mode_t mode);*/
int main()
{
        int fd;
        fd = open("./test4.c",O_RDWR);
        if(fd == -1){
                printf("open file fial\n");
            	/*打开文件失败后创建一个文件权限是可读可写*/
                fd = open("./test4.c",O_RDWR|O_CREAT,0600);
                if(fd > 0){
                        printf("create file success!\n");
                }
        }
        return 0;
}
/*O_CREAT 若文件不存在则创建它。使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限。
mode 0 6 0 0(6 = 4+2,即可读+可写权限,0同组用户,0其它组用户) 
可读r   4  
可写w   2
执行x   1*/

在这里插入图片描述
open可以打开/创建文件,还有一个函数也可以创建函数,和open用法类似(这里不做过多展示),那就是creat函数:
在这里插入图片描述

(2)写入文件

**write():**write()从指向的缓冲区向文件描述符fd引用的文件写入最多count个字节。

/*头文件*/
#include <unistd.h>
/*write的函数原型*/
ssize_t write(int fd, const void *buf, size_t count);

参数说明:

  1. fd: 根据文件描述符打开对应的文件
  2. buf: 向文件缓冲区写入内容
  3. count: 写入最多count个字节

代码示例:

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

 // ssize_t write(int fd, const void *buf, size_t count);
int main()
{
    int fd;
    char *buf = "Tomorrow will be better!";//buf中存放的内容
    fd = open("./test4.c",O_RDWR);
    if(fd == -1){
        printf("open file fial\n");
        fd = open("./test4.c",O_RDWR|O_CREAT,0600);
        if(fd > 0){
            printf("create file success!\n");
        }
    }
    write(fd,buf,strlen(buf));//向文件中写入buf
    close(fd);//关闭文件
    return 0;
}

运行后打开test4.c文件发现文件中已经有了我们在buf中存放的内容:
在这里插入图片描述

(3)读取文件

read():read ( )尝试从文件描述符fd中读入以buf为起点的缓冲区进行字节计数。如果count为零,则read ( )返回0。如果count大于SSIZE _ MAX,则结果不指定。

/*头文件*/
#include <unistd.h>
/*read()函数原型*/
ssize_t read(int fd, void *buf, size_t count);

参数说明:

  1. fd: 根据文件描述符打开对应的文件
  2. buf: 读取缓冲区的内容
  3. count: 读取最多count个字节
    代码示例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

 // ssize_t read(int fd, void *buf, size_t count);
int main()
{
    int fd;
    char *buf = "Tomorrow will be better!";
    fd = open("./new.c",O_RDWR);
    if(fd == -1){
        printf("open file fial\n");
        fd = open("./new.c",O_RDWR|O_CREAT,0600);
        if(fd > 0){
            printf("create file success!\n");
        }
    }
    int n_write = write(fd,buf,strlen(buf));
    if(n_write != 1){
        /*向文件写入n_write个字节*/
        printf("write %d byte to file\n",n_write);
    }
	/*给读取的缓冲区开辟内存空间*/
    char *readBuf = (char *)malloc(sizeof(char)*n_write + 1);
    int n_read = read(fd,readBuf,n_write);
    /*打印读取的字节个数和读取的内容*/
    printf("read:%d, context:%s\n",n_read,readBuf);
    close(fd);
    return 0;
}

如果向文件中写完内容就直接读取,那么可能出现写入成功但读取到的内容为空的情况:这里涉及文件光标的问题,写入后内容后光标停留在写入的内容之后,直接读取是从光标往后读取,所以读取到的内容为空。

在这里插入图片描述

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

int main()
{
    int fd;
    char *buf = "Tomorrow will be better!";
    fd = open("./new.c",O_RDWR);
    if(fd == -1){
        printf("open file fial\n");
        fd = open("./new.c",O_RDWR|O_CREAT,0600);
        if(fd > 0){
            printf("create file success!\n");
        }
    }
    int n_write = write(fd,buf,strlen(buf));
    if(n_write != 1){
        printf("write %d byte to file\n",n_write);
    }
    
    close(fd);//写入成功后先关闭文件
    fd = open("./new.c",O_RDWR);//然后打开文件让光标移动到文件初始位置
    
    char *readBuf = (char *)malloc(sizeof(char)*n_write + 1);
    int n_read = read(fd,readBuf,n_write);
    printf("read:%d, context:%s\n",n_read,readBuf);
    close(fd);
    return 0;
}

这里暂时使用写入内容后先关闭文件然后在重新打开文件让光标移到文件开始的位置这种方法解决问题,下面介绍移动光标lseek()函数时会用重新定位光标的方法解决问题。

在这里插入图片描述

(4)移动光标

lseek():重新定位读/写文件的偏移量。

/*头文件*/
#include <sys/types.h>
#include <unistd.h>
/*函数原型:将文件读写指针相对whence移动offset个字节,返回偏移的字节数*/
off_t lseek(int fd, off_t offset, int whence);

参数说明:

  1. fd: 根据文件描述符打开对应的文件
  2. offset: 设置的偏移量
  3. whence: 相对当前光标位置

函数将与文件描述符fd相关联的open文件的偏移量根据下面的语句重新定位到参数设置的偏移量:

  • SEEK _ SET:偏移量设置为偏移字节数。
  • SEEK _ CUR:偏移量设置为其当前位置加上偏移字节。
  • SEEK _ END:偏移量设置为文件的大小加上偏移字节。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

//off_t lseek(int fd, off_t offset, int whence);
int main()
{
    int fd;
    char *buf = "Tomorrow will be better!";
    fd = open("./new.c",O_RDWR);
    if(fd == -1){
        printf("open file fial\n");
        fd = open("./new.c",O_RDWR|O_CREAT,0600);
        if(fd > 0){
            printf("create file success!\n");
        }
    }
    int n_write = write(fd,buf,strlen(buf));
    if(n_write != 1){
        printf("write %d byte to file\n",n_write);
    }
	/*移动光标到文件的初始位置*/
    lseek(fd,0,SEEK_SET);
    /*相对当前光标位置向前移动光标n_write个字节*/
    //lseek(fd,-n_write,SEEK_CUR);这里注意向前移动光标要用负的字节数
    /*相对当前文件最后的位置向前移动光标n_write个字节*/
    //lseek(fd,-n_write,SEEK_END);这里注意向前移动光标要用负的字节数

    char *readBuf = (char *)malloc(sizeof(char)*n_write + 1);
    int n_read = read(fd,readBuf,n_write);
    printf("read:%d, context:%s\n",n_read,readBuf);
    close(fd);
    return 0;
}

对于上一节中向文件中写入内容后光标在文件末尾的问题,这节使用移动光标的方法解决,代码中三种移动光标的方法都可以读取到文件中的内容,要注意的是向前移动光标要用负的字节数

在这里插入图片描述

(5)关闭文件

close():Close ( )关闭一个文件描述符,使其不再指向任何文件,并且可以被重复使用。

/*头文件*/
#include <unistd.h>
/*函数原型,成功关闭文件返回值为0,失败则返回-1*/
int close(int fd);

3、文件操作应用

(1)实现cp复制文件的操作指令

复制文件的cp指令:cp src.c des.c
cp 是复制文件的指令,src.c是原文件,des.c是复制后的目标文件,这三个都是参数。

复制文件的思路:

  • 先打开原文件src.c
  • 读取src.c的内容到buf
  • 打开/创建des.c文件
  • 将buf写入到des.c文件中
  • 关闭两个文件

在实现复制文件的操作之前,先了解一下针对参数的测试代码:

  • int main(int argc,char** argv)
  • argc 命令行输入参数的个数 (以空白符为分隔)
  • argv 存储了所有命令行参数 ,以NULL 结束
#include <stdio.h>

int main(int argc, char** argv)
{
	printf("parameters number:%d\n",argc);
	printf("NO.1 parameter :%s\n",argv[0]);
	printf("NO.2 parameter :%s\n",argv[1]);
	printf("NO.3 parameter :%s\n",argv[2]);
	return 0;
}
/*运行结果
./a.out src.c des.c
parameters number:3
NO.1 parameter :./a.out
NO.2 parameter :src.c
NO.3 parameter :des.c
*/

实现文件复制:

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

int main(int argc, char **argv)
{
    int fdSrc;
    int fdDes;
    char *bufSrc = NULL;

    /*先判断是否是三个参数,如果不是就推出程序*/
    if(argc != 3){
        printf("paramete error\n");
        exit(-1);
    }
	
    /*先打开原文件,返回文件描述符fdSrc*/
    fdSrc = open(argv[1],O_RDWR);
     /*用lseek函数获取原文件中的字节数*/
    int size = lseek(fdSrc,0,SEEK_END);
    /*给读取缓存区开辟内存空间,大8个字节*/
    bufSrc = (char*)malloc(sizeof(char)*size + 8);
    /*读完字节后要让光标重新移动到文件初始位置,不然什么也读不到*/
    lseek(fdSrc,0,SEEK_SET);
    /*读取文件缓存区的内容*/
    int n_read = read(fdSrc,bufSrc,size);

	/*打开/创建一个目标文件,这里使用O_TRUNC要将目标文件中之前的内容先全部删除然后在写入*/
    fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);
    /*向目标文件中写入读取的缓存区内容*/
    int n_write = write(fdDes,bufSrc,strlen(bufSrc));

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

编译运行test7.c文件后生成一个绿色的mycp可执行文件,然后用mycp去实现将test7.c文件的内容复制并创建到test8.c文件中:
在这里插入图片描述

然后使用vimdiff函数(vimdiff + 文件1 + 文件2)对比两个文件,发现文件内容完全相同:

在这里插入图片描述

(2)修改配置文件的内容

修改配置文件的思路:

  • 先打开原文件src.c
  • 读取src.c的内容到buf
  • 找到要修改的内容
  • 先将指针地址移到对应位置前,然后在修改内容
  • 关闭文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

char *changeConfig(char *buf,char *str)
{		
		/*先找到文件中要修改的内容*/
        char *p = strstr(buf,str);
        if(p == NULL){
                printf("This file doesn't the string\n");
                exit(-1);
        }else{
            	/*先移动指针位置然后在修改*/
                p  = p + strlen("LENG = ");
                *p = '9';
        }
        return p;
}

int main(int argc, char **argv)
{
    int fdSrc;
    char *bufSrc = NULL;

    if(argc != 2){
        printf("paramete error\n");
        exit(-1);
    }
    /*打开目标文件,返回一个文件描述符*/
    fdSrc = open(argv[1],O_RDWR);
    /*获取文件的字节大小,然后给缓存区开辟对应大小的内存*/
    int size = lseek(fdSrc,0,SEEK_END);
    bufSrc = (char*)malloc(sizeof(char)*size + 8);
    /*将光标位置移到文件初始位置,然后将文件内容读取到缓存区*/
    lseek(fdSrc,0,SEEK_SET);
    int n_read = read(fdSrc,bufSrc,size);
	/*修改配置文件的内容*/
    char *ret = changeConfig(bufSrc,"LENG = ");
	/*再将光标移动到文件初始位置开始写入*/
    lseek(fdSrc,0,SEEK_SET);
    int n_write = write(fdSrc,bufSrc,strlen(bufSrc));
	/*关闭文件*/
    close(fdSrc);
    return 0;
}

修改前和修改后:

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

(3)写入内容到文件

根据上文描述read和write函数中的缓存区是一个无类型的指针,所以我们不仅可以写入字符串,也可以写入其他内容,如整数、结构体等,只要将地址发送到缓存区就行。

  1. 写入一个整数:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

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

        int data = 100;
        int data2;
        if(argc != 2){
                printf("paramete error\n");
                exit(-1);
        }
		
        fdSrc = open(argv[1],O_RDWR);
        int n_write = write(fdSrc,&data,sizeof(int));

        lseek(fdSrc,0,SEEK_SET);
        int n_read = read(fdSrc,&data2,sizeof(int));
        printf("data2 = %d\n",data2);
        close(fdSrc);
        return 0;
}
/*运行结果
data2 = 100*/
  1. 写入一个结构体:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

struct Test{
    int a;
    char c;
};

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

    struct Test data = {100,'b'};
    struct Test data2;
    if(argc != 2){
        printf("paramete error\n");
        exit(-1);
    }

    fdSrc = open(argv[1],O_RDWR);
    int n_write = write(fdSrc,&data,sizeof(struct Test));

    lseek(fdSrc,0,SEEK_SET);
    int n_read = read(fdSrc,&data2,sizeof(struct Test));
    printf("data2.a = %d, data2.c = %c\n",data2.a,data2.c);
    close(fdSrc);
    return 0;
}
/*运行结果
data2.a = 100, data2.c = b*/
  1. 写入一个结构体数组到文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

struct Test{
    int a;
    char c;
};

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

    struct Test data[2] ={{100,'b'},{111,'g'}};
    struct Test data2[2];
    if(argc != 2){
    printf("paramete error\n");
    exit(-1);
    }

    fdSrc = open(argv[1],O_RDWR);
    int n_write = write(fdSrc,&data,sizeof(struct Test)*2);

    lseek(fdSrc,0,SEEK_SET);
    int n_read = read(fdSrc,&data2,sizeof(struct Test)*2);
    printf("data2[0].a = %d, data2[0].c = %c\n",data2[0].a,data2[0].c);
    printf("data2[1].a = %d, data2[1].c = %c\n",data2[1].a,data2[1].c);
    close(fdSrc);
    return 0;
}

/*运行结果
data2[0].a = 100, data2[0].c = b
data2[1].a = 111, data2[1].c = g*/

在文件中的写入的内容人眼看起来有些乱码,但电脑阅读是正确的值:

在这里插入图片描述
关于文件操作的函数,除了Linux系统中的相关函数,还有标准C库文件中的一些函数:fopen,flocse,fread,fwrite等,具体使用方法和区别可以参考:
open和fopen的区别
C库文件函数操作详解

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值