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

《UNIX环境高级编程》读书笔记05: 文件和目录

作者: andylin02
学习章节: 第4章 文件和目录
关键词: stat/lstat/fstatat、文件类型、文件权限、umask、chmod、chown、硬链接、符号链接、目录遍历、文件时间戳


一、引言:从文件I/O到文件属性的跨越

上一章我们聚焦于文件I/O的五个核心系统调用,理解了如何读写文件数据。本章更进一步,将目光投向文件本身——不仅仅是数据,还包括文件的元数据。第3章关注“文件的内容是什么”,第4章关注“文件是什么”。前者是读写数据,后者是理解文件的身份、权限、类型和时间等属性,二者构成了Unix文件操作的两大基石。

本章的学习目标:掌握如何获取和修改文件的元数据,理解Unix文件系统的组织结构,学会遍历目录和操作文件权限。

二、stat函数族:获取文件属性

2.1 stat、fstat、lstat和fstatat

UNIX系统提供了四个stat函数,用于获取文件的属性信息:

#include <sys/stat.h> int stat(const char *restrict pathname, struct stat *restrict buf); int fstat(int fd, struct stat *buf); int lstat(const char *restrict pathname, struct stat *restrict buf); int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);
函数参数类型特点
stat路径名跟随符号链接,返回链接指向的文件信息
fstat文件描述符获取已打开文件的属性
lstat路径名不跟随符号链接,返回符号链接本身的信息
fstatat目录文件描述符+路径名相对于目录打开文件,flag控制是否跟随符号链接

关键区别:当文件是符号链接时,stat返回链接所指向的文件的信息,而lstat返回符号链接本身的信息。在遍历目录树时,使用lstat可以正确识别符号链接本身,而不是陷入无限循环。

2.2 stat结构体

这四个函数通过buf指针填充一个struct stat结构体。虽然具体实现可能有所不同,但其基本形式如下:

struct stat { mode_t st_mode; // 文件类型和访问权限 ino_t st_ino; // i节点号 dev_t st_dev; // 文件所在设备的设备号 dev_t st_rdev; // 特殊文件的设备号 nlink_t st_nlink; // 硬链接数 uid_t st_uid; // 文件所有者的用户ID gid_t st_gid; // 文件所有者的组ID off_t st_size; // 文件大小(字节数) struct timespec st_atime; // 最后访问时间 struct timespec st_mtime; // 最后修改时间 struct timespec st_ctime; // 最后状态更改时间 blksize_t st_blksize; // 最佳I/O块大小 blkcnt_t st_blocks; // 分配的磁盘块数 };

POSIX.1与XSI扩展:POSIX.1未要求st_rdevst_blksizest_blocks字段,Single UNIX Specification的XSI扩展才定义了这些字段。timespec结构按照秒和纳秒提供了更高精度的时间戳,为了保持兼容性,旧的名字可以定义为tv_sec成员。

三、文件类型

3.1 UNIX的七种文件类型

UNIX/Linux系统将文件分为7种类型,通过stat结构的st_mode成员判断:

文件类型测试宏说明
普通文件S_ISREG()最常见类型,包含某种形式的数据,UNIX内核不区分文本或二进制
目录文件S_ISDIR()包含其他文件名和指针,只有内核可以直接写目录
块特殊文件S_ISBLK()对设备提供带缓冲的访问,每次以固定长度为单位(如磁盘)
字符特殊文件S_ISCHR()对设备提供不带缓冲的访问,每次长度可变(如终端)
FIFOS_ISFIFO()命名管道,用于进程间通信
套接字S_ISSOCK()用于进程间的网络通信
符号链接S_ISLNK()指向另一个文件的间接指针

3.2 判断文件类型的代码示例

#include "apue.h" int main(int argc, char *argv[]) { int i; struct stat buf; char *ptr; for (i = 1; i < argc; i++) { printf("%s: ", argv[i]); if (lstat(argv[i], &buf) < 0) { err_ret("lstat error"); continue; } if (S_ISREG(buf.st_mode)) ptr = "regular"; else if (S_ISDIR(buf.st_mode)) ptr = "directory"; else if (S_ISCHR(buf.st_mode)) ptr = "character special"; else if (S_ISBLK(buf.st_mode)) ptr = "block special"; else if (S_ISFIFO(buf.st_mode)) ptr = "fifo"; else if (S_ISLNK(buf.st_mode)) ptr = "symbolic link"; else if (S_ISSOCK(buf.st_mode)) ptr = "socket"; else ptr = "** unknown mode **"; printf("%s\n", ptr); } exit(0); }

为什么使用lstat而不是statlstat返回符号链接本身的信息,而stat会跟随符号链接返回目标文件的信息。用lstat可以正确识别符号链接这种文件类型。

四、文件访问权限

4.1 九个基本权限位

每个文件有9个访问权限位,取自<sys/stat.h>

类别常量含义
用户(所有者)S_IRUSR用户读
S_IWUSR用户写
S_IXUSR用户执行
S_IRGRP组读
S_IWGRP组写
S_IXGRP组执行
其他S_IROTH其他读
S_IWOTH其他写
S_IXOTH其他执行

4.2 目录执行权限的含义

对于目录,执行权限位常被称为搜索位。打开任一类型的文件时,对该路径名的每一级目录都必须具有执行权限。

权限对文件的作用对目录的作用
读(r)读取文件内容读取目录中的文件名列表
写(w)修改文件内容在目录中创建/删除文件
执行(x)执行文件(对脚本/程序)搜索目录(进入目录的通行证)

4.3 新文件和目录的所有权

当创建一个新文件时:

  • 用户ID:设置为进程的有效用户ID

  • 组ID:可以是进程的有效组ID,也可以是所在目录的组ID(若目录设置了setgid位)

五、umask函数:文件创建掩码

umask函数为进程设置文件模式创建屏蔽字,并返回之前的值:

#include <sys/stat.h> mode_t umask(mode_t cmask);

工作原理umask的功能是创建新文件时屏蔽掉用户不希望生效的权限位。对于文件,权限的最大值是666(不允许直接赋予执行权限);目录则允许777

示例:创建两个文件,第一个的umask为0,第二个的umask值禁止所有组和其他用户的访问权限。

#include "apue.h" #include <fcntl.h> #define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) int main(void) { umask(0); if (creat("foo", RWRWRW) < 0) err_sys("creat error for foo"); umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (creat("bar", RWRWRW) < 0) err_sys("creat error for bar"); exit(0); }

运行结果:

  • foo:权限为-rw-rw-rw-(umask=0)

  • bar:权限为-rw-------(umask禁止了组和其他用户的读写)

📌常用umask值002(阻止其他用户写)、022(阻止同组成员和其他用户写)、027(阻止同组成员写,阻止其他用户读写执行)。

六、chmod和fchmod函数

chmodfchmod用于更改已有文件的访问权限:

#include <sys/stat.h> int chmod(const char *pathname, mode_t mode); int fchmod(int fd, mode_t mode);

权限要求:要修改文件权限,进程的有效用户ID必须等于文件的所有者ID,或者进程具有超级用户权限。

示例:修改文件权限

#include "apue.h" int main(void) { struct stat statbuf; // 打开set-group-ID位,关闭组执行位 if (stat("foo", &statbuf) < 0) err_sys("stat error for foo"); if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) err_sys("chmod error for foo"); // 设置绝对模式为 rw-r--r-- if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) err_sys("chmod error for bar"); exit(0); }

七、粘着位(Sticky Bit)

粘着位(S_ISVTX)在历史上用于普通文件的交换区保存,现代系统对目录设置了粘着位后,只有目录所有者、文件所有者或超级用户才能删除或重命名目录中的文件。这个特性常用于/tmp/var/tmp等共享目录,防止用户删除其他用户的临时文件。

📌普通文件的粘着位:只有超级用户可以设置普通文件的粘着位,以防止不怀好意的用户设置粘住位。

八、硬链接与符号链接

8.1 链接函数

#include <unistd.h> int link(const char *existingpath, const char *newpath); // 创建硬链接 int unlink(const char *pathname); // 删除目录项 int symlink(const char *actualpath, const char *sympath); // 创建符号链接 ssize_t readlink(const char *pathname, char *buf, size_t bufsize); // 读符号链接 int remove(const char *pathname); // 对文件=unlink,对目录=rmdir int rename(const char *old, const char *new);

8.2 硬链接与符号链接的对比

特性硬链接符号链接
本质目录中另一个指向相同i节点的文件名存储目标文件路径的特殊文件
跨文件系统❌ 否,必须在同一文件系统内✅ 是
指向目录❌ 否(只有超级用户可以)✅ 是
链接数影响st_nlink增加st_nlink不变
删除原文件数据仍存在(通过其他硬链接)链接变成“悬空”(dangling)
文件大小与原文件共享数据块存储路径字符串,st_size为路径长度

硬链接的限制:硬链接通常要求链接和文件位于同一文件系统中,且只有超级用户才能创建指向目录的硬链接。引入符号链接正是为了避开这些限制。

8.3 目录遍历的流程

目录遍历的核心函数调用链如下:

DIR *dp = opendir("."); struct dirent *entry; while ((entry = readdir(dp)) != NULL) { printf("%s\n", entry->d_name); } closedir(dp);

opendir函数返回指向DIR结构的指针,readdir循环读取每个目录项,返回dirent结构指针,d_name成员为文件名。

九、文件时间戳

9.1 三个时间值

每个文件维护三个时间字段:

字段说明修改操作ls选项
st_atime最后访问时间readexeclstat-u
st_mtime最后修改时间(内容)writetruncate默认
st_ctime最后状态更改时间(i节点)chmodchownlinkunlink-c

9.2 修改时间戳的函数

#include <sys/time.h> int utimes(const char *pathname, const struct timeval times[2]); int futimens(int fd, const struct timespec times[2]); int utimensat(int fd, const char *path, const struct timespec times[2], int flag);

十、目录操作函数汇总

函数功能
mkdir创建新目录
rmdir删除空目录
opendir打开目录,返回DIR*指针
readdir读取目录项,返回struct dirent*
rewinddir重置目录流到开头
closedir关闭目录流
telldir/seekdir获取/设置目录流位置
chdir/fchdir更改当前工作目录
getcwd获取当前工作目录绝对路径

十一、完整代码示例

11.1 实现简单的ls命令

#include "apue.h" #include <dirent.h> int main(int argc, char *argv[]) { DIR *dp; struct dirent *dirp; if (argc != 2) err_quit("usage: ls directory_name"); if ((dp = opendir(argv[1])) == NULL) err_sys("can't open %s", argv[1]); while ((dirp = readdir(dp)) != NULL) printf("%s\n", dirp->d_name); closedir(dp); exit(0); }

11.2 递归遍历目录树(ftw风格的简化版)

#include "apue.h" #include <dirent.h> #include <limits.h> typedef int Myfunc(const char *, const struct stat *, int); static Myfunc myfunc; static int myftw(char *, Myfunc *); static int dopath(Myfunc *); static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot; int main(int argc, char *argv[]) { int ret; if (argc != 2) err_quit("usage: ftw <starting-pathname>"); ret = myftw(argv[1], myfunc); ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock; if (ntot == 0) ntot = 1; printf("regular files = %7ld, %5.2f %%\n", nreg, nreg * 100.0 / ntot); printf("directories = %7ld, %5.2f %%\n", ndir, ndir * 100.0 / ntot); printf("block special = %7ld, %5.2f %%\n", nblk, nblk * 100.0 / ntot); printf("char special = %7ld, %5.2f %%\n", nchr, nchr * 100.0 / ntot); printf("FIFOs = %7ld, %5.2f %%\n", nfifo, nfifo * 100.0 / ntot); printf("symbolic links = %7ld, %5.2f %%\n", nslink, nslink * 100.0 / ntot); printf("sockets = %7ld, %5.2f %%\n", nsock, nsock * 100.0 / ntot); exit(ret); } #define FTW_F 1 // 非目录文件 #define FTW_D 2 // 目录 #define FTW_DNR 3 // 不可读目录 #define FTW_NS 4 // 无法获取stat信息 static char *fullpath; static size_t pathlen; static int myftw(char *pathname, Myfunc *func) { fullpath = path_alloc(&pathlen); if (pathlen <= strlen(pathname)) { pathlen = strlen(pathname) * 2; if ((fullpath = realloc(fullpath, pathlen)) == NULL) err_sys("realloc failed"); } strcpy(fullpath, pathname); return dopath(func); } static int dopath(Myfunc *func) { struct stat statbuf; struct dirent *dirp; DIR *dp; int ret, n; if (lstat(fullpath, &statbuf) < 0) return func(fullpath, &statbuf, FTW_NS); if (S_ISDIR(statbuf.st_mode) == 0) return func(fullpath, &statbuf, FTW_F); // 是目录,先处理该目录本身 if ((ret = func(fullpath, &statbuf, FTW_D)) != 0) return ret; n = strlen(fullpath); if (n + NAME_MAX + 2 > pathlen) { pathlen *= 2; if ((fullpath = realloc(fullpath, pathlen)) == NULL) err_sys("realloc failed"); } fullpath[n++] = '/'; fullpath[n] = 0; if ((dp = opendir(fullpath)) == NULL) return func(fullpath, &statbuf, FTW_DNR); while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) continue; strcpy(&fullpath[n], dirp->d_name); if ((ret = dopath(func)) != 0) break; } fullpath[n-1] = 0; if (closedir(dp) < 0) err_ret("can't close directory %s", fullpath); return ret; } static int myfunc(const char *pathname, const struct stat *statptr, int type) { switch (type) { case FTW_F: switch (statptr->st_mode & S_IFMT) { case S_IFREG: nreg++; break; case S_IFBLK: nblk++; break; case S_IFCHR: nchr++; break; case S_IFIFO: nfifo++; break; case S_IFLNK: nslink++; break; case S_IFSOCK: nsock++; break; case S_IFDIR: ndir++; break; } break; case FTW_D: ndir++; break; case FTW_DNR: err_ret("can't read directory %s", pathname); break; case FTW_NS: err_ret("stat error for %s", pathname); break; } return 0; }

十二、第4章知识点速查表

知识点核心内容
stat系列函数stat/fstat/lstat/fstatat的区别
七种文件类型普通文件、目录、块/字符特殊文件、FIFO、套接字、符号链接
文件权限9个基本权限位 + 粘着位
umask文件创建掩码,屏蔽不需要的权限位
chmod/fchmod修改已有文件的权限
chown/fchown修改文件所有者
硬链接 vs 符号链接i节点 vs 路径指针,跨文件系统、跨目录的区别
目录遍历opendir/readdir/closedir
三个时间戳st_atimest_mtimest_ctime的区别
文件系统结构i节点、目录项、数据块的层次关系

十三、学习心得

第4章是Unix文件系统的“百科全书”。本章的核心在于理解i节点、目录项和路径解析的关系。重点掌握:

  • statlstat的区别:符号链接的处理方式

  • 文件权限模型:9个基本位 + 粘着位的含义

  • 硬链接与符号链接的本质区别:一个指向i节点,一个指向路径

  • 目录遍历的方法opendir/readdir是递归处理文件树的基石

  • 三个文件时间戳的含义:访问、修改、状态更改的区别

本章内容多而杂,但每条知识点都有实际用途。建议配合man手册和大量编码练习,将各种系统调用的行为理解透彻。

十四、下一篇预告

下一篇将进入第5章“标准I/O库”,内容包括:

  • 标准I/O库与系统调用的关系

  • 流与FILE对象

  • 缓冲类型(全缓冲、行缓冲、无缓冲)

  • 打开、关闭、读写流(fopenfclosefreadfwrite

  • 格式化I/O(printfscanf家族)

  • 临时文件(tmpfiletmpnam

  • 内存流(fmemopenopen_memstream

敬请期待!


本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

http://www.jsqmd.com/news/736626/

相关文章:

  • nli-MiniLM2-L6-H768详细步骤:supervisor日志轮转配置防止/workspace日志爆满
  • ToastFish:如何在工作间隙悄无声息地提升英语词汇量?
  • 手机千问 文心 元宝 Kimi怎么导出pdf
  • 【金融级容器安全合规白皮书】:Docker 27等保2.0三级适配全栈落地指南(含央行《金融科技产品认证规则》映射表)
  • Conductor微服务编排引擎:5步掌握分布式工作流管理
  • 2026年3月知名的保温被品牌推荐,温室大棚遮阳网/散射幕布/内遮阳保温幕/保温被/黑白遮阳网,保温被品牌口碑推荐 - 品牌推荐师
  • C++初阶:入门基础
  • StructBERT中文large模型效果展示:句式变换(主动/被动)、同义词替换高鲁棒性案例
  • 【踩坑】你以为在过人机验证,实际上正亲手把木马装进电脑 | ClickFix攻击
  • JSON 小传:从 JavaScript 捡来的“数据网红”
  • 必知必会:大模型对齐数据构造与PPO算法详解
  • 2026五一出行运动扭伤,五种常用止痛药怎么选?
  • 2026变频互感器测试仪技术解析:互感器励磁特性综合测试仪/互感器特性测试仪/充气式试验变压器/变压器综合特性测试仪/选择指南 - 优质品牌商家
  • Android蓝牙开发深度解析:从技术基础到面试准备
  • 如何快速掌握AssetRipper:Unity资源逆向工程的完整指南
  • CMOS与双极型运算放大器特性对比与应用设计
  • 收藏!2026年大模型红利爆发|程序员+小白必看,阿里跳槽案例+薪资表
  • 2026年郑州博亚财务服务有限公司性价比高吗? - myqiye
  • Phi-3-mini-4k-instruct-gguf部署教程:多模型并行服务配置与端口路由策略
  • 必知必会:奖励模型训练与PPO稳定训练方法详解
  • NVIDIA G-Assist插件开发实战:从Twitch集成到性能优化
  • Keras Hub:一行代码加载预训练模型,加速深度学习开发与迁移学习
  • Qwen2.5-VL-7B-InstructGPU优化:梯度检查点+FlashAttention-2启用指南
  • 洛阳博亚财务口碑好不好?值得信赖不? - myqiye
  • IDE Eval Resetter:JetBrains IDE试用期管理的终极解决方案
  • GLM-5.1在Agent场景的性价比拆解:94%的Opus水准,价格只要1/3
  • LM保姆级使用手册:从零输入提示词到高清人像生成的完整步骤详解
  • 3分钟终极指南:用KMS智能激活脚本永久激活Windows和Office
  • 中山市厨凰电器线下销售地点在哪 - mypinpai
  • 大数据缺失值处理:bigMICE分布式解决方案解析