文件属性获取

文件属性获取

1. 课程概述与目标

  • 核心主题:学习如何获取文件的详细属性(File Attributes)。
  • 背景:上一节课学习了访问目录,但仅能获取文件名字和文件类型。
  • 目标:实现类似 ls -al 命令的效果。
    • 展示文件的时间戳(Time Stamp)。
    • 展示文件的读写权限(Read/Write/Execute Permissions)。
    • 展示文件大小、所有者等细致属性。
  • 关键技术:借助 stat 系列函数获取文件元数据。

2. 核心函数:stat、lstat、fstat

这三个函数用于获取文件状态信息,需包含以下头文件(缺一不可):

  • <sys/types.h>
  • <sys/stat.h>
  • <unistd.h>

2.1 函数原型与参数

查阅手册命令:man 2 stat

  1. stat

    • 原型int stat(const char *pathname, struct stat *buf);
    • 参数 1pathname(文件路径字符串)。
    • 参数 2bufstruct stat 结构体指针)。
      • 注意:这是一个“空盘子”模型。调用前需定义结构体变量,传入其地址。函数执行成功后,结构体被填充数据。
      • 传值方式:指针传递(地址传递),用于回传数据。
    • 返回值
      • 成功:返回 0
      • 失败:返回 -1,并设置 errno
  2. lstat

    • 原型int lstat(const char *pathname, struct stat *buf);
    • 区别:与 stat 功能基本一致,但在处理**符号链接(Symbolic Link)**时不同。
      • stat:会跟随符号链接,获取指向目标文件的属性。
      • lstat:不跟随符号链接,获取符号链接文件本身的属性。
    • 应用场景:需要区分链接文件本身与其指向文件时使用。
  3. fstat

    • 原型int fstat(int fd, struct stat *buf);
    • 区别:第一个参数是文件描述符(File Descriptor, fd),而非路径字符串。
    • 应用场景:当文件已经通过 open 打开,拥有 fd 时使用,效率更高。

3. struct stat 结构体分析

函数执行成功后,通过 struct stat 结构体成员获取具体信息。常用成员如下:

  • st_mode:文件类型和访问权限(16 位值,核心重点)。
  • st_size:文件大小(字节)。
  • st_mtime:最后一次修改时间(Time Stamp)。
  • 其他成员:所有者 ID、组 ID 等(本课程暂不深入)。

注意:文件属性具有时效性。获取结构体后,若文件被修改,结构体信息不会自动同步,需注意信息的实时性。

4. st_mode 成员深度解析

st_mode 是一个 16 位的二进制值,每一位代表不同含义。通常通过预定义的**宏(Macro)**进行判断,而非直接操作二进制。

4.1 位分布含义

  • Bit 12-15:标记文件类型(File Type)。
  • Bit 9-11:特殊权限位(SetUID, SetGID, Sticky Bit)。
  • Bit 6-8:所有者(User)权限。
  • Bit 3-5:所属组(Group)权限。
  • Bit 0-2:其他用户(Other)权限。

4.2 文件类型判断(Bit 12-15)

共有 7 种常见文件类型,通过宏进行位运算判断。

  • 判断方式

    1. 位与运算(Bitwise AND)st_mode & S_IFMT 得到类型掩码,再与具体类型宏比较。
    2. 专用宏判断:直接使用如 S_ISREG(st_mode) 等宏(推荐)。
  • 常见类型宏

    • S_IFREG:普通文件(Regular File),ls 显示为 -
    • S_IFDIR:目录(Directory),ls 显示为 d
    • S_IFLNK:符号链接(Symbolic Link),ls 显示为 l
    • S_IFCHR:字符设备(Character Device),ls 显示为 c
    • S_IFBLK:块设备(Block Device),ls 显示为 b
    • S_IFIFO:管道(FIFO/Pipe),ls 显示为 p
    • S_IFSOCK:套接字(Socket),ls 显示为 s
  • 宏的数值形式:通常以八进制表示。

    • 例如:类型掩码部分对应八进制的高位,权限部分对应低三位(如 0777)。

4.3 权限判断(Bit 0-11)

权限分为三组(User, Group, Other),每组包含 Read(r), Write(w), Execute(x)。

  • 权限宏
    • S_IRUSR (User Read), S_IWUSR (User Write), S_IXUSR (User Execute)。
    • S_IRGRP, S_IWGRP, S_IXGRP
    • S_IROTH, S_IWOTH, S_IXOTH
  • 数值对应
    • r = 4
    • w = 2
    • x = 1
    • 每组最大值为 7 (4+2+1)。

5. 代码实现 walkthrough:模仿 ls -l

5.1 基础框架搭建

  1. 包含头文件
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h> // 用于 open 函数
    #include <time.h>  // 用于时间格式化
    
  2. 获取文件名:通过命令行参数 argv[1] 获取目标文件路径。
  3. 定义结构体
    struct stat st;
    
  4. 调用函数
    if (stat(argv[1], &st) == -1) {
        perror("stat error");
        return -1;
    }
    

5.2 实现文件类型输出

使用 switch-case 结构判断 st_mode 的文件类型部分。

  • 逻辑:先通过位运算提取类型部分,再匹配宏。
  • 代码示例
    switch (st.st_mode & S_IFMT) {
        case S_IFREG:
            printf("-");
            break;
        case S_IFDIR:
            printf("d");
            break;
        case S_IFLNK:
            printf("l");
            break;
        case S_IFCHR:
            printf("c");
            break;
        case S_IFBLK:
            printf("b");
            break;
        case S_IFIFO:
            printf("p");
            break;
        case S_IFSOCK:
            printf("s");
            break;
        default:
            printf("?");
            break;
    }
    

5.3 实现权限位输出(rwx)

权限共有 9 个位(User 3 位 + Group 3 位 + Other 3 位)。

  • 算法逻辑

    1. 循环 9 次(或从高位到低位遍历)。
    2. 利用位移(Shift)和取模(Modulo)运算确定当前位对应 rw 还是 x
    3. 视频中的特定算法
      • 定义循环变量 n 从 8 递减到 0。
      • 利用 n % 3 的结果判断权限类型:
        • 余数为 2:对应 r (Read)
        • 余数为 1:对应 w (Write)
        • 余数为 0:对应 x (Execute)
      • 注意:由于位移方向与权限顺序的对应关系,需仔细验证位移量。视频中提到通过 n 的变化规律挂钩 rwx 顺序。
    4. 判断有无权限
      • st_mode 左移或右移相应位数后与 1 进行位与运算。
      • 若结果为 1,打印对应字符(r/w/x);若为 0,打印 -
  • 简化逻辑
    也可以直接分别与 S_IRUSR, S_IWUSR 等宏进行位与判断,代码可读性更高。

    // 示例:判断所有者读权限
    if (st.st_mode & S_IRUSR) printf("r"); else printf("-");
    

5.4 实现文件大小输出

  • 直接访问 st_size 成员。
  • 格式化为十进制整数输出。
    printf(" %ld", st.st_size);
    

5.5 实现时间戳输出

  • 成员st_mtime(最后一次修改时间)。
  • 类型time_t
  • 格式化函数ctime()
    • 原型:char *ctime(const time_t *timep);
    • 用法:printf("%s", ctime(&st.st_mtime));
    • 注意:ctime 返回的字符串包含换行符,需注意输出格式控制。
  • 头文件:需包含 <time.h>

6. 调试与常见问题记录

6.1 编译与运行问题

  1. 头文件缺失
    • 使用 open 函数未包含 <fcntl.h> 会导致 O_RDONLY 等宏未定义。
    • 使用时间函数未包含 <time.h> 会导致 ctime 报错。
    • 解决:根据编译器报错补充对应头文件。
  2. open 函数标志位
    • 文件 I/O (open) 与标准 I/O (fopen) 标志不同。
    • open 需使用 O_RDONLY, O_WRONLY 等宏,而非 "r", "w" 字符串。
  3. switch-case 语法错误
    • Case 标签必须是整型常量表达式。确保使用的宏(如 S_IFREG)已正确包含头文件。
    • 视频中出现 case 位置报错,原因是宏未定义或拼写错误(如 S_IFLNK 写错)。

6.2 逻辑验证

  • 文件类型测试
    • 普通文件(.c):应输出 -
    • 目录(.):应输出 d
    • 链接文件:stat 会显示目标文件类型,lstat 显示 l
  • 权限位测试
    • 通过 chmod 修改文件权限后运行程序,验证输出是否同步变化。
  • 输出格式对齐
    • ls -l 输出有固定格式,自行实现时需注意空格和换行处理,避免信息堆叠。

7. 总结与建议

  • 函数选择
    • 一般场景使用 stat
    • 涉及符号链接分析使用 lstat
    • 已打开文件描述符场景使用 fstat
  • 位运算技巧
    • 理解 st_mode 的位分布是核心。
    • 优先使用系统提供的宏(S_ISxxx, S_IRxxx)而非硬编码数值,增强可移植性。
  • 扩展学习
    • 查阅 man 2 stat 了解所有结构体成员。
    • 尝试实现完整的 ls -l 输出(包括硬链接数、所有者名、组名等,需结合 pwd.hgrp.h)。
    • 研究特殊权限位(SetUID, Sticky Bit)的含义与判断。
标准IO:目录操作 2026-03-02
静态库与动态库 2026-03-04

评论区