Linux目录遍历实现,列出目录下文件,可使用部分参数

本文档详细介绍了如何使用C语言编写一个名为list.c的程序,该程序模仿ls命令的功能,处理各种参数选项,如列出当前目录、隐藏文件、递归列出子目录、指定文件大小范围和时间限制。程序涉及对命令行参数的解析、目录遍历、文件信息获取以及文件筛选等操作,同时包含了错误处理和帮助信息展示。
摘要由CSDN通过智能技术生成

目标

编程实现程序list.c,列表普通磁盘文件,包括文件名和文件大小。

内容

  • 对选项的处理,自行编程逐个分析命令行参数。不考虑多选项挤在一个命令行参数内的情况。
  • 与ls命令类似,处理对象可以有0到多个
    • 0个:列出当前目录下所有文件
    • 普通文件:列出文件
    • 目录:列出目录下所有文件
  • 实现自定义选项 -r, -a, -l, -h, -m 以及 –
    • -r 递归方式列出子目录(每项要含路径,类似find的-print输出风格,需要设计递归程序)
    • -a 列出文件名第一个字符为圆点的普通文件(默认情况下不列出文件名首字符为圆点的文件)
    • -l 后跟一整数,限定文件大小的最小值(字节)
    • -h 后跟一整数,限定文件大小的最大值(字节)
    • -m 后跟一整数n,限定文件的最近修改时间必须在n天内
    • – 显式地终止命令选项分析

参考资料

<sys/stat.h>头文件

文件数据结构如下

struct stat { 
     dev_t st_dev; // 文件所在设备ID 
     ino_t st_ino; // 结点(inode)编号  
     mode_t st_mode; // 保护模式 
     nlink_t st_nlink; // 硬链接个数  
     uid_t st_uid; // 所有者用户ID  
     gid_t st_gid; // 所有者组ID  
     dev_t st_rdev; // 设备ID(如果是特殊文件) 
     off_t st_size; // 总体尺寸,以字节为单位 
     blksize_t st_blksize; // 文件系统 I/O 块大小
     blkcnt_t st_blocks; // 已分配的 512B 块个数
     time_t st_atime; // 上次访问时间 
     time_t st_mtime; // 上次更新时间 
     time_t st_ctime; // 上次状态更改时间 
};

主要函数有

int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fields, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);

其中stat函数的作用是获取路径名 path 对应的 inode 中的属性

struct stat st;	//存储 inode 属性信息
ret = stat(path, &st); //成功返回0,失败返回-1

还有如下定义:

#define S_IFMT   0170000
#define S_IFSOCK 0140000
#define S_IFLNK  0120000
#define S_IFREG  0100000
#define S_IFBLK  0060000
#define S_IFDIR  0040000
#define S_IFCHR  0020000
#define S_IFIFO  0010000
#define S_ISUID  0004000
#define S_ISGID  0002000
#define S_ISVTX  0001000
 
#define S_ISLNK(m)      (((m) & S_IFMT) == S_IFLNK)
#define S_ISREG(m)      (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m)      (((m) & S_IFMT) == S_IFDIR)
#define S_ISCHR(m)      (((m) & S_IFMT) == S_IFCHR)
#define S_ISBLK(m)      (((m) & S_IFMT) == S_IFBLK)
#define S_ISFIFO(m)     (((m) & S_IFMT) == S_IFIFO)
#define S_ISSOCK(m)     (((m) & S_IFMT) == S_IFSOCK)

首先S_IFMT是一个掩码,它的值是0170000(注意这里用的是八进制), 可以用来过滤出前四位表示的文件类型。

其后的连续七个分别对应套接口文件、符号链接文件、普通文件、块设备、目录、字符设备、管道,它们分别对应一个不同的值。

要判断一个文件是不是目录,首先通过掩码S_IFMT把其他无关的部分置0,再与表示目录的数值比较,从而判断这是否是一个目录,下面的代码

if ((info.st_mode & S_IFMT) == S_IFDIR)
    printf("this is a directory");

为了简便操作,<sys/stat.h>中提供了宏来代替上述代码,所以如果需要判断文件是不是目录就可以这样:

if (S_ISDIR(info.st_mode))
    printf("this is a directory");

实现步骤

1、实现核心功能

首先实现核心功能:遍历列出普通磁盘文件,包括文件名和文件大小。

先定义两个功能函数,分别用于遍历目录和打印文件信息,在遍历目录的过程中打印文件信息:

void dirwalk(char *dir, void (*func)(char *));
void print_file_info(char *name);

下面来分别实现这两个函数,先实现dirwalk函数:

/*对目录中所有文件执行print_file_info操作*/
/*把print_file_info函数作为参数传进去*/
void dirwalk(char *dir, void (*func)(char *)) {
	char name[MAX_PATH];
	struct dirent *dp;
	DIR *dfd;
	
	if ((dfd = opendir(dir)) == NULL) { //打开文件失败
		fprintf(stderr, "dirwalk: can't open %s\n", dir);
		return;
	}
	
	while ((dp = readdir(dfd)) != NULL) { //读目录记录项
		if (strcmp(dp->d_name, ".") == 0 || strcmp(dp -> d_name, "..") == 0) {
			continue;  //跳过当前目录以及父目录
		}
		
        //目录过长
		if (strlen(dir) + strlen(dp -> d_name) + 2 > sizeof(name)) {
			fprintf(stderr, "dirwalk : name %s %s too long\n", dir, dp->d_name);
		}
		else {
			sprintf(name, "%s/%s", dir, dp->d_name); //添加子目录
			(*func)(name); //打印文件信息
		}
	}
	closedir(dfd);
}

然后实现print_file_info函数:

/*打印文件信息*/
void print_file_info(char *name) {
	struct stat stbuf;
	
	// 获取文件状态并储存在stbuf结构中
	if (stat(name, &stbuf) == -1) {
		fprintf(stderr, "file size: open %s failed\n", name);
		return;
	}
	
	//如果是目录遍历下一级目录
	if (S_ISDIR(stbuf.st_mode)) { 
		dirwalk(name, print_file_info);	 
	}
	else {//不是目录,打印文件size及name
		printf("%8ld    %s\n", stbuf.st_size, name);
	}
}

到此就把核心功能实现了,接下来进一步完善功能。

2、添加命令行参数选项和错误响应

定义一个结构体记录参数选项,

struct stu { //0表示未使用,1表示使用
	int r;
	int a;
	int l;
	int h;
	int m;
	int min;
	int max;
	int day;
}Parameter = {0, 0, 0, 0, 0, -1, -1, -1};

定义一个新的功能函数get_parameter,用来获取参数选项,不考虑完整的错误分析,

/*分析参数,不具体考虑错误*/
void get_parameter(int argc, char *argv[]) {
	for (int i = 1; i < argc; i++) {	//逐个分析命令行参数
		if (strcmp(argv[i], "-r") == 0) { // -r
			Parameter.r = 1;
		}
		else if (strcmp(argv[i], "-a") == 0) { // -a
			Parameter.a = 1;
		}
		else if (strcmp(argv[i], "-l") == 0) { // -l min
			Parameter.l = 1;
			i++;
			Parameter.min = atoi(argv[i]);
		}
		else if (strcmp(argv[i], "-h") == 0) { // -l max
			Parameter.h = 1;
			i++;
			Parameter.max = atoi(argv[i]);
		}
		else if (strcmp(argv[i], "-m") == 0) { // -m day
			Parameter.m = 1;
			i++;
			Parameter.day = atoi(argv[i]);
		}
		else if (strcmp(argv[i], "--") == 0) { // --
			i++;
			if (i == argc) strcpy(path, "."); 
			else if (i = argc - 1) strcpy(path, argv[i]);
			else error();
			break;
		}
		else if (i = argc - 1) { //不是功能参数,判断是不是最后一项(路径)
			strcpy(path, argv[i]);
		}
		else {
			error();
		}
	}
}

其中error函数为错误响应函数,打印参数提示信息:

void error() {
	printf("List information about the FILEs (the current directory by default)\n\n");
	printf("  -?           Display this help and exit\n");
	printf("  -a           Do not hide entries starting with .\n");
	printf("  -r           List subdirectories recursively\n");
	printf("  -l <bytes>   Minimum of file size\n");
	printf("  -h <bytes>   Maximum of file size\n");
	printf("  -m <days>    Limit file last modified time\n\n");
	exit(-1);
}

接下来修改dirwalk函数和print_file_info函数,添加参数响应功能

dirwalk函数中打印文件的分支修改如下,添加对 -a 的处理:

else {
    if (dp->d_name[0] == '.' && Parameter.a == 0) //以.开头的文件 
        continue;
    sprintf(name, "%s/%s", dir, dp->d_name);
    (*func)(name);
}

然后在print_file_info函数中添加对 -r, -l, -h, -m 的处理:

//如果是目录
if (S_ISDIR(stbuf.st_mode)) {
    if (strcmp(name, path) != 0) //不显示目标目录的信息
        printf("%8ld    %s/\n", stbuf.st_size, name);
    if (Parameter.r == 1 || strcmp(name, path) == 0) { //如果有 -r 遍历下一级目录
        dirwalk(name, print_file_info);
    }
}
else {//不是目录,根据-l -h -m打印文件size及name
    int flag = 1; //判断是否要输出 
    if (Parameter.l == 1 && stbuf.st_size < Parameter.min) flag = 0;
    if (Parameter.h == 1 && stbuf.st_size > Parameter.max) flag = 0;
    if (Parameter.m == 1) {
        struct timeval nowTime;
        gettimeofday(&nowTime, NULL);
        if (nowTime.tv_sec - stbuf.st_mtim.tv_sec > (time_t)(Parameter.day*86400))
            flag = 0;
    }
    if (flag == 1) 
        printf("%8ld    %s\n", stbuf.st_size, name);
}

这样代码就完成啦,完整代码见附录

3、测试

使用命令vi list.c编写代码并保存后,利用命令gcc list.c -o list编译成可执行文件,开始测试

  • 先执行ls命令,再执行简单的./list命令,对比正确

在这里插入图片描述

  • 执行ls -a命令,再执行./list -a命令,对比正确

在这里插入图片描述

  • 执行./list -r命令,结果正确

在这里插入图片描述

  • 执行./list -r -l 4000 -h 20000 temp命令,结果正确

在这里插入图片描述

  • 执行./list -- -a命令,结果显示不存在该文件,正确

在这里插入图片描述

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>

#define MAX_PATH 512  //最大文件长度定义为512

struct stu { //0表示未使用,1表示使用
	int r;
	int a;
	int l;
	int h;
	int m;
	int min;
	int max;
	int day;
}Parameter = {0, 0, 0, 0, 0, -1, -1, -1};

char path[MAX_PATH] = ".";

void dirwalk(char *dir, void (*func)(char *));
void print_file_info(char *name);
void get_parameter(int argc, char *argv[]);
void error();

/*对目录中所有文件执行print_file_info操作*/
/*把print_file_info函数作为参数传进去*/
void dirwalk(char *dir, void (*func)(char *)) {
	char name[MAX_PATH];
	struct dirent *dp;
	DIR *dfd;
	
	if ((dfd = opendir(dir)) == NULL) {
		fprintf(stderr, "dirwalk: can't open %s\n", dir);
		return;
	}
	
	while ((dp = readdir(dfd)) != NULL) { //读目录记录项
		if (strcmp(dp->d_name, ".") == 0 || strcmp(dp -> d_name, "..") == 0) {
			continue;  //跳过当前目录以及父目录
		}
		
		if (strlen(dir) + strlen(dp -> d_name) + 2 > sizeof(name)) {
			fprintf(stderr, "dirwalk : name %s %s too long\n", dir, dp->d_name);
		}
		else {
			if (dp->d_name[0] == '.' && Parameter.a == 0) //以.开头的文件 
				continue;
			sprintf(name, "%s/%s", dir, dp->d_name);
			(*func)(name);
		}
	}
	closedir(dfd);
}

/*打印文件信息*/
void print_file_info(char *name) {
	struct stat stbuf;
	
	// 获取文件状态并储存在stbuf结构中
	if (stat(name, &stbuf) == -1) {
		fprintf(stderr, "file size: open %s failed\n", name);
		return;
	}
	
	//如果是目录
	if (S_ISDIR(stbuf.st_mode)) {
		if (strcmp(name, path) != 0)
			printf("%8ld    %s/\n", stbuf.st_size, name);
		if (Parameter.r == 1 || strcmp(name, path) == 0) { //如果有 -r 遍历下一级目录
			dirwalk(name, print_file_info);
		}
	}
	else {//不是目录,根据-l -h -m打印文件size及name
		int flag = 1; //判断是否要输出 
		if (Parameter.l == 1 && stbuf.st_size < Parameter.min) flag = 0;
		if (Parameter.h == 1 && stbuf.st_size > Parameter.max) flag = 0;
		if (Parameter.m == 1) {
			struct timeval nowTime;
			gettimeofday(&nowTime, NULL);
			if (nowTime.tv_sec - stbuf.st_mtim.tv_sec > (time_t)(Parameter.day*86400))
				flag = 0;
		}
		if (flag == 1) 
			printf("%8ld    %s\n", stbuf.st_size, name);
	}
}

/*分析参数,不具体考虑错误*/
void get_parameter(int argc, char *argv[]) {
	for (int i = 1; i < argc; i++) {
		if (strcmp(argv[i], "-r") == 0) {
			Parameter.r = 1;
		}
		else if (strcmp(argv[i], "-a") == 0) {
			Parameter.a = 1;
		}
		else if (strcmp(argv[i], "-l") == 0) {
			Parameter.l = 1;
			i++;
			Parameter.min = atoi(argv[i]);
		}
		else if (strcmp(argv[i], "-h") == 0) {
			Parameter.h = 1;
			i++;
			Parameter.max = atoi(argv[i]);
		}
		else if (strcmp(argv[i], "-m") == 0) {
			Parameter.m = 1;
			i++;
			Parameter.day = atoi(argv[i]);
		}
		else if (strcmp(argv[i], "--") == 0) {
			i++;
			if (i == argc) strcpy(path, "."); 
			else if (i = argc - 1) strcpy(path, argv[i]);
			else error();
			break;
		}
		else if (i = argc - 1) {
			strcpy(path, argv[i]);
		}
		else {
			error();
		}
	}
}

void error() {
	printf("List information about the FILEs (the current directory by default)\n\n");
	printf("  -?           Display this help and exit\n");
	printf("  -a           Do not hide entries starting with .\n");
	printf("  -r           List subdirectories recursively\n");
	printf("  -l <bytes>   Minimum of file size\n");
	printf("  -h <bytes>   Maximum of file size\n");
	printf("  -m <days>    Limit file last modified time\n\n");
	exit(-1);
}

int main(int argc, char *argv[]) {
	get_parameter(argc, argv);
	printf("file size    file name\n");
	print_file_info(path);
	return 0;
}

参考来源:
https://blog.csdn.net/astrotycoon/article/details/8679676
https://blog.csdn.net/y396397735/article/details/50640919

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值