【Linux】linux中的文件IO及目录操作

导读:本篇文章讲解 【Linux】linux中的文件IO及目录操作,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

目录

一,标准C库中的文件IO操作

1. 标准C库对文件操作常用函数

2. 标准C库IO和Linux系统IO的关系

 3. 虚拟地址空间

二、 Linux中的文件操作

2.1 Linux中的文件IO操作常用函数

2.2 文件IO函数详细介绍

■ 打开/关闭文件

■ 读写文件

■ 定位文件

 ■ 获取文件信息

三,目录操作函数

1.目录的增删改

2. 目录遍历函数

四,与文件描述符相关的函数


一,标准C库中的文件IO操作

在介绍Linux文件IO及目录操作之前,先了解下标准C库中的文件IO的一些操作。

1. 标准C库对文件操作常用函数

函数名 说明
fopen 打开文件,返回FILE*
fclose 关闭文件
fread 读取文件
fwrite 写入文件
fgets 读取一行或指定个字符串,成功返回指向读取到的字符串的首地址,失败返回NULL
fputs 写入一个字符串到文件中
fscanf 格式化读取文件
fprintf 格式化写入文件
fseek 把文件读写指针定位到指定的位置
fgetc 从文件中读取一个字符,成功返回这个字符,失败返回EOF
fputc 把一个字符写入到文件,成功返回这个字符,失败返回EOF
feof 当设置了文件结束标识符时,返回非零值,否则返回0
fflush 刷新流的输出缓冲区

C库文件操作大致过程:

  • 使用fopen()函数打开文件,fopen()返回FILE*,FILE是一个结构体,包含文件描述符,文件读写位置等信息;
  • 通过文件描述符,可以找到对应文件;通过文件读写位置,可以在对应文件中定位读写的位置。
  • 当写入文件时,文件内容并不会立即被写入文件,而是被放到缓冲区,当满足条件时,缓冲区被刷新,缓冲区中的内容写入到文件中。

【Linux】linux中的文件IO及目录操作

2. 标准C库IO和Linux系统IO的关系

【Linux】linux中的文件IO及目录操作

 3. 虚拟地址空间

当运行一个可执行程序时,系统会自动为该程序分配一个虚拟地址空间,以32位系统为例,虚拟地址空间大小是4G。其中3G~4G之间的空间是内核区,用户无法在此空间进行读写操作;0~3G之间是用户区,包含了环境变量、命令行参数、堆栈空间、共享库等等部分,比如我们在程序中定义的变量会在用户区中。

【Linux】linux中的文件IO及目录操作

 上面提到的文件描述符在内核区中的PCB进程控制块中,该块中有一个文件描述符表,表的大小是1024,用户每打开一个文件,就会占用一个文件描述符(空闲的最小的一个)。

二、 Linux中的文件操作

2.1 Linux中的文件IO操作常用函数

int open(const char *pathname,int flags);  // 打开一个已存在的文件
int open(const char *pathname,int flags,mode_t mode);  // 打开文件,可以创建文件
int close(int fd);  // 关闭文件

// 读写文件
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf,size_t count);

// 定位文件
off_t lseek(int fd, off_t offset, int whence);

// 获取文件信息
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

// 文件属性相关
int access(const char *pathname,int mode);  // 判断文件权限,或者判断文件是否存在
int chmod(const char *pathname,int mode);  // 修改文件权限
int chown(const char *path,uid_t owner,gid_t group);  // 修改文件的所有者或所在组
int truncate(const char *path, off_t length);  // 扩展或缩减文件大小

想要了解某个函数的具体说明,可以使用命令: $ man 2 函数名。表示要查看第2节中的某个函数(第2节是关于Linux系统的库函数介绍,第3节是C库函数介绍)。比如查看open函数的说明,可以使用命令:

$ man 2 open

显示如下:

【Linux】linux中的文件IO及目录操作

 在Linux系统中,除了返回值表示具体含义的(比如open()函数返回值表示文件描述符),通常以int类型作为返回值的函数,当函数执行成功时,返回0,失败返回-1。

2.2 文件IO函数详细介绍

■ 打开/关闭文件

打开文件使用open()函数,关闭文件使用close()函数。

int open(const char *pathname,int flags);  // 打开一个已存在的文件
int open(const char *pathname,int flags,mode_t mode);  // 打开文件,可以创建文件
int close(int fd);  // 关闭文件

参数说明:

@pathname:要打开的文件路径;

@flags:文件权限设置及其它设置,表示本进程的读写权限,而不是文件本身的权限;

        权限包括:O_RDONLY        O_WRONLY        O_RDWR

                               只读                     只写                     读写

        以上权限是互斥的,使用时只能选择其中一个

@ mode:是一个八进制的数,表示创建出的新文件的创建权限,

        文件最终的权限是  mode & ~umask;umask的作用就是舍弃某些权限,让创建的文件更合理。

       mode表示的就是如下【-rwxrwxr-x】,如下红线标出的权限对应的八进制数应该是0775

【Linux】linux中的文件IO及目录操作

@fd:文件描述符;

返回值:

open()返回值是文件描述符,返回-1表示文件打开失败;

close()成功返回0,失败返回-1;

■ 读写文件

read()从文件中读取数据,write()向文件中写入数据。

// 读写文件
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf,size_t count);

参数介绍:

  • fd:文件描述符
  • buf:要读取/写入的内容
  • count:要读取的内容的大小/要写入的内容的大小,单位:byte

返回值:

  • open():>0表示读取到的字节数,=0表示文件已经读完,=-1表示文件读取失败;
  • write():>0表示实际写入的字节数,=0表示没有数据写入,=-1表示写入失败;

■ 定位文件

lseek()可以定位文件读/写指针,在需要的位置开始读/写。

// 定位文件
off_t lseek(int fd, off_t offset, int whence);

参数:

  • fd:文件描述符
  • offset:以whence为基础的文件指针偏移量
  • whence:取值
    • SEEK_SET:文件指针偏移的位置设置为文件开始;
    • SEEK_CUR:文件指针偏移的位置设置为当前指针所在位置;
    • SEEK_END:文件指针偏移的位置设置为文件末尾;

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

lseek()函数的作用:

  • 移动文件指针到文件头:lseek(fd,0,SEEK_SET);
  • 获取当前文件指针的位置:lseek(fd,0,SEEK_CUR);
  • 获取文件大小:lseek(fd,0,SEEK_END);
  • 扩展文件长度:lseek(fd,100,SEEK_END);

使用示例,使用以上介绍的函数,模拟cp命令,复制文件,并设置复制后的文件是否扩展文件大小。

/**
 * @file Example1.c
 * @author 
 * @brief  模拟cp命令,并设置是否扩展文件,
 * @version 0.1
 * @date 2022-08-25
 * 
 * @copyright Copyright (c) 2022
 * 
 */

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

int extend_file(int fd,int length);

int main(int argc , char *argv[])
{
    if(argc != 4)
    {
        printf("%s srcFile dstFile extend_length\ninput error!\n",argv[0]);
        return -1;
    }

    // 打开文件
    int srcfd = open(argv[1],O_RDONLY);
    if(srcfd == -1)
    {
        perror("open");
        return -1;
    }
    int dstfd = open(argv[2],O_WRONLY|O_CREAT,0664);
    if(dstfd == -1)
    {
        perror("open");
        close(srcfd);
        return -1;
    }

    // 读取文件并写入

    ssize_t rdLen=0;
    char rdBuf[1024]={0};

    while ( (rdLen = read(srcfd,rdBuf,sizeof(rdBuf)))  > 0)
    {
        write(dstfd,rdBuf,rdLen);
    }


    close(srcfd);

    // 扩展文件
    int extend_length = atoi(argv[3]);
    if( extend_length > 0)
    {
        int ret = extend_file(dstfd,extend_length);
        if(ret == -1)
        {
            close(dstfd);
            return -1;
        }

    }
    close(dstfd);

    return 0;
}


/**
 * @brief 扩展文件
 * 
 * @param fd :文件描述符
 * @param length :扩展的文件长度
 * @return int :失败返回-1,成功返回0
 */
int extend_file(int fd,int length)
{

    int ret = lseek(fd,length,SEEK_END);
    if (ret == -1)
    {
        perror("lseek");
        return -1;
    }

    ret = write(fd,"",1);
    if(ret == -1)
    {
        perror("write");
        return -1;
    }

    return 0;
}

运行结果:

【Linux】linux中的文件IO及目录操作

 ■ 获取文件信息

获取文件信息使用stat()函数,获取链接文件本身的信息使用lstat()函数。

// 获取文件信息
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

参数:

  • pathname:获取信息的文件名;
  • statbuf:文件信息,为stat结构体,成员包含以下:

返回值:获取成功返回0,失败返回-1;

stat结构体:

struct stat {
    dev_t        st_dev;    // 文件的设备编号
    ino_t        st_ino;    // 节点
    mode_t       st_mode;   // 文件的类型和存取的权限
    nlink_t      st_nlink;  // 练到该文件的硬连接数目
    uid_t        st_uid;    // 用户ID
    gid_t        st_gid;    // 组ID
    dev_t        st_rdev;   // 设备文件的设备编号
    off_t        st_size;   // 文件字节数(文件大小)
    blksize_t    st_blksize;  // 块大小
    blkcnt_t     st_blocks; // 块数
    time_t       st_atime;  // 最后一次访问时间
    time_t       st_mtime;  // 最后一次修改时间
    time_t       st_ctime;  // 最后一次属性改变的时间
};

其中需要关注的是st_mode成员,st_mode表示文件的类型和存取的权限,是unsigned int类型,有16位,每个位代表不同的含义:

【Linux】linux中的文件IO及目录操作

 各个位表示的含义如下:

【Linux】linux中的文件IO及目录操作

 判断某个文件是否的普通文件可以先使用stat()函数获取到文件信息,然后使用if (st_mode & S_IFMT == S_IFREG)判断。

使用示例:使用stat()函数模拟实现ls -l命令,显示某个文件的信息:

/**
 * @file ls_l.c
 * @author 
 * @brief  使用stat/lstat函数模拟实现 ls -l命令
 *          -rw-rw-r-- 1 zoya zoya   13 8月  22 15:27 a.txt
 * @version 0.1
 * @date 2022-08-23
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <pwd.h>  // 获取文件所有者
#include <grp.h>  // 获取所在组
#include <time.h>
#include <string.h>


int main(int argc, char *argv[])
{
    // 1. 判断输入的参数是否正确
    if(argc < 2)
    {
        printf("Input argument is less 2,parameters list: %s filename\n",argv[0]);
        return -1;
    }

    // 获取传入的文件的信息
    struct stat statbuf;
    int ret = stat(argv[1],&statbuf);
    if (ret == -1)
    {
        perror("stat");
        return -1;
    }

    // 获取文件类型和文件权限
    //-rw-rw-r-- 1 zoya zoya   13 8月  22 15:27 a.txt
    char szFiletype[11]={0};

    mode_t mode = statbuf.st_mode;
    // 文件类型
    switch (mode & S_IFMT)
    {
    case S_IFLNK:
        szFiletype[0]='l';
        break;
    case S_IFDIR:
        szFiletype[0]='d';
        break;
    case S_IFREG:
        szFiletype[0]='-';
        break;
    case S_IFBLK:
        szFiletype[0]='b';
        break;
    case S_IFSOCK:
        szFiletype[0]='s';
        break;
    case S_IFCHR:
        szFiletype[0]='c';
        break;
    case S_IFIFO:
        szFiletype[0]='p';
        break;
    default:
        szFiletype[0]='?';
        break;
    }
    
    // 文件访问权限
    // 文件所有者
    szFiletype[1] = (mode & S_IRUSR?'r':'-');
    szFiletype[2] = (mode & S_IWUSR?'w':'-');
    szFiletype[3] = (mode & S_IXUSR?'x':'-');
    // 文件所有者所在组
    szFiletype[4] = (mode & S_IRGRP?'r':'-');
    szFiletype[5] = (mode & S_IWGRP?'w':'-');
    szFiletype[6] = (mode & S_IXGRP?'x':'-');
    // 其他人
    szFiletype[7] = (mode & S_IROTH?'r':'-');
    szFiletype[8] = (mode & S_IWOTH?'w':'-');
    szFiletype[9] = (mode & S_IXOTH?'x':'-');

    // 硬链接数
    int linkNum = statbuf.st_nlink;

    // 文件所有者
    char *fileUsr = getpwuid(statbuf.st_uid)->pw_name;

    // 文件所在组
    char *groupName = getgrgid(statbuf.st_gid)->gr_name;

    // 文件大小
    off_t fileSize = statbuf.st_size;

    // 获取修改时间
    char *time = ctime(&statbuf.st_mtime);  // 将秒数转换为表示时间的字符串
    char sztime[512]={0};
    strncpy(sztime,time,strlen(time)-1);

    char buf[1024]={0};

    sprintf(buf,"%s %d %s %s %ld %s %s",szFiletype,
    linkNum,fileUsr,groupName,fileSize,sztime,argv[1]);

    printf("%s\n",buf);
    
    return 0;
}

 ■ 文件属性相关

// 文件属性相关
int access(const char *pathname,int mode);  // 判断文件权限,或者判断文件是否存在
int chmod(const char *pathname,int mode);  // 修改文件权限
int chown(const char *path,uid_t owner,gid_t group);  // 修改文件的所有者或所在组
int truncate(const char *path, off_t length);  // 扩展或缩减文件大小

 查看uid、gid可以使用命令【 id 用户名】。

access()函数中mode取值如下:

  • F_OK        判断文件是否存在
  • R_OK       判断是否有读的权限
  • W_OK       判断是否有写的权限
  • X_OK       判断是否有执行权限

chmod()函数中mode是八进制的数字;

truncate()函数中参数length表示文件最终要变成的大小。

上面的函数返回值:成功返回0,失败返回-1。

三,目录操作函数

1.目录的增删改

目录的增、删、改常用函数有:

int rename(const char *oldpath,const char *newpath);  // 重命名目录
int chdir(const cahr *path);  // 改变目录
char *getcwd(char *buf,size_t size);  // 获取当前进程的工作目录
int mkdir(const char *pathname,mode_t mode);  // 创建具有mode权限的目录
int rmdir(const char *pathname);  // 删除目录

getcwd()函数中:

        参数buf保存获取到的路径;

        参数size是数组大小;

        返回值,成功返回获取到的路径,失败返回NULL。

2. 目录遍历函数

DIR *opendir(const char *name);  // 打开目录
struct dirent *readdir(DIR *dirp);  // 读取目录
int closedir(DIR *dirp);  // 关闭目录

目录遍历函数中需要了解结构体dirent:

struct dirent {
    ino_t d_ino;  // 此目录进入点的inode
    off_t d_off;  // 目录文件开头至此目录进入点的位移
    unsigned short int d_reclen;  // d_name的长度,不包含NULL字符
    unsigned char d_type;  // d_name 所指的文件类型
    char d_name[256];  // 文件名
};

其中文件类型d_type参数有:

  • DT_BLK                 块设备
  • DT_CHR                字符设备
  • DT_DIR                  目录
  • DT_LNK                  软链接
  • DT_FIFO                 管道
  • DT_REG                 普通文件
  • DT_SOCK               套接字
  • DT_UNKNOWN       未知

示例:可以使用以上函数统计某个目录下的某种文件或目录的个数:

/**
 * @file example.c
 * @author your name (you@domain.com)
 * @brief  统计某个目录下普通文件、目录、软链接等的个数
 * @version 0.1
 * @date 2022-08-26
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include "fileIO.h"

typedef enum{
    E_TYPE_BLK=0,   E_TYPE_CHR,    E_TYPE_DIR,    E_TYPE_LNK,    E_TYPE_FIFO,    E_TYPE_REG,    E_TYPE_SOCK,    E_TYPE_UNKNOWN,    E_TYPE_TOTAL,} FILE_TYPES;

int g_nCount[E_TYPE_TOTAL];

char *szFileType[] = {"块设备","字符设备","目录","软连接","管道","普通文件","套接字","未知文件"};

int getFiles(const char *dirname);

int main(int argc, char *argv[])
{
    // 判断命令参数
    if(argc < 2)
    {
        printf("%s dirname,please input correct parameters!\n",argv[0]);
        return -1;
    }

    // 格式化打印
    printf("统计:\t\t");
    for(int i=0;i<E_TYPE_TOTAL;i++)
    {
        printf(" %s ",szFileType[i]);
    }
        
    printf("\n");

    for (int j=1;j<argc;j++)
    {
        memset(g_nCount,0,sizeof(g_nCount));
        // 获取目录中的文件信息
        int ret = getFiles(argv[j]);
        if(ret == -1)
        {
            return -1;
        }

        // 打印结果
        printf("%s\n\t\t",argv[j]);
        for(int i=0;i<E_TYPE_TOTAL;i++)
        {
            printf("  %d\t",g_nCount[i]);
        }
        printf("\n");
    }
    
    return 0;
}


int getFiles(const char *dirname)
{
    // 打开目录
    DIR *pdir = opendir(dirname);
    if(pdir == NULL)
    {
        perror("opendir");
        return -1;
    }

    // 读取目录
    int totals=0;
    struct dirent *pd;
    while((pd =  readdir(pdir)) != NULL)
    {
        // 如果是.或.. ,忽略
        if(strcmp(pd->d_name,".")==0 || strcmp(pd->d_name,"..")==0)
            continue;

        // 判断文件类型
        switch (pd->d_type)
        {
        case DT_DIR:  // 如果是目录,递归调用该函数打开目录
            {
                g_nCount[E_TYPE_DIR]++;
                char name[512]={0};
                sprintf(name,"%s/%s",dirname,pd->d_name);
                getFiles(name);
            }
            break;
        case DT_BLK:
            g_nCount[E_TYPE_BLK]++;
            break;
        case DT_CHR:
            g_nCount[E_TYPE_CHR]++;
            break;
        case DT_LNK:
            g_nCount[E_TYPE_LNK]++;
            break;
        case DT_FIFO:
            g_nCount[E_TYPE_FIFO]++;
            break;
        case DT_REG:
            g_nCount[E_TYPE_REG]++;
            break;
        case DT_SOCK:
            g_nCount[E_TYPE_SOCK]++;
            break;
        case DT_UNKNOWN:
            g_nCount[E_TYPE_UNKNOWN]++;
            break;
        default:
        {
            perror("readdir");
        }
            break;
        }
    }

    // 关闭目录
    closedir(pdir);

    return 0;
}

显示结果:

【Linux】linux中的文件IO及目录操作

四,与文件描述符相关的函数

int dup(int oldfd);  // 复制文件描述符
int dup2(int oldfd,int newfd);  //重定向文件描述符

int fcntl(int fd,int cmd,...);  // 复制文件描述符,设置/获取文件的状态标志

函数说明:

dup():复制一个新的文件描述符

        -oldfd:旧的文件描述符;

                -假设fd指向文件a.txt,且fd=3,那么执行 int fd1 = dup(fd);后,fd1也指向a.txt文件,但fd1是从 空闲的文件描述符表中找一个最小的,

        – 返回值:返回一个新的文件描述符。

dup2():重定向文件描述符

        – 如果oldfd指向文件a.txt,newfd指向b.txt,那么int ret = dup2(oldfd,newfd);调用后,newfd和b.txt会被关闭,然后newfd指向a.txt;

        – 需要注意,oldfd必须是一个有效的文件描述符;当oldfd和newfd值相同时,表示什么都不会做。

fcntl():复制文件描述符;设置文件属性或状态

        – 参数 fd:文件描述符

        -参数 cmd:对文件的操作,可以有以下取值

                -F_DUPFD:复制文件描述符,复制参数fd,返回值是一个新的文件描述符

                -F_GETFL:获取指定文件描述符的文件状态flag(和open函数的参数flag一样)

                -F_SETFL:设置指定文件描述符的文件状态flag

                        -必选项:O_RDONLY        O_WRONLY        O_RDWR

                        -可选项:

                                -O_APPEND:追加数据

                                -O_NONBLOCK:设置成非阻塞     

使用示例1:dup()复制一个文件描述符

/**
 * dup()函数复制文件描述符
 */

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


int main()
{
    // 打开一个文件
    int fd = open("1.txt",O_RDWR | O_CREAT,0664);
    if(fd == -1)
    {
        perror("open");
        return -1;
    }

    // 复制文件描述符
    int fd1 = dup(fd);
    if(fd1 == -1)
    {
        perror("dup");
        return -1;
    }

    printf("fd = %d, fd1 = %d\n",fd,fd1);

    // 关闭fd
    close(fd);

    // 对fd1写入数据
    char *str = "Hello";
    write(fd1,str,strlen(str));

    // 关闭fd1
    close(fd1);

    return 0;
}

使用示例2:dup2()重定向文件描述符

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

int main()
{
    int fd1 = open("2.txt",O_RDWR|O_CREAT,0664);
    int fd2 = open("3.txt",O_RDWR|O_CREAT, 0664);
    if(fd1 == -1 || fd2 == -1)
    {
        perror("open");
        return -1;
    }

    printf("fd1=%d, fd2=%d\n",fd1,fd2);

    int fd3 = dup2(fd1,fd2);  // 复制fd1到fd2
    if(fd3 == -1)
    {
        perror("dup2");
        return -1;
    }
    printf("fd1=%d, fd2=%d, fd3=%d\n",fd1,fd2,fd3);

    close(fd1);

    char *str = "Hello dup2";
    write(fd2,str,strlen(str));
    close(fd2);

    return 0;
}

运行结束后,2.txt文件中有内容,3.txt文件中没有内容

【Linux】linux中的文件IO及目录操作

使用示例3:使用fcntl()设置文件状态flag,并向文件写入数据。


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


int main()
{

    // 复制文件描述符
    // 打开文件
    // int fd = open("1.txt",O_RDWR);
    // int ret = fcntl(fd,F_DUPFD);
    // printf("fd = %d, ret = %d\n",fd,ret);

    // 修改或者获取文件的flag
    int fd = open("1.txt",O_WRONLY);
    if(fd == -1)
    {
        perror("open");
        return -1;
    }

    // 修改文件描述符的状态,给flag加入O_APPEND标记,可以追加数据
    // 获取文件描述符的状态
    int fflag = fcntl(fd,F_GETFL);
    // 设置文件描述符的状态
    fflag |= O_APPEND;
    int ret = fcntl(fd,F_SETFL,fflag);
    if(ret == -1)
    {
        perror("fcntl");
        return -1;
    }
    char *str = " nihao ";
    write(fd,str,strlen(str));
    close(fd);
    

    return 0;
}

全文参考:课程列表_牛客网 (nowcoder.com)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/46093.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!