当前位置: 首页 > article >正文

【Linux文件IO】Linux中文件属性与目录操作的API介绍和用法

Linux中文件属性与目录操作的API介绍和用法

  • 一、文件属性
    • 1、获取文件属性信息的函数接口
    • 2、文件的设备号
      • 示例代码:
    • 3、类型与权限
    • 4、其他简单文件信息
  • 二、目录操作
    • 1、基本概念
    • 2、相关的接口函数
      • (1) 打开目录
      • (2) 读取目录(重点)
        • 示例代码:
      • (3) 新建目录
        • 示例代码:
      • (4) 删除目录
        • 示例代码:
      • (5) 关闭目录

在应用开发中,经常要获取文件的属性,例如:文件的类型、大小、权限、设备号、最近修改时间等等,比如网络传输文件时,一般都需要先传递文件的属性,等准备妥善了再开始传输文件的真实内容。因此,熟悉文件属性的细节,并熟练获取这些信息的方式至关重要。

一、文件属性

1、获取文件属性信息的函数接口

如下函数可以获取指定文件的属性:
在这里插入图片描述
注意:
1.以上三个函数的功能一样,区别如下:

  • stat针对文件名获取其信息。
  • fstat针对文件描述符获取其信息。
  • lstat可以获取软连接文件(快捷方式)本身的属性。

2.这几个函数获取了文件的属性之后,会将这些信息存入一个称为stat的结构体中(与函数同名),该结构体的细节如下:

struct stat
{
     dev_t     st_dev;    // 本文件所在的设备的设备号,适用于非设备文件
     ino_t     st_ino;    // i节点号,相当于身份证号码
     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;   
 
     // 文件时间戳
     struct timespec st_atim;  // 最近访问时间,比如打开看一下文件的时间
     struct timespec st_mtim;  // 最近修改时间,比如打开并改一下的时间
     struct timespec st_ctim;  // 最近状态改变时间,比如修改了文件的权限的时间
 };
 
// 其中,时间戳结构体的细节是:
struct timespec
{
     long    tv_sec;   /* 秒 */
     long    tv_nsec;  /* 纳秒 */
     1== 1000毫秒 == 1000 000微妙 == 1000 000 000纳秒
};

2、文件的设备号

struct stat
{
     dev_t     st_dev;    // 本文件所在的设备的设备号,适用于非设备文件
     ...
     dev_t     st_rdev;   // 本文件的设备号,适用于特殊设备文件
     ...
     ...
 };

Linux系统为了方便管理,为每一种设备分配了主次设备号,主设备号用来规范设备的类型;次设备号用来规范该种设备在本系统中的序号。设备号是系统资源,在设备被加载的时候分配完毕。

  • 以下两个函数,常用来获取主次设备号:
 unsigned int major(dev_t dev);  // 从 dev 中获取主设备号
 unsigned int minor(dev_t dev);  // 从 dev 中获取次设备号
  • 注意,在结构体stat中有两个类型为 dev_t 的成员。他们分别代表:
    • 本文件所在设备的设备号,适用于非设备文件。
    • 本文件的设备号,适用于特殊设备文件。
    • 特殊设备文件只有设备号的属性,没有文件大小的属性,即st_size是无效的。
  • 解析:
    • 非设备文件是没有设备号的,此时该文件的 st_rdev 成员是无效的。
    • 非设备文件一定是存储某个存储器上的,此时该文件的 st_dev 代表的是其所在存储器的设备号,比如某个硬盘。
    • 特殊设备文件指的是类型为字符设备或块设备的文件,比如键盘、鼠标、硬盘、显示器等。
    • 特殊设备文件的 st_dev 成员是无效的,只有 st_rdev 有效。

示例代码:

int main(int argc, char **argv)
{
     // 获取指定文件的属性信息
     struct stat info;
     stat(argv[1], &info); 

     // 是特定的设备文件,那么 st_rdev 有效且 st_size 无效
     if(S_ISCHR(info.st_mode) || S_ISBLK(info.st_mode))
     {
          printf("该文件的主次设备号分别是:%d,%d\n",
                  major(info.st_rdev),
                  minor(info.st_rdev));
     }

     // 不是设备文件,那么 st_dev 有效且 st_size 也有效
     else
     {
          printf("文件大小是:%d", info.st_size);
          printf("文件所在设备的主次设备号分别是:%d,%d\n",
                  major(info.st_dev),
                  minor(info.st_dev));
     }
}

3、类型与权限

struct stat
{
     mode_t    st_mode;   // 文件类型 + 文件权限
     ...
     ...
 };

在结构体 stat 中,文件的类型和权限并没有分开存储,而是被统一存储到同一个成员 st_mode 中,该成员的内部结构如下所示:
在这里插入图片描述

  • 关键点:
    • st_mode是一个16位的 short 短整型数据。
    • 前4位表达文件的类型,由于Linux文件类型总共7种。(0000到1111)
    • 中间三位分别是 setuid、setgid 和 stickyBit。
    • 后9位表达文件的权限,与三组权限一一对应。
  • 判断文件的类型可以用如下宏即可:
S_ISREG(st_mode)  is it a regular file?                     // 是一个普通文件吗?
S_ISDIR(st_mode)  directory?                                // 是一个目录文件吗?
S_ISCHR(st_mode)  character device?                         // 是一个字符设备文件吗?
S_ISBLK(st_mode)  block device?                             // 是一个块设备文件吗?
S_ISFIFO(st_mode) FIFO (named pipe)?                        // 是一个管道文件吗?(系统编程)
S_ISLNK(st_mode)  symbolic link?  (Not in POSIX.1-1996.)    // 是一个链接文件吗?(快捷方式)
S_ISSOCK(st_mode) socket?  (Not in POSIX.1-1996.)           // 是一个网络文件吗?(网络编程)
  • setuid、setgid(只对普通文件有效)与stickyBit (只针对目录有效)
  • 作用解析:
    • setuid: 使权得文件的使用者获得文件所有者的临时授。 // ubuntu20.04系统没用
    • setgid: 使得文件的使用者获得文件所在目录的所属组的临时授权。
    • stickyBit:使得用户只能增加和删除属于自身的文件,不能删除别的用户的文件 // ubuntu20.04系统有用
  • 修改文件setuID示例:
ls -l
-rwxr-xr-x 1 xxx xxx  8520 Dec 18 00:27 a.out
chmod 04755 a.out
ls -l
-rwxr-xr-x 1 xxx xxx  8520 Dec 18 00:27 a.out

使用 setID 解决实际问题的一个典型例子,就是系统中用于修改密码的命令:

ls -l /usr/bin/passwd 
-rwsr-xr-x 1 root root 59640 Mar 23  2019 /usr/bin/passwd

程序命令 passwd 是属于根用户 root 的,但由于它被设置了setuid,因此以后不管是谁来执行这个程序,在其执行期间都会临时获得 root 的临时授权,这么做是因为修改密码的本质上修改文件 /etc/passwd 的内容,而该文件只有管理员 root 才能修改,设置了 setuid 之后,普通用户既可以通过命令 passwd 来修改此文件,也避免了索取管理员密码的步骤,非常实用。

  • 修改目录stickyBit示例:
ls -l
drwxrwxrwx  2 xxx xxx 4096 Dec 20 18:13 dir/
chmod 01777 dir/
ls -l
drwxrwxrwt  2 xxx xxx 4096 Dec 20 18:13 dir/

4、其他简单文件信息

  • 文件的所有者与所属组:
struct stat
{
     uid_t  st_uid;  // 文件所有者ID
     gid_t  st_gid;  // 文件所在组ID
     ...
};

从结构体 stat 中获取的文件所有者和所属组信息中,只有它们的ID号,而没有切确的名称,可以通过以下函数来获取切确的名称:

struct passwd *getpwuid(uid_t uid);
struct group *getgrgid(gid_t gid);
  • 文件的尺寸相关信息:
struct stat
{
     nlink_t   st_nlink;   // 文件的别名的数目
     off_t     st_size;    // 文件大小
     blksize_t st_blksize; // 标准IO建议的数据块大小  
     blkcnt_t  st_blocks;  // 文件占用的数据块个数
     ...
};
  • 关键点:
    a.文件别名数目也称为硬链接数目,亦即索引个数,文件系统正是以此来判断某个文件是否可以彻底删除的标记。
    b.文件大小就是执行命令 “ls -l” 时所展示大小,包含文件空间的大小。

二、目录操作

1、基本概念

\quad 目录也是一种文件,因此操作流程与普通文件类似,有诸如打开、关闭、定位等概念,但目录是一种特殊的文件,目录存储的数据的最小单位并不是字符,而是目录项。这使得目录跟普通文件又有区别。
\quad 在Linux文件系统的经典结构中,目录不同于文件夹,目录的本质是索引,文件夹的本质是容器。在Linux中,目录有几个要点:

  • 整个分区被分成两部分,一部分称为i节点域,另一部分称为数据域
    • i节点域记录的是整个分区的基本信息,包括分区可用空间和已用空间的管理信息
    • 数据域存储文件实际内容数据
  • 每一个文件(包括目录本身)拥有一个唯一的标识,称为i节点号,分区使用i节点号管理并索引所有的文件,注意i节点号是分区内部的信息,就像美国的公民ID是美国内部管理信息一样,在中国是无效的,i节点号不能跨分区,这也是为什么使用命令 ln 创建文件别名不能跨分区的原因。
  • 目录所存储的数据单元是目录项,目录项指的是结构体
    struct dirent{},其内部保存的是文件的名称、i节点号等基本信息,不包含文件具体内容。
  • 任何一个目录至少包含两个目录项:.和…
    • [.]代表当前目录,[…]代表上一级目录(任意一个文件夹内敲:ls -a即可看到)
    • 如果本目录就是根目录,那么[…]也代表本机目录
      在这里插入图片描述

2、相关的接口函数

(1) 打开目录

#include <dirent.h>
       DIR *opendir(const char *name);
              返回值:成功 返回DIR指针表示打开的那个目录
                           失败 NULL
                 参数:name --》目录的路径名

(2) 读取目录(重点)

struct dirent *readdir(DIR *dirp);
      返回值:成功返回存放当前目录中的文件信息
                  struct dirent {
                            ino_t          d_ino;       //节点编号
                            off_t          d_off;       //文件偏移位置
                            unsigned short d_reclen;    
                            unsigned char  d_type;      //文件类型
                                           DT_BLK      块设备
                                           DT_CHR      字符设备
                                           DT_DIR      目录
                                           DT_FIFO     管道
                                           DT_LNK      软链接
                                           DT_REG      普通文件
                                           DT_SOCK     套接字
                                           DT_UNKNOWN  文件不认识

                            char           d_name[256];  //文件名字
                   };
              失败或者读取目录完毕,会返回NULL
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>

int main(int argc, char const *argv[])
{
    DIR * dir_ret= opendir("/home/lmr");
    struct dirent *dirent;
    if (dir_ret == NULL)
    {
        perror("打开目录失败\n");
        return -1;
    }
    // 循环读取目录
    while (1)
    {
        // 读取目录中的目录项(指的是目录中包含各种类型的文件)
        dirent = readdir(dir_ret);
        if (dirent == NULL)
            break;

        if (dirent->d_type == DT_REG)
            printf("读取的是普通文件,名字为:%s\n", dirent->d_name);
        
        if (dirent->d_type == DT_DIR)
            printf("读取的是子目录(子文件夹),名字为:%s\n", dirent->d_name);
    }
    
    closedir(dir_ret);

    return 0;
}

(3) 新建目录

int mkdir(const char *pathname, mode_t mode);
        返回值:成功 0   失败 -1
           参数:pathname --》目录的路径名
                     mode --》权限0777  0666   
                           表面上写的是0777,但是实际的权限计算公式是:   0777&(~umask)
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>

int main(int argc, char const *argv[])
{
    // 新建空白目录
    // mkdir("./new_dir", 0777);

    // 不允许嵌套新建目录--错误写法
    // mkdir("./new_dir/123/456/789", 0777);

    // 可以这样写--正确写法 
    mkdir("./new_dir", 0777);
    mkdir("./new_dir/123", 0777);
    mkdir("./new_dir/123/456", 0777);
    mkdir("./new_dir/123/456/789", 0777);

    return 0;
}

(4) 删除目录

在这里插入图片描述

示例代码:
int main(void)
{
    // 在家目录下创建一个空目录
    mkdir("/home/xxx/a", 0755);

    // 将空目录删除(以下两条语句等价)
    rmdir("/home/xxx/a");
    remove("/home/xxx/a");
}

(5) 关闭目录

在这里插入图片描述


http://www.kler.cn/a/598427.html

相关文章:

  • 施磊老师高级c++(五)
  • 使用 Go 构建 MCP Server
  • UWB定位技术在矿山、地铁等特殊环境的核心应用
  • 蓝桥杯关于字符串的算法题目(leetcode回文串的判断问题)
  • wangEditor富文本轻量使用及多个编辑器
  • 利用 MATLAB/Simulink 建立完整的控制系统模型,并进行阶跃响应和负载扰动响应仿真
  • 用ACM模式模板刷hot100
  • 一个KADB测试实践
  • 【AI模型】深度解析:DeepSeek的联网搜索的实现原理与认知误区
  • 路由工程师大纲-2:结合AI技术构建路由拓扑与BGP异常检测的知识链体系
  • 计算机操作系统(三) 操作系统的特性、运行环境与核心功能(附带图谱更好对比理解))
  • [DDD架构]不同数据模型DTO、VO、PO、DAO、DO的含义
  • uboot linux-kernel buildroot 编译纪要
  • 如何获取thinkphp的所有发行版本
  • nginx vue history模式 try_files
  • PyTorch核心基础知识点
  • 蓝桥杯web备赛----html篇
  • MATLAB代码丨信号处理:对Python中Librosa库部分函数的重现
  • 如何在望获实时Linux系统上配置静态IP
  • 【LeetCode】大厂面试算法真题回忆(37)--知识图谱新词挖掘