美文网首页
Linux系统编程(一) ------ 文件操作函数

Linux系统编程(一) ------ 文件操作函数

作者: 穹蓝奥义 | 来源:发表于2017-01-06 16:34 被阅读0次

    文件操作

    打开文件

    1.使用open()函数打开和创建文件

    • 手册文件 man 2 open

    函数头文件及函数原型
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    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       若欲打开的文件不存在则自动建立该文件
      O_TRUNC       若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 
                    而原来存于该文件的资料也会消失。
      O_EXCL        如果O_CREAT 也被设置, 此指令会去检查文件是否存在。 
                    文件若不存在则建立该文件,否则将导致打开文件错误. 
                    此外, 若O_CREAT 与O_EXCL 同时设置,并且欲打开的文件为符号连接, 
                    则会打开文件失败。
    
      参数mode仅在flags中含有O_CREAT时有效,设定新建文文件的打开权限,有下列数种组合,
      S_IRWXU             00700 权限,代表该文件所有者具有可读、可写及可执行的权限。
      S_IRUSR 或S_IREAD,  00400 权限,代表该文件所有者具有可读取的权限。
      S_IWUSR 或S_IWRITE, 00200 权限,代表该文件所有者具有可写入的权限。
      S_IXUSR 或S_IEXEC,  00100 权限,代表该文件所有者具有可执行的权限。
      S_IRWXG             00070 权限,代表该文件用户组具有可读、可写及可执行的权限。
      S_IRGRP             00040 权限,代表该文件用户组具有可读的权限。
      S_IWGRP             00020 权限,代表该文件用户组具有可写入的权限。
      S_IXGRP             00010 权限,代表该文件用户组具有可执行的权限。
      S_IRWXO             00007 权限,代表其他用户具有可读、可写及可执行的权限。
      S_IROTH             00004 权限,代表其他用户具有可读的权限。
      S_IWOTH             00002 权限,代表其他用户具有可写入的权限。
      S_IXOTH             00001 权限,xit代表其他用户具有可执行的权限。
    

    函数返回值: 打开文件成功,返回一个文件描述符 >2;打开失败,返回-1。

    提示:使用 access()作用户认证方面的判断要特别小心, 例如在access()后再作open()空文件可能会造成系统安全上的问题。

    2.使用create()函数创建并打开文件

    函数原型

         int creat(const char *pathname, mode_t mode);
         相当于使用调用方式,
         open(const char *pathname, (O_CREAT|O_WRONLY|O_TRUNC));
    

    函数参数:

      pathname   待打开文件的绝对路径和文件名。
    
      mode       新创建文件的权限,见上面open()
    

    函数返回值:若成功会返回新的文件描述符,若有错误发生则会返回-1。

    提示:creat()无法建立特别的装置文件,如果需要请使用mknod()。

    读写文件

    1.使用read()函数从文件中读取数据

    • 手册文件 man 2 read

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

    函数参数:

     fd      文件指针,提供数据的文件的文件描述符,读取的数据的来源。
    
     buf     读到的数据所存放的内存空间的起始地址,同时文件的当前读写位置向后移。
    
     count   想要读取的数据的字节数,也是提供的存储空间字节数。
    

    函数说明及返回值: read()会把参数fd 所指的文件传送count 个字节到buf 指针所指的内存中(暨在[0,count]区间变化)。

    1.若参数count 为0,则read()不会有作用并返回0。
    2.成功时,返回值为实际读取到的字节数。
    3.如果返回0,表示已到达文件尾,暨碰到了EOF或是无可读取的数据。
    4.此外文件读写位置会随读取到的字节移动。
    5.有错误发生时则返回-1,而文件读写位置则无法预测。
    
    提示:

    read()函数负责从文件句柄中读取指定数量的字节,并将这些字节放在标量型变量中。read()函数和标准I/O函数fread()相同的方式处理I/O缓冲的。为了提高效率,read()函数并不是一次读取一个字节,而是读取一块数据并保存到临时存储区中。然后,C的fread函数与Perl的read函数会从临时缓冲区将数据一次一个字节地传送给程序。print()函数(而不是write()函数负责输出read()函数返回的实际字节。print()函数类似于C中的fwrite()函数。

    附加:如果顺利 read()会返回实际读到的字节数,最好能将返回值与参数count 作比较,若返回的字节数比要求读取的字节数少,则
    1. 读取普通文件时,读到文件末尾还不够 nbytes 字节。例如:如果文件只有 30 字节,
    而我们想读取 100字节,那么实际读到的只有 30 字节,read 函数返回 30 。
    此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
    2. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。
    3. 从网络读取时,网络缓存可能导致读取的字节数小于 nbytes 字节。
    4. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 nbytes 。
    5. 从面向记录的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
    6. 在读取了部分数据时被信号中断。读操作始于 cfo 。在成功返回之前,cfo 增加,
    增量为实际读取到的字节数。

    2.使用write()函数向指定文件中写入数据
    • 手册文件 man 2 write

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

    函数参数:

    fd      待写入数据的文件的描述符
    
    buf     写入数据的起始地址
    
    count   待写入的数据的字节数
    

    函数说明及返回值: write()会把参数buf 所指的内存写入count 个字节到参数fd 所指的文件内。当然,文件读写位置也会随之移动。
    如果顺利会返回实际写入数据的字节数,表示写了部分或者全部的数据。
    当有错误发生时,返回-1,我们要根据错误的类型来处理。如果错误为EINTR表示在写时出现了中断错误。如果为EPIPE表示网络连接出现了问题。

    提示:对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。

    定位文件

    预概念: 所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。

    使用lseek()函数定位指定已打开文件的读写指针

    • 手册文件 man lseek

    函数头文件及函数原型
    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);

    函数参数:

    fd 待重新定位读写指针位置的文件的描述符
    
    offset 读写指针的偏移量(可正可负可为0)
    
    whence 读写指针的偏移位置
        SEEK_SET   相对文件首部偏移,文件偏移量将被设置为 offset。
        SEEK_CUR   相对文件当前读写位置偏移,文件偏移量将被设置为 cfo 加上 offset,
                   offset 可以为正也可以为负。
        SEEK_END   相对文件尾部偏移,文件偏移量将被设置为文件长度加上 offset,
                   offset 可以为正也可以为负。
    

    函数说明及返回值: 每一个已打开的文件都有一个读写位置,当打开文件时通常其读写位置是指向文件开头,若是以附加的方式打开文件(如O_APPEND),则读写位置会指向文件尾。当read()或write()时,读写位置会随之增加,lseek()便是用来控制该文件的读写位置。参数fildes 为已打开的文件描述词,参数offset 为根据参数whence来移动读写位置的位移数。当调用成功时则返回目前的读写位置,也就是距离文件多少个字节数。若有错误则返回-1。

    例:

     将读写位置移到文件开头时: lseek(int fildes, 0, SEEK_SET);
     将读写位置移到文件尾时:   lseek(int fildes, 0, SEEK_END);
     想要取得目前文件位置时:   lseek(int fildes, 0, SEEK_CUR);
    
    提示:

    1.Linux 系统不允许lseek()对tty 装置作用,此项动作会令lseek()返回ESPIPE。
    2.如果参数 fd(文件描述符)指定的是 pipe(管道)、FIFO 或者 socket,lseek 返回 -1 并且置 errno 为 ESPIPE。 对于普通文件(regular file),cfo 是一个非负整数。但对于特殊设备,cfo 有可能是负数。因此,我们不能简单地测试 lseek 的返回值是否小于 0 来判断 lseek 成功与否,而应该测试 lseek 的返回值是否等于 -1 来判断 lseek 成功与否。
    3.lseek 仅将 cfo 保存于内核中,不会导致任何 I/O 操作。这个 cfo 将被用于之后的读写操作。
    4.如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造"空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。

    关闭文件

    使用close函数关闭指定文件

    • 手册文件 man close

    函数头文件及函数原型

     #include <unistd.h>
     int close(int fd);
    

    函数参数:

    fd    为open()或creat()打开的文件描述符。
    

    函数说明及返回值: 当使用完已打开的文件后若已不再需要则可使用 close()关闭该文件, 而close()会让数据写回磁盘, 并释放该文件所占用的资源. 参数fd 为先前由open()或creat()所返回的文件描述词.**返回值:若文件顺利关闭则返回0, 发生错误时返回-1.

    提示:虽然在进程结束时,系统会自动关闭已打开的文件,但仍建议自行关闭文件,并确实检查返回值。

    综合案例

    // ./my-cp <src_file> <dst_file>
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    #define BUFFER_SIZE 100
    
    int main(int argc, char *argv[])
    {
        if(argc != 3)
        {
            printf("usage : %s <src_file> <dst_file>\n",
                argv[0]);
            return 1;
        }
        
        int src_fd = 0;
        int dst_fd = 0;
        int n = 0;
        char buf[BUFFER_SIZE] = {'\0'};
        char *src_file = argv[1];
        char *dst_file = argv[2];
        
        // 1.open
        // 1.1 以只读方式打开源文件
        if((src_fd = open(src_file, O_RDONLY)) == -1)
        {
            perror("open src error");
            return 1;
        }
        // 1.2 以只写方式打开目的文件
        if((dst_fd = open(dst_file, 
                O_WRONLY | O_CREAT | O_TRUNC,
                S_IRUSR | S_IWUSR)) == -1)
        {
            perror("open dst error");
            return 1;
        }
        
        // 2. 循环从源文件中读取数据写入到目的文件中
        // 直到读到源文件的尾部为止
        // 2.1 read data from src_file
        // 2.2 write data to dst_file
        while((n = read(src_fd, buf, BUFFER_SIZE)) > 0)
        {
            write(dst_fd, buf, n);
        }
        
        // 3.close
        close(src_fd);
        close(dst_fd);
    
        return 0;
    }
    
    // 练习:
    // 实现一个相对完整版的cp程序,要求能够判断出目标文件是否存在。
    //  如果存在,给出提示是否覆盖。
    // 思路:
    // 1.打开源文件
    // 2.判断目的文件是否存在
    // 3.如果目的文件存在,提示是否覆盖
    // 4.如果选择覆盖,则以只写的方式打开文件,并截短文件内容(O_TRUNC)
    // 5.如果选择不覆盖,则提醒输入新的保存文件名,并已只写方式打开
    // 6.循环读取源文件内容,写入到目的文件中
    // 7.关闭已打开的文件
    
    // 思考题1:能否关闭标准输入文件、标准输出文件、标准出错文件?
    

    参考资料

    刘老师上课资料及网上前辈资料
    计算机操作系统教程:介绍现代操作系统原理及应用

    相关文章

      网友评论

          本文标题:Linux系统编程(一) ------ 文件操作函数

          本文链接:https://www.haomeiwen.com/subject/quotbttx.html