目标
编程实现程序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