Linux系统编程_文件编程

发布于:2024-04-25 ⋅ 阅读:(17) ⋅ 点赞:(0)

一.文件编程

修改一个文件一般需要:打开/创建文件–>编辑文档–>保存文档–>关闭文档。

自动化完成文件的编写,操作系统提供了一系列的API。

如Linux系统:

  • 打开:open
  • 读写:read/write
  • 光标定位:lseek
  • 关闭:close
  1. 文件描述符

    • 对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读写一个文件时,用open和creat返回的文件描述符标识该文件,将其作为参数传递给read和write。

      按照惯例,UNIX shell使用文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合,文件描述符2与标准错误输出相结合。STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO这几个宏代替了0、1、2这几个数。

      read(0,buf,5);//0表示标准输入,STDIN_FILENO,键盘输入
      
      write(1,buf,strlen(buf));//1表示标准输入,STDOUT_FILENO
      
      
    • 文件描述符,这个数字在一个进程中表示一个特定含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果要操作这个动态文件,只需要用这个文件描述符区分。

    • 文件描述符的作用域就是当前进程,出了这个进程文件描述符就没有意义了。

    • open函数打开文件,打开成功返回一个文件描述符,打开失败,返回-1。

  2. 动态/静态文件

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

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

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

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

    • 问什么这么设计,不直接对块设备直接操作。

      块设备本身读写非常不灵活,是按块读写的,而内存是按字节单位操作的,而且可以随机操作,很灵活。

  3. 打开/创建文件

    包含头文件

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

    打开/创建文件的API

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

    打开and创建一个文件file1

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>//open需要包含的头文件
    #include <stdio.h>
    
    int main()
    {
            int fd;//file description
            fd = open("./file1",O_RDWR);//file1不存在,打开它
            if(fd == -1){//打开失败返回值为-1
                    printf("open file1 failed!\n");//打开失败
                    fd = open("./file1",O_RDWR|O_CREAT,0600);//创建file1
            }
            printf("fd = %d\n",fd);
            if(fd > 0){
                    printf("create file1 success!\n");
            }
            return 0;
    }
    
    

    O_EXCL指令

    fd = open("./file1",O_RDWR|O_CREAT|O_EXCL,0600);//在创建文件时,如果文件已经存在,则出错,返回-1
            if(fd == -1){
                    printf("file1 exist!\n");
            }
    

    O_APPEND指令

     fd = open("./file1",O_RDWR|O_APPEND);
    //如果添加了O_APPEND,写入的时候在文件尾部,另一起一行写入,不加O_APPEND,写入时会覆盖原有的内容
    

    O_TRUNC指令

    fd = open("./file1",O_RDWR|O_TRUNC);
    //添加O_TRUNC后,文件内容清空
    

    creat函数:创建文件

    int creat(const char *pathname, mode_t mode);
    filename:要创建的文件名(包含路径,缺省为当前路径)
    mode:创建模式(包括读、写、执行)
    常见创建模式:
    宏表示   数字 功能
    S_IRUSR  4   可读
    S_IWUSR  2   可写
    S_IXUSR  1   可执行
    S_IRWXU  7   可读、可写、可执行
    例:creat("./file2",S_IRWXU);
    
  4. 写入文件Write

    #include <unistd.h>//write和close 需要包含的头文件
    #include <string.h>//strlen 需要包含的头文件
    //size_t write(int fd, const void *buf, size_t count);
    write(fd,buf,strlen(buf));//写入文件
    //int close(int fd);    
    close(fd);//关闭文件
    
  5. 读取文件Read

    #include <unistd.h>//read需要包含的头文件
    close(fd);
    fd = open("./file1",O_RDWR);//重新打开文件,重置光标位置
    
    char *read_buf;
    read_buf = (char *)malloc(sizeof(char)*num_write + 1);//防止野指针,为read_buf分配空间
    
    //ssize_t read(int fd, void *buf, size_t count);
    int num_read = read(fd,read_buf,num_write);//读取文件
    
    printf("read %d,context:%s\n",num_read,read_buf);
    

    需要注意的是:如果写入完后直接读取,因为写入完毕后光标在内容最后,所以读取不到内容,可以重新打开文件,让光标回到最开始,也可以重置光标的位置。如下:

    off_t lseek(int fd, off_t offset, int whence);
    offset:偏移值,相对于whence
    whence:位置,有SEEK_SET:文件头;SEEK_CUR:当前光标位置;SEEK_END:文件的尾部
    lseek();函数的返回值是从文件头到whence偏移位置的bytes,所以可以用来计算文件的byte数。
    例:lseek(fd,0,SEEK_END);
    
  6. 练习1:Linux 文件cp

    int main(int argc, char *argv[])
    main函数的函数声明。
    其中argc是外部输入的参数个数,argv[ ]是参数的字符串数组。
    
    int main(int argc,char **argv)
    {
            int fd_scr;//源文件fd
            int fd_des;//目标文件fd
    
            char *buf = NULL;
    
            if(argc != 3){
                    printf("argc error!\n");
                    exit(-1);
            }//参数错误退出
    
            fd_scr= open(argv[1],O_RDWR);
            int size = lseek(fd_scr,0,SEEK_END);//计算文件内容大小
            buf = (char *)malloc(sizeof(char)*size + 8);//分配空间
            lseek(fd_scr,0,SEEK_SET);//重置光标位置
            read(fd_scr,buf,size);//读取源文件内容
    
    
            fd_des = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);//打开/创建目标文件,如果文件存在且有内容,O_TRUNC清空内容
            //size_t write(int fd, const void *buf, size_t count);
            write(fd_des,buf,strlen(buf));//写入目标文件
    
            close(fd_scr);
            close(fd_des);//关闭两个文件,防止文件损坏
    
            return 0;
    }
    
  7. 练习2:修改配置文件中的参数

    例如:在一个配置文件中存在:SPEED=3;LENGTH=3;SCORE=9;LEVEL=5。将LENGTH修改为5。

            read(fd_src,buf,size);//读取内容
    
            char *p = strstr(buf,"LENGTH=");//找到要修该的内容,strstr的返回值是指针
            if(p == NULL){
                    printf("not found\n");
                    exit(-1);
            }//没找到退出
            p = p + strlen("LENGTH=");
            *p = '5';//修改值
    
            lseek(fd_src,0,SEEK_SET);//写入前光标重置,也可以打开文件的时候O_TRUNC
            //size_t write(int fd, const void *buf, size_t count);
            write(fd_src,buf,strlen(buf));//写入
    
            close(fd_src);//关闭文件
    
  8. 文件如何记录一个结构体

    struct Test
    {
            int i;
            char c;
    };
    int main(int argc,char **argv)
    {
            struct Test data = {100,'a'};
            struct Test data2;
            int fd_src;
    
            fd_src = open(argv[1],O_RDWR);//打开文件
            //size_t write(int fd, const void *buf, size_t count);
            write(fd_src,&data,sizeof(struct Test));//写入结构体数据
    
            lseek(fd_src,0,SEEK_SET);//重置光标位置
            read(fd_src,&data2,sizeof(struct Test));//读取第一个结构体的数据,写入到第二个结构体
            printf("read %d,%c\n",data2.i,data2.c);//打印出第二个结构体的数据
            close(fd_src);//关闭文件
    
            return 0;
    }
    

二.总结open与fopen的区别

  1. 来源

    • open是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。
    • fopen是ANSIC标准中的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等。
  6. 使用fopen、fwrite、fread文件编程

    #include <stdio.h>
    #include <string.h>
    //打开/创建一个文件,写入字符串,读取到另一个字符数组,打印这个字符数组
    int main()
    {
            FILE *fp;//文件指针,类似于Linux下的文件描述符
            char *str = "hello Linux,this is fopen";
            char read_buf[128] = {0};
            //FILE *fopen(const char *path, const char *mode);
            fp = fopen("./fopen.txt","w+");//打开/创建一个文件,w+是创建一个可读写的文件
            //size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
            fwrite(str,sizeof(char),strlen(str),fp);//写入文件
            fseek(fp,0,SEEK_SET);//光标的移动
            //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
            fread(read_buf,sizeof(char),strlen(str),fp);//读取文件
    
            printf("read_buf:%s\n",read_buf);
    
            fclose(fp);
            return 0;
    }
    
  7. fputc、fgetc、feof

    //fputc
    int len = strlen(str);
            for(i=0;i<len;i++){
                    //int fputc(int c, FILE *stream);       
                    fputc(*str,fp);//往文件fp中写入一个字符
                    str++;
            }
    
    //fgetc、feof
     while(!feof(fp)){//判断是否到达文件末尾,不在末尾返回0,在末尾返回非0
                    //int fgetc(FILE *stream);
                    c = fgetc(fp);//从文件中得到一个字符
                    printf("%c",c);
            }
    

Linux一切皆文件!