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

UNIX下C语言编程与实践22-UNIX 文件其他属性获取:stat 结构与 localtime 函数的使用

从 stat 结构解析到时间戳转换,掌握 UNIX 文件全属性的获取与格式化

一、核心基础:stat 结构中的文件其他属性

在 UNIX 系统中,struct stat 结构体不仅包含文件类型(st_mode)和权限信息,还存储了一系列关键的文件属性,如链接数、所有者 ID、文件大小、时间戳等。这些属性是文件管理和系统运维的重要依据,通过 stat 或 lstat 函数可完整获取。

1. stat 结构核心属性解析

以下是除文件类型和权限外,struct stat 中最常用的文件属性字段,定义在 <sys/stat.h> 头文件中:

属性字段数据类型核心含义单位/格式示例值
st_nlinknlink_t文件的硬链接数(目录默认 2 个:. 和 ..整数1(普通文件,无额外硬链接)、2(空目录)
st_uiduid_t文件所有者的用户 ID(UID),对应 /etc/passwd 中的用户整数(用户唯一标识)1000(普通用户 bill)、0(root 用户)
st_gidgid_t文件所属组的组 ID(GID),对应 /etc/group 中的组整数(组唯一标识)1000(组 bill)、20(组 dialout)
st_sizeoff_t文件大小(普通文件:字节数;设备文件:0;符号链接:目标路径长度)字节(64 位整数)1234(普通文件,1234 字节)、0(设备文件)、4(符号链接,目标路径 "bash")
st_atimetime_t文件最后访问时间(Access Time):读取文件内容时更新时间戳(自 1970-01-01 00:00:00 UTC 起的秒数)1727500800(对应 2024-09-28 10:00:00)
st_mtimetime_t文件最后修改时间(Modify Time):修改文件内容时更新(ls -l 默认显示)时间戳1727499000(对应 2024-09-28 09:30:00)
st_ctimetime_t文件最后状态变更时间(Change Time):修改文件属性(如权限、所有者)时更新时间戳1727499000(与修改时间同步,若仅改权限则单独更新)
st_blocksblkcnt_t文件占用的数据块数(每块默认 512 字节,与 ls -s 输出一致)块数8(对应 8×512=4096 字节,即 1 个 4KB 数据块)

关键认知

  • st_nlink 对目录的特殊意义:空目录的硬链接数为 2(. 指向自身,.. 指向父目录),每新增一个子目录,父目录的 st_nlink 增加 1(子目录的 .. 指向父目录);
  • st_sizest_size 恒为 0,符号链接的 st_size 是目标路径字符串的长度(不含终止符 \0);
  • 三个时间戳的区别:st_atime 对应“读操作”,st_mtime 对应“写内容”,st_ctime 对应“改属性”,三者独立更新,需根据需求选择使用。

二、C 语言实战:GetFileOtherAttr 函数实现

通过编写 GetFileOtherAttr 函数,结合 lstat 函数获取 struct stat 结构,再通过用户/组 ID 转换、时间戳格式化等操作,可生成类似 ls -l 命令的文件属性输出(如链接数、所有者、修改时间)。以下是完整实现流程。

1. 完整程序实现:获取并格式化文件属性

程序功能:接收文件路径,获取文件的硬链接数、所有者/组、文件大小、修改时间等属性,将 UID/GID 转换为用户名/组名,将时间戳转换为可读时间,最终输出格式化结果。

#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 函数:将 UID 转换为用户名(如 1000 → "bill")
const char* UidToName(uid_t uid) {struct passwd *pwd = getpwuid(uid);return (pwd != NULL) ? pwd->pw_name : "unknown";
}// 函数:将 GID 转换为组名(如 1000 → "bill")
const char* GidToName(gid_t gid) {struct group *grp = getgrgid(gid);return (grp != NULL) ? grp->gr_name : "unknown";
}// 函数:将 time_t 时间戳转换为可读时间字符串(格式:YYYY-MM-DD HH:MM:SS)
void TimeToStr(time_t timestamp, char *time_str, size_t max_len) {struct tm* local_tm = localtime(×tamp);if (local_tm == NULL) {strncpy(time_str, "invalid time", max_len);time_str[max_len - 1] = '\0';return;}// 格式化时间:年(tm_year+1900)、月(tm_mon+1)、日、时、分、秒strftime(time_str, max_len, "%Y-%m-%d %H:%M:%S", local_tm);
}// 核心函数:获取文件其他属性并格式化输出
void GetFileOtherAttr(const char *file_path) {struct stat file_stat;if (lstat(file_path, &file_stat) == -1) {perror("lstat error");return;}// 1. 转换 UID/GID 为用户名/组名const char *owner = UidToName(file_stat.st_uid);const char *group = GidToName(file_stat.st_gid);// 2. 转换修改时间戳为可读字符串char mtime_str[32];TimeToStr(file_stat.st_mtime, mtime_str, sizeof(mtime_str));// 3. 格式化输出(模仿 ls -l 格式)printf(" Links: %-3ld Owner: %-8s Group: %-8s Size: %-8lld Modify Time: %s File: %s\n",(long)file_stat.st_nlink,owner,group,(long long)file_stat.st_size,mtime_str,file_path);
}int main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usage: %s <file_path1> [file_path2 ...]\n", argv[0]);exit(EXIT_FAILURE);}// 遍历所有命令行参数,输出每个文件的属性printf("属性格式: Links Owner Group Size Modify Time File\n");printf("--------------------------------------------------------------------------\n");for (int i = 1; i < argc; i++) {GetFileOtherAttr(argv[i]);}return EXIT_SUCCESS;
}

2. 关键函数解析

  • getpwuid(uid_t uid)

    定义在 <pwd.h>,通过 UID 获取 struct passwd 结构体,其中 pw_name 字段为用户名。若 UID 不存在(如自定义无效 UID),返回 NULL

  • getgrgid(gid_t gid)

    定义在 <grp.h>,通过 GID 获取 struct group 结构体,其中 gr_name 字段为组名。与 getpwuid 类似,无效 GID 返回 NULL

  • localtime(const time_t *timer)

    定义在 <time.h>,将 time_t 类型的时间戳转换为本地时区的 struct tm 结构体(包含年、月、日等字段)。核心用于时间戳的“可读化”转换,下文将详细讲解。

  • strftime(char *s, size_t maxsize, const char *format, const struct tm *tm)

    定义在 <time.h>,按指定格式将 struct tm 结构体格式化为字符串(如 %Y-%m-%d %H:%M:%S 对应“2024-09-28 09:30:00”),避免手动拼接时间字段的繁琐操作。

3. 程序编译与多场景测试

将代码保存为 fileattr.c,编译后对不同类型的文件(普通文件、目录、符号链接、设备文件)进行测试,验证属性获取的正确性。

步骤 1:编译程序

gcc fileattr.c -o fileattr

步骤 2:测试 1:普通文件(/etc/passwd)

./fileattr /etc/passwd

属性格式

Links  Owner  Group  Size  Modify Time           File
-----------------------------------------------------------------------------
1      root   root   2345  2024-09-27 18:00:00  /etc/passwd

结果验证

普通文件 /etc/passwd 的硬链接数为 1,所有者和组均为 root,大小 2345 字节,修改时间与系统实际一致,符合预期。

步骤 3:测试 2:目录文件(空目录与非空目录)

创建空目录和含子目录的目录

mkdir empty_dir
mkdir -p non_empty_dir/sub_dir

测试两个目录的属性

./fileattr empty_dir non_empty_dir

属性格式

Links Owner Group Size Modify Time File
--------------------------------------------------------------------------
Links: 2 Owner: bill Group: bill Size: 4096 2024-09-28 10:10:00 File: empty_dir
Links: 3 Owner: bill Group: bill Size: 4096 2024-09-28 10:11:00 File: non_empty_dir

结果验证

空目录 empty_dir 的硬链接数为 2(.和..),含子目录的 non_empty_dir 硬链接数为 3(子目录 sub_dir 的 .. 增加 1 个链接),正确反映目录链接数的特性。

步骤 4:测试 3:符号链接与设备文件

创建符号链接(指向 /etc/passwd)

ln -s /etc/passwd passwd_link

测试符号链接和字符设备文件 /dev/tty

./fileattr passwd_link /dev/tty

属性格式

Links Owner Group Size Modify Time File
--------------------------------------------------------------------------
Links: 1 Owner: bill Group: bill Size: 11 2024-09-28 10:15:00 File: passwd_link
Links: 1 Owner: root Group: tty Size: 0 2024-09-28 08:00:00 File: /dev/tty

结果验证

符号链接 passwd_linkst_size 为 11(目标路径 /etc/passwd 的长度),设备文件 /dev/ttyst_size 为 0,所有者为 root、组为 tty,与设备文件特性完全一致。

三、时间戳转换核心:localtime 函数详解

UNIX 系统中,文件的时间属性(st_atimest_mtimest_ctime)均以 time_t 类型的时间戳存储(自 1970-01-01 00:00:00 UTC 起的秒数),无法直接阅读。localtime 函数是将时间戳转换为“年、月、日、时、分、秒”可读格式的核心工具,其使用逻辑和细节需重点掌握。

1. localtime 函数的使用流程

localtime 函数的核心作用是“将 UTC 时间戳转换为本地时区的结构化时间”,具体使用流程如下:

// 1. 定义变量:时间戳、结构化时间、时间字符串
time_t timestamp = file_stat.st_mtime;  // 从 stat 结构获取文件修改时间戳
struct tm* local_tm;  // 存储结构化时间的指针
char time_str[32];  // 存储最终的可读时间字符串// 2. 调用 localtime 转换时间戳
local_tm = localtime(×tamp);
if (local_tm == NULL) {perror("localtime error");return;
}// 3. 提取 struct tm 中的时间字段(注意:部分字段需调整)
int year = local_tm->tm_year + 1900;  // tm_year:自 1900 年起的年数 → 需加 1900
int month = local_tm->tm_mon + 1;  // tm_mon:0-11(1 月为 0)→ 需加 1
int day = local_tm->tm_mday;  // tm_mday:1-31(无需调整)
int hour = local_tm->tm_hour;  // tm_hour:0-23(24 小时制)
int minute = local_tm->tm_min;  // tm_min:0-59
int second = local_tm->tm_sec;  // tm_sec:0-60(60 为闰秒)// 4. 格式化输出(手动拼接或用 strftime)
snprintf(time_str, sizeof(time_str), "%d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second);
printf("Modify Time: %s\n", time_str);  // 输出:Modify Time: 2024-09-28 09:30:00

2. struct tm 结构体字段解析

struct tm 结构体

定义在 <time.h> 中,包含完整的时间字段,部分字段需调整后才能直接使用,具体解析如下:

  • tm_year:自 1900 年起的年数(2024 → 124)
  • tm_mon:月份(0-11)(9 月 → 8)
  • tm_mday:月内天数(1-31)(28 日 → 28)
  • tm_hour:小时(0-23)(9 点 → 9)
  • tm_min:分钟(0-59)(30 分 → 30)
  • tm_sec:秒(0-60)(15 秒 → 15)
  • tm_wday:周内天数(0-6,周日为 0)(周六 → 6)
  • tm_yday:年内天数(0-365)(9 月 28 日 → 271)
  • tm_isdst:夏令时标识(1=启用,0=禁用,-1=未知)(未启用 → 0)

常见错误点:

  • 忘记调整 tm_year 和 tm_mon:直接使用 tm_year 会得到“124”(2024-1900),直接使用 tm_mon 会得到“8”(9 月),导致时间显示错误;
  • 忽略 localtime 的返回值检查:当时间戳无效(如负数)时,localtime 返回 NULL,若未检查会导致空指针访问,程序崩溃;
  • 混淆本地时区与 UTC 时区:localtime 转换的是本地时区时间,若需 UTC 时间,应使用 gmtime 函数(用法与 localtime 一致)。

3. localtime 函数的线程安全问题

localtime 函数存在一个关键缺陷:其返回的 struct tm 指针指向静态全局变量,多个线程同时调用时会导致数据竞争,出现时间错乱(如线程 A 的时间被线程 B 覆盖)。

线程安全解决方案:

  • 方案 1:使用 localtime_r(推荐,POSIX 标准):

    localtime_r 是 localtime 的线程安全版本,需手动传入用户定义的 struct tm 变量地址,避免静态变量竞争,用法如下:

    struct tm local_tm; localtime_r(×tamp, &local_tm); // 结果存储在用户提供的 local_tm 中 int year = local_tm.tm_year + 1900; // 直接访问结构体成员(非指针)

  • 方案 2:使用互斥锁(兼容非 POSIX 系统):

    若系统不支持 localtime_r(如部分嵌入式系统),可通过互斥锁(pthread_mutex_t)确保同一时间只有一个线程调用 localtime,避免竞争。

四、其他时间相关函数拓展

除 localtime 外,UNIX 还提供了 ctimemktimestrptime 等时间函数,分别用于快速获取可读时间、时间戳反向转换、字符串解析为时间结构,满足不同场景的时间处理需求。

函数原型核心功能使用场景示例代码输出结果
char *ctime(const time_t *timer);将时间戳直接转换为本地时区的可读字符串(格式:Wed Sep 28 09:30:00 2024\n),无需手动处理 struct tm快速打印时间戳,无需自定义格式time_t t = file_stat.st_mtime;
printf("Modify Time: %s", ctime(&t));
Modify Time: Sat Sep 28 09:30:00 2024
time_t mktime(struct tm *tm);将 struct tm 结构体(本地时区)反向转换为 time_t 时间戳,支持自定义时间的时间戳计算计算特定时间(如“2024-10-01 00:00:00”)的时间戳,用于时间比较struct tm t = {0};
t.tm_year = 2024-1900;
t.tm_mon = 10-1;
t.tm_mday = 1;
time_t timestamp = mktime(&t);
printf("Timestamp: %ld", (long)timestamp);
Timestamp: 1727750400
char *strptime(const char *s, const char *format, struct tm *tm);将自定义格式的时间字符串(如“2024-09-28 09:30:00”)解析为 struct tm 结构体,与 strftime 功能相反解析用户输入的时间字符串,转换为时间戳进行计算char time_str[] = "2024-09-28 09:30:00";
struct tm t;
strptime(time_str, "%Y-%m-%d %H:%M:%S", &t);
time_t ts = mktime(&t);
printf("Timestamp: %ld", (long)ts);
Timestamp: 1727499000
struct tm *gmtime(const time_t *timer);将时间戳转换为 UTC 时区的 struct tm 结构体,与 localtime 唯一区别是时区需要统一 UTC 时间的场景(如日志同步、跨时区数据对比)struct tm *utc_tm = gmtime(&t);
strftime(buf, 32, "%Y-%m-%d %H:%M:%S UTC", utc_tm);
printf("UTC Time: %s", buf);
UTC Time: 2024-09-28 01:30:00 UTC
实战:计算文件修改时间与当前时间的差值

结合 time(获取当前时间戳)、mktimedifftime(计算时间戳差值)函数,可计算文件修改时间距当前的时间差:

#include <time.h>
#include <stdio.h>void CalcTimeDiff(time_t file_mtime)
{time_t now = time(NULL);double diff_sec = difftime(now, file_mtime);int days = diff_sec / (24 * 3600);int hours = (diff_sec % (24 * 3600)) / 3600;int mins = (diff_sec % 3600) / 60;int secs = diff_sec % 60;printf("文件最后修改时间距现在:%d天 %d时 %d分 %d秒\n", days, hours, mins, secs);
}
struct stat file_stat;
stat("filename.txt", &file_stat);
CalcTimeDiff(file_stat.st_mtime);

五、常见问题与解决方法

在获取文件其他属性和处理时间戳的过程中,常因函数使用不当、权限不足等导致问题。以下是高频问题及对应的解决方法:

常见问题问题现象原因分析解决方法
getpwuid/getgrgid 返回 NULL用户名/组名显示为“unknown”,但 UID/GID 实际存在(如 1000)1. 系统 /etc/passwd 或 /etc/group 文件损坏或权限不足(无法读取);
2. 程序运行在 chroot 环境中,未挂载 /etc 目录,导致无法读取用户/组配置
1. 检查 /etc/passwd 权限:ls -l /etc/passwd,确保有读权限(如 644);
2. chroot 环境:挂载 /etc/passwd 和 /etc/group 到 chroot 目录;
3. 降级处理:若无法修复,直接输出 UID/GID(如“UID=1000”)
localtime 转换时间错误(年份为 1970)时间显示为“1970-01-01 08:00:00”,与实际时间不符1. 时间戳为 0 或无效值(如负数),localtime 无法正确转换;
2. 文件的 st_mtime 未初始化(如 stat 函数调用失败后未检查,直接使用 file_stat.st_mtime
1. 检查时间戳有效性:确保 st_mtime 为正数(正常时间戳自 1970 年起,最小值为 0);
2. 强制检查 stat/lstat 返回值,失败时不进行时间转换
线程安全问题导致时间错乱多线程程序中,不同线程输出的文件时间相互覆盖(如线程 A 的时间显示为线程 B 的时间)localtime 返回静态全局变量的指针,多线程并发访问时存在数据竞争1. 替换为线程安全函数 localtime_r(推荐);
2. 若不支持 localtime_r,使用互斥锁(pthread_mutex_lock/unlock)保护 localtime 调用
符号链接的属性与目标文件不一致获取符号链接的 st_uid/st_size 时,得到的是目标文件的属性,而非链接本身使用了 stat 函数而非 lstat 函数——stat 会自动跟随符号链接,获取目标文件的属性始终使用 lstat 函数获取符号链接本身的属性;若需目标文件属性,再对符号链接的目标路径调用 stat

本文从 stat 结构的文件属性解析入手,详细讲解了文件链接数、所有者、时间戳等属性的获取方法,重点剖析了 localtime 函数的时间戳转换逻辑,并拓展了其他时间相关函数的使用。掌握这些知识,可实现对 UNIX 文件属性的全面管控,为文件管理工具开发、系统运维脚本编写提供基础。

建议结合实际需求多做实践(如编写文件属性统计脚本、时间差计算工具),加深对函数使用细节和属性特性的理解,避免因细节疏忽导致的错误。

http://www.dtcms.com/a/426574.html

相关文章:

  • UNIX下C语言编程与实践15-UNIX 文件系统三级结构:目录、i 节点、数据块的协同工作机制
  • 青浦做网站的公司网站开发语言html5 php
  • 【分布式中间件】RabbitMQ 功能详解与高可靠实现指南
  • SOME/IP-SD报文结构和交互详解
  • 给贾维斯加“手势控制”:从原理到落地,打造多模态交互的本地智能助
  • 电商数据分析优化清理大师
  • 论文阅读:《Self-Supervised Continual Graph Learning in Adaptive Riemannian Spaces》
  • Qt事件处理全解析
  • 深入理解 LLM 分词器:BPE、WordPiece 与 Unigram
  • 【大模型评估】大模型评估的五类数据
  • 3-2 Windows 安全设置
  • 网站建设平台 汉龙举报个人备案网站做经营性
  • 做技术网站赚钱比较好用的微信社群管理软件
  • DCT与DST变换原理及其在音视频编码中的应用解析
  • 高端网络建站松岗做网站哪家便宜
  • 大连网站设计报价游戏大全免费版入口
  • 长沙人才招聘网站硅谷主角刚开始做的是软件还是网站
  • 网站正能量做网站 人员
  • 做刷票的网站阳山做网站
  • 可以做超链接或锚文本的网站有哪些西安品牌策划公司排名
  • 抽奖网站怎么制作手机端网站的建设
  • 黄岛网站建设多少钱wordpress 硬件要求
  • 网站建设开票名称怎么写做网站宣传图的网站
  • 花店网站建设课程设计论文城市生活服务app下载
  • 从哪方面建设网站开通网站必须做域名空间
  • 涡阳在北京做网站的名人如何与老板谈网站建设
  • icp备案网站建设方案书wordpress会员阅读权限
  • 可以个人做单的猎头网站你买域名我送网站
  • 专业做家居的网站有哪些做网站要注意哪些问题
  • app使用什么做的网站吗wordpress英文版改中文