• Linux文件IO操作


    来源:微信公众号「编程学习基地」

    文件操作

    在进行 Linux 文件操作之前,我们先简单了解一下 Linux 文件系统

    Linux文件类型

    Linux中文件类型只有以下这几种:

    符号 文件类型
    - 普通文件
    d 目录文件,d是directory的简写
    l 软连接文件,亦称符号链接文件,s是soft或者symbolic的简写
    b 块文件,是设备文件的一种(还有另一种),b是block的简写
    c 字符文件,也是设备文件的一种(这就是第二种),c是character的文件
    s 套接字文件,这种文件类型用于进程间的网络通信
    p 管道文件,这种文件类型用于进程间的通信

    怎么判断文件类型?

    请添加图片描述

    $ ls -l
    total 8
    drwxrwxr-x 2 ubuntu ubuntu 4096 Oct 25 15:30 dir
    prw-rw-r-- 1 ubuntu ubuntu    0 Oct 25 15:30 FIFOTEST
    -rw-rw-r-- 1 ubuntu ubuntu    2 Oct 25 15:25 main.c
    lrwxrwxrwx 1 ubuntu ubuntu    6 Oct 25 15:28 main.c-temp -> main.c
    srwxrwxr-x 1 ubuntu ubuntu    0 Oct 25 15:24 test.sock
    

    ls -l 第一个字母代表文件类型

    Linux文件权限

    文件权限是文件的访问控制权限,那些用户和组群可以访问文件以及可以执行什么操作

    查看文件权限

    请添加图片描述

    文件类型后面紧跟着的就是文件权限

    简单介绍下文件权限,如下图所示:
    请添加图片描述

    因为Linux是一个多用户登录的操作系统,所以文件权限跟用户相关。

    修改文件权限

    1.以二进制的形式修改文件权限

    什么是二进制形式?以main.c的权限为例

    -rw-rw-r-- 1 ubuntu ubuntu    2 Oct 25 15:25 main.c
    

    文件的权限为rw-rw-r--,对应的二进制为664,如何计算呢,看下表

    可读 可写 可执行
    字符表示 r w x
    数字表示 4 2 1

    所有者的权限为rw-,对应着4+2+0,也就是最终的权限6,以此类推,用户组的权限为6,其他用户的权限为4.

    修改文件权限需要用到chmod命令,如下所示

    $ ls -l
    -rw-rw-r-- 1 ubuntu ubuntu    2 Oct 25 15:25 main.c
    $ chmod 666 main.c
    $ ls -l
    -rw-rw-rw- 1 ubuntu ubuntu    2 Oct 25 15:25 main.c
    

    二进制的计算不要算错了

    2.以加减赋值的方式修改文件权限

    还是用到chmod命令,直接上手

    $ ls -l
    -rw-rw-rw- 1 ubuntu ubuntu    2 Oct 25 15:25 main.c
    $ chmod o-w main.c
    $ ls -l
    -rw-rw-r-- 1 ubuntu ubuntu    2 Oct 25 15:25 main.c
    
    文件所有者 user 文件所属组用户 group 其他用户 other
    u g o

    +- 分别表示增加和去掉相应权限

    简单的了解了Linux下的文件操作之后就开始进入代码编程阶段

    Linux error

    获取系统调用时的错误描述

    Linux下的文件操作属于系统调用,Linux中系统调用的错误都存储于 errno 中,例如文件不存在,errno置 2,即宏定义 ENOENT ,对应的错误描述为 No such file or directory

    打印系统调用时的错误描述需要用到 strerror,定义如下

    #include <string.h>
    char *strerror(int errnum);
    

    查看系统中所有的 errno 所代表的含义,可以采用如下的代码:

    /* Function: obtain the errno string
    *   char *strerror(int errno)
    */
    #include <stdio.h>
    #include <string.h>     //for strerror()
    //#include <errno.h>
    int main()
    {
        int tmp = 0;
        for(tmp = 0; tmp <=256; tmp++)
        {
            printf("errno: %2d	%s
    ",tmp,strerror(tmp));
        }
        return 0;
    }
    

    可以自己手动运行下,看下输出效果

    打印错误信息

    之前谈到Linux系统调用的错误都存储于 errno 中,errno定义如下

    #include <errno.h>
    int errno;
    

    除了 strerror 可以输出错误描述外,perror 也可以,而且更加方便

    打印系统错误消息 perror ,函数原型及头文件定义如下

    #include <stdio.h>
    void perror(const char *s);
    

    使用示例:

    /**
     * @brief 文件不存在打开失败时打印错误描述
     */
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h> //strerror
    #include <errno.h>  //errno
    
    int main() {
        int fd = open("test.txt", O_RDWR);
        if(fd == -1) {
            perror("open");
            //printf("open:%s
    ",strerror(errno));
        }
        close(fd);
        return 0;
    }
    

    当文件 test.txt 不存在时打印如下

    ./main 
    open: No such file or directory
    

    系统IO函数

    UNIX环境下的C 对二进制流文件的读写有两种体系:

    1. fopen,fread,fwrite ;
    2. open, read, write;

    fopen 系列是标准的C库函数;open系列是 POSIX 定义的,是UNIX系统里的system call。

    文件操作不外乎 open,close,read,write,lseek,从打开文件开始介绍

    open/close

    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 这三个设置是互斥的

    - mode:八进制数,表示创建出的新的文件的操作权限,例如:0775

    close 定义如下

    #include <unistd.h>
    int close(int fd);
    
    打开文件

    通过open打开一个存在的文件

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        int fd = open("test.txt", O_RDONLY);// 打开一个文件
        if(fd == -1) {
            perror("open");
        }
        // 读写操作
        close(fd);	// 关闭
        return 0;
    }
    

    如果文件存在,打开文件;文件不存在,打开失败,错误描述为No such file or directory

    创建文件

    通过open创建一个新的文件

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    int main() {
        // 创建一个新的文件
        int fd = open("test.txt", O_RDWR | O_CREAT, 0777);
        if(fd == -1) {
            perror("open");
        }
        // 关闭
        close(fd);
        return 0;
    }
    

    这里有个东西需要注意一下,先看输出结果

    $ ls -l test.txt
    -rwxrwxr-x 1 dengzr dengzr    0 Oct 27 19:50 test.txt
    

    创建文件的同时赋予文件权限,在上面的Linux文件权限中已经介绍过了文件权限

    创建文件时赋值的权限为777,但是创建的文件权限为775,这是我们需要注意的地方。

    在linux系统中,我们创建一个新的文件或者目录的时候,这些新的文件或目录都会有默认的访问权限。默认的访问权限通过命令umask查看。

    $ umask
    0002
    

    用户创建文件的最终权限为mode & ~umask

    例如Demo中创建的文件权限mode = 0777,所以最终权限为 0775

     777	->	111111111	
    ~002	->	111111101	&
     775	->	111111101
    

    修改默认访问权限

    更改umask值,可以通过命令 umask mode 的方式来更改umask值,比如我要把umask值改为000,则使用命令 umask 000 即可。

    $ umask 
    0002
    $ umask 000
    $ umask 
    0000
    

    删除test.txt重新运行程序创建

    $ ./create 
    $ ls -l test.txt 
    -rwxrwxrwx 1 dengzr dengzr 0 Oct 27 20:06 test.txt
    

    ps:这种方式并不能永久改变umask值,只是改变了当前会话的umask值,打开一个新的shell输入umask命令,可以看到umask值仍是默认的002。要想永久改变umask值,则可以修改文件/etc/bashrc,在文件中添加一行 umask 000

    read/write

    文件I/O最基本的两个函数就是read和write,在《unix/linux编程实践教程》也叫做unbuffered I/O。

    这里的ubuffered,是指的是针对于read和write本身来说,他们是没有缓存机制(应用层无缓冲)。

    read定义如下

    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
    

    参数:

    - fd:文件描述符

    - buf:保存读取数据的缓冲区

    - count:读取数据的大小

    返回值:

    - 成功:

    >0: 返回实际的读取到的字节数

    =0:文件已经读取完了

    - 失败:-1 ,并且设置errno

    简单应用一下,示例Demo

    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <errno.h>  //errno
    
    int main() {
        int fd = open("test.txt", O_RDWR);
        if(fd == -1) {
            perror("open");
        }
        char buff[1024];
        memset(buff,'',1024);
        int readLen = read(fd,buff,1024);
        printf("readLen:%d,data:%s",readLen,buff);
        close(fd);
        return 0;
    }
    

    Demo读取 test.txt 里面的数据并显示

    $ ./main 
    readLen:5,data:text
    

    write定义如下

    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
    

    参数:

    - fd:文件描述符

    - buf:待写入数据块

    - count:写入数据的大小

    返回值:

    - 成功:实际写入的字节数

    - 失败:返回-1,并设置errno

    同样简单应用一下,Demo如下

    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <errno.h>  //errno
    
    int main() {
        int fd = open("test.txt", O_WRONLY | O_CREAT ,0775);
        if(fd == -1) {
            perror("open");
        }
        char buf[256] = "text";
        int Len = write(fd,buf,strlen(buf));
        printf("readLen:%d,data:%s
    ",Len,buf);
        // close(fd);
        return 0;
    }
    

    在Demo中注释掉close,数据写入成功

    $ ./main 
    readLen:4,data:text
    $ cat test.txt 
    text
    

    对比fwrite等标准的C库函数,write是没有缓冲的,不需要fflush或者close将缓冲数据写入到磁盘中但是程序open后一定要close,这是一个良好的编程习惯。

    ps:其实write是有缓冲的,在用户看不到的系统层,我们可以理解为没有缓冲

    lseek

    作用:对文件文件指针进行文件偏移操作

    lseek定义如下

    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
    

    参数:

    ​ - fd:文件描述符

    ​ - offset:偏移量

    ​ - whence:

    ​ SEEK_SET :设置文件指针的偏移量

    ​ SEEK_CUR :设置偏移量:当前位置 + 第二个参数offset的值

    ​ SEEK_END:设置偏移量:文件大小 + 第二个参数offset的值

    返回值:返回文件指针的位置

    lseek和标准C库函数fseek没有什么区别,几个作用简单了解一下

    1.移动文件指针到文件头

    lseek(fd, 0, SEEK_SET);
    

    2.获取当前文件指针的位置

    lseek(fd, 0, SEEK_CUR);
    

    3.获取文件长度

    lseek(fd, 0, SEEK_END);
    

    示例Demo如下

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    int main() {
        int fd = open("test.txt", O_RDWR);
        if(fd == -1) {
            perror("open");
            return -1;
        }
        // 获取文件的长度
        int len = lseek(fd, 0, SEEK_END);
        printf("file len:%d
    ",len);
        // 关闭文件
        close(fd);
        return 0;
    }
    
    ./main 
    file len:4
    

    linux下的标准输入/输出/错误

    在文件IO操作里面一直讲到文件描述符,那我就不得不提一下linux中的标准输入/输出/错误

    在C语言的学习过程中我们经常看到的stdin,stdout和stderr,这3个是被称为终端(Terminal)的标准输入(standard input),标准输出( standard out)和标准错误输出(standard error),这对应的是标准C库中的fopen/fwrite/fread。

    但是在在Linux下,操作系统一级提供的文件API都是以文件描述符来表示文件,对应的的标准输入,标准输出和标准错误输出是0,1,2,宏定义为STDIN_FILENO、STDOUT_FILENO 、STDERR_FILENO ,这对应系统API接口库中的open/read/write。

    标准输入(standard input)

    在c语言中表现为调用scanf函数接受用户输入内容,即从终端设备输入内容。也可以用fscanf指明stdin接收内容或者用read接受,对应的标准输入的文件标识符为0或者STDIN_FILENO。

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char buf[1024];
        //C语言下标准输入
        scanf("%s",buf);
        //UNIX下标准输入 stdin
        fscanf(stdin,"%s",buf);
        //操作系统级 STDIN_FILENO
        read(0,buf,strlen(buf));
        return 0;
    }
    

    ps:注意read不可以和stdin搭配使用

    标准输出(standard out)

    在c语言中表现为调用printf函数将内容输出到终端上。使用fprintf指明stdout也可以把内容输出到终端上或者wirte输出到终端,对应的标准输出的文件标识符为1或者STDOUT_FILENO 。

    #include<stdio.h>
    #include<string.h>
    #include <unistd.h>  
    int main()
    {
        char buf[1024];
        //C语言下标准输入 输出
        scanf("%s",buf);
        printf("buf:%s
    ",buf);
        //UNIX下标准输入 stdin 
        fscanf(stdin,"%s",buf);
        fprintf(stdout,"buf:%s
    ",buf);
    	//操作系统级 STDIN_FILENO
        read(STDIN_FILENO,buf,strlen(buf));	
        write(STDOUT_FILENO,buf,strlen(buf));
        return 0;
    }
    

    标准错误输出(standard error)

    标准错误和标准输出一样都是输出到终端上, 标准C库对应的标准错误为stderr,系统API接口库对应的标准错误输出的文件标识符为2或者STDERR_FILENO。

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char buf[1024]="error";
        fprintf(stderr,"%s
    ",buf);
    
        write(2,buf,strlen(buf));
        return 0;
    }
    

    既然有标准输出,为什么要有标准错误呢?既生瑜何生亮?

    一个简单的Demo让你了解一下,诸葛的牛逼

    #include <stdio.h>
    int main()
    {
        fprintf(stdout, "stdout");
        fprintf(stderr, "stderr");
        return 0;
    }
    

    猜一下是先输出stdout还是stderr,按照正常思维是先输出stdout,再输出stderr。

    但是stderr属于诸葛流,喜欢抢占先机,所以先输出stderr,再输出stdout。

    ~咳咳,扯远了,实际上stdout是块设备,stderr不是。对于块设备,只有当下面几种情况下才会被输入:遇到回车;缓冲区满;flush被调用。而stderr因为没有缓冲所以直接输出。

    谈一下stdin和STDIN_FILENO区别

    以前我一直没搞明白,以为stdin等于0,其实stdin类型为 FILE*;STDIN_FILENO类型为 int,不能相提并论,其次stdin属于标准I/O,高级的输入输出函数,对应的fread、fwrite、fclose等;STDIN_FILENO属于系统API接口库,对应的read,write,close。

    上面都是我零碎的知识点总结一下备忘。

  • 相关阅读:
    javascript 犀牛书
    Hmtl/css
    SQL Server 查询中使用Union或Union All后Order by排序无效(嵌套查询乱序)
    CSS 动画
    CSS 文字排版
    CSS 太极图
    Amazon Products 服务费估算
    CSS3 结构伪类选择器
    JS 执行机制
    vue 生命周期函数的学习
  • 原文地址:https://www.cnblogs.com/deroy/p/15484908.html
Copyright © 2020-2023  润新知