使用C语言进行文件类型统计

CCBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

本章基于 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 中的文件信息。
  • 执行目录操作,如打开目录和读取目录项。
  • 创建一个递归统计不同文件类型的程序,包括普通文件、目录、块特殊文件、字符特殊文件、命名管道、符号链接和套接字。
  • 计算并显示目录中每种文件类型的百分比。

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL c(("`C`")) -.-> c/BasicsGroup(["`Basics`"]) c(("`C`")) -.-> c/ControlFlowGroup(["`Control Flow`"]) c(("`C`")) -.-> c/CompoundTypesGroup(["`Compound Types`"]) c(("`C`")) -.-> c/PointersandMemoryGroup(["`Pointers and Memory`"]) c(("`C`")) -.-> c/FunctionsGroup(["`Functions`"]) c(("`C`")) -.-> c/UserInteractionGroup(["`User Interaction`"]) c/BasicsGroup -.-> c/operators("`Operators`") c/ControlFlowGroup -.-> c/switch("`Switch`") c/CompoundTypesGroup -.-> c/strings("`Strings`") c/CompoundTypesGroup -.-> c/structures("`Structures`") c/PointersandMemoryGroup -.-> c/memory_address("`Memory Address`") c/FunctionsGroup -.-> c/function_declaration("`Function Declaration`") c/FunctionsGroup -.-> c/function_parameters("`Function Parameters`") c/FunctionsGroup -.-> c/recursion("`Recursion`") c/UserInteractionGroup -.-> c/output("`Output`") subgraph Lab Skills c/operators -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} c/switch -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} c/strings -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} c/structures -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} c/memory_address -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} c/function_declaration -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} c/function_parameters -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} c/recursion -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} c/output -.-> lab-298832{{"`使用C语言进行文件类型统计`"}} end

基础知识与创建项目文件

接下来,我们将介绍从构思到实现的步骤,主要应用以下 C 语言知识点:

  • stat 结构体和 lstat 函数、opendirreaddir 函数、dirent 结构体、递归、函数调用等。

在整个程序中,我们构建了诸如 mainmyftwdopathmyfuncpath_alloc 等函数。

  • myfunc 函数主要遍历并统计符合条件的文件类型。
  • dopath 函数主要递归获取路径并确定它们是目录还是文件。
  • myftw 函数从 path_alloc 获取用于存储完整路径的内存空间的起始地址和大小开始。
  • path_alloc 函数主要为路径(完整路径)分配内存空间。

~/project 目录中创建一个名为 file_type.c 的新文件,并在你喜欢的代码编辑器中打开它。

cd ~/project
touch file_type.c
✨ 查看解决方案并练习

设计 main 函数

主函数的主要功能是首先接收命令行参数并检查其有效性。然后调用 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工具。

您可能感兴趣的其他 C 教程