介绍
本章基于 Linux 的文件和目录接口。本项目围绕文件系统的本质展开,使用 lstat 函数和目录操作来实现一个用于递归统计文件类型的程序。它提供了一种便捷的方式,能让你深入了解 Linux 文件系统中文件类型的构成。此外,本项目中开发的文件类型统计程序可用于实际学习和工作环境。
👀 预览
$./file_type.
普通文件 = 2, 66.67 %
目录 = 1, 33.33 %
块特殊文件 = 0, 0.00 %
字符特殊文件 = 0, 0.00 %
FIFO = 0, 0.00 %
符号链接 = 0, 0.00 %
套接字 = 0, 0.00 %
🎯 任务
在本项目中,你将学习:
- 如何使用 Linux 文件和目录接口,用 C 语言实现一个递归统计目录中文件类型的程序。
🏆 成果
完成本项目后,你将能够:
- 使用
lstat函数获取 Linux 中的文件信息。 - 执行目录操作,如打开目录和读取目录项。
- 创建一个递归统计不同文件类型的程序,包括普通文件、目录、块特殊文件、字符特殊文件、命名管道、符号链接和套接字。
- 计算并显示目录中每种文件类型的百分比。
基础知识与创建项目文件
接下来,我们将介绍从构思到实现的步骤,主要应用以下 C 语言知识点:
stat结构体和lstat函数、opendir、readdir函数、dirent结构体、递归、函数调用等。
在整个程序中,我们构建了诸如 main、myftw、dopath、myfunc 和 path_alloc 等函数。
myfunc函数主要遍历并统计符合条件的文件类型。dopath函数主要递归获取路径并确定它们是目录还是文件。myftw函数从path_alloc获取用于存储完整路径的内存空间的起始地址和大小开始。path_alloc函数主要为路径(完整路径)分配内存空间。
在 ~/project 目录中创建一个名为 file_type.c 的新文件,并在你喜欢的代码编辑器中打开它。
cd ~/project
touch file_type.c
设计主函数
主函数的主要功能是首先接收命令行参数并检查其有效性。然后调用 myftw 函数来计算各类文件的数量。最后计算文件类型的百分比并输出。
#include <dirent.h>
#include <limits.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define FTW_F 1 /* 非目录文件的标志 */
#define FTW_D 2 /* 目录文件的标志 */
#define FTW_DNR 3 /* 不可读目录文件的标志 */
#define FTW_NS 4 /* 无法通过 stat 访问的文件的标志 */
static char *fullpath;
static size_t pathlen;
/* 定义处理文件的函数 */
typedef int Myfunc(const char *, const struct stat *, int);
static Myfunc myfunc;
static int myftw(char *, Myfunc *);
static int dopath(Myfunc *);
char *path_alloc(size_t *size);
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;
int main(int argc, char *argv[])
{
int ret;
// 进行输入有效性检查
if (argc!= 2)
{
printf("无效的命令输入!\n");
exit(1);
}
/* 计算各类文件的数量 */
ret = myftw(argv[1], myfunc);
/* 计算文件总数 */
ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;
/* 避免除以 0 以提高程序稳定性 */
if (ntot == 0)
ntot = 1;
/* 打印各类文件的百分比 */
printf("普通文件 = %7ld, %5.2f %%\n", nreg,
nreg*100.0 / ntot);
printf("目录 = %7ld, %5.2f %%\n", ndir,
ndir*100.0 / ntot);
printf("块特殊文件 = %7ld, %5.2f %%\n", nblk,
nblk*100.0 / ntot);
printf("字符特殊文件 = %7ld, %5.2f %%\n", nchr,
nchr*100.0 / ntot);
printf("FIFO = %7ld, %5.2f %%\n", nfifo,
nfifo*100.0 / ntot);
printf("符号链接 = %7ld, %5.2f %%\n", nslink,
nslink*100.0 / ntot);
printf("套接字 = %7ld, %5.2f %%\n", nsock,
nsock*100.0 / ntot);
exit(ret);
}
*fullpath:用于存储文件的完整路径。pathlen:用于存储文件路径的长度。nreg:普通文件的数量。ndir:目录文件的数量。nblk:块特殊文件的数量。nchr:字符特殊文件的数量。nfifo:命名管道的数量。nslink:符号链接文件的数量。nsock:套接字文件的数量。ntot:文件总数。
设计 myftw 函数
该函数用于处理 pathname 并将其保存在全局字符数组中,然后调用 dopath。函数 path_alloc 用于分配空间,需要注意的是,fullpath 是一个全局变量,因此不同函数可以方便地使用它。然后,调用 dopath 函数进一步处理路径名(无论它是否为目录)。
static int myftw(char *pathname, Myfunc *func)
{
/* 为保存路径的字符串数组分配空间 */
fullpath = path_alloc(&pathlen);
/* 如果分配的空间不足以保存路径,则使用 realloc 重新分配 */
if (pathlen <= strlen(pathname)) {
pathlen = strlen(pathname) * 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL);
printf("realloc 失败!\n");
}
/* 将路径名参数保存在完整路径中。注意:fullpath 是全局变量
可被 dopath 调用 */
strcpy(fullpath, pathname);
/* 调用 dopath 函数 */
return(dopath(func));
}
/* 路径数组分配 */
char *path_alloc(size_t* size)
{
char *p = NULL;
if (!size)
return NULL;
p = malloc(256);
if (p)
*size = 256;
else
*size = 0;
return p;
}
设计“dopath”函数
int lstat(const char *path, struct stat *buf):当文件是符号链接时,lstat返回符号链接本身的信息,而stat返回链接指向的文件的信息。这里使用的一个数据结构是stat结构体。该结构体的定义如下:
struct stat {
dev_t st_dev;
ino_t st_ino;
mode_t st_mode;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
off_t st_size;
timestruc_t st_atim;
timestruc_t st_mtim;
timestruc_t st_ctim;
blksize_t st_blksize;
blkcnt_t st_blocks;
char st_fstype[_ST_FSTYPSZ];
};
DIR* opendir (const char * path )函数:打开一个目录,失败时返回空指针。struct dirent *readdir(DIR *dir)函数:readdir()返回目录流dir中的下一个目录项。返回值:成功时,readdir()返回指向下一个目录项的指针。到达目录流末尾时,readdir()返回NULL。
/* dopath 用于判断是否为目录,然后选择是直接进入 myfunc 函数进行计数,还是递归调用 dopath 函数。 */
static int dopath(Myfunc* func)
{
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret, n;
/* 调用 lstat 函数获取 pathname 的 stat 信息。如果失败,调用 func 函数并传递 FTW_NS */
if (lstat(fullpath, &statbuf) < 0)
return(func(fullpath, &statbuf, FTW_NS));
/* 检查文件 stat 结构体的 st_mode。如果不是目录,调用 func 函数并传递 FTW_F,然后由 myfunc 确定文件类型 */
if (S_ISDIR(statbuf.st_mode) == 0)
return(func(fullpath, &statbuf, FTW_F));
/* 最后一种情况是路径名表示一个目录。func 的正常返回值是 0,所以执行 func 后不会返回,并将继续递归调用 func */
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)
printf("realloc 失败!\n");
}
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) /* 然后使用新的路径名递归调用 dopath */
break;
}
fullpath[n-1] = 0;
/* 关闭目录 */
if (closedir(dp) < 0)
printf("无法关闭目录 %s", fullpath);
return(ret);
}
设计 myfunc 函数
myfunc 函数的主要目的是根据 stat 确定文件类型并进行计数。S_IFMT 是一个用于解释 st_mode 标志的掩码。
有一些宏定义用于帮助确定文件类型。这些是参数化宏,类似于函数调用:
S_ISBLK:测试是否为特殊块设备文件。S_ISCHR:测试是否为特殊字符设备文件。S_ISDIR:测试是否为目录。S_ISFIFO:测试是否为 FIFO 设备。S_ISREG:测试是否为普通文件。S_ISLNK:测试是否为符号链接。S_ISSOCK:测试是否为套接字。
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:
printf("for S_IFDIR for %s", pathname);
}
break;
/* 目录文件的处理 */
case FTW_D:
ndir++;
break;
/* 不可读目录的处理 */
case FTW_DNR:
printf("%s 目录不可读", pathname);
break;
case FTW_NS:
printf("%s 在 stat 中出错", pathname);
break;
default:
printf("类型 %d 未识别,路径名是 %s", type, pathname);
}
return(0);
}
编译并测试
在终端中输入以下命令进行编译和运行:
cd ~/project
gcc -o file_type file_type.c
./file_type.
labex:project/ $ ls
file_type file_type.c
labex:project/ $ gcc -o file_type file_type.c
labex:project/ $./file_type.
普通文件 = 2, 66.67 %
目录 = 1, 33.33 %
块特殊文件 = 0, 0.00 %
字符特殊文件 = 0, 0.00 %
FIFO = 0, 0.00 %
符号链接 = 0, 0.00 %
套接字 = 0, 0.00 %
结果表明,在当前目录中,普通文件占 66.67%,目录占 33.33%。
如果你需要统计系统目录中的权限,可以在执行命令前使用 sudo,即便如此,仍有一些文件的权限无法统计。
总结
通过这个项目的训练,你可以增进对 Linux 文件系统的理解。你将学习如何进行目录操作,并更深入地了解用于存储文件信息的stat结构。通过完成这个项目,你可以开发一个实用的 Linux 工具。



