一、du命令解析
Summarize disk usage of the set of FILEs, recursively for directories.
du 命令用于输出文件所占用的磁盘空间
默认情况下,它会输出当前目录下(包括该目录的所有子目录下)的所有文件的大小总和,以 1024B 为单位
也可指定路径。若指定的路径为目录, 则输出该目录下所有文件大小的总和;若指定的路径为文件,则输出该文件大小。均以 1024B 为单位
二、类 du 命令实现
我们希望实现一个命令,该命令能够按照如下使用方式使用,统计 path 所占的磁盘空间(以1024B为单位)
mydu path
2.1 如果 path 为普通文件
先考虑实现输出普通文件大小的功能
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
static int64_t mydu(const char *path) {
struct stat statbuf;
if (lstat(path, &statbuf) < 0) {
perror("lstat()");
exit(1);
}
if (!S_ISDIR(statbuf.st_mode)) // 如果为普通文件
return statbuf.st_blocks / 2; // 为什么要除以2?
// 因为stat结构体中的st_blocks成员统计的是文件占了多少个大小为512B的块
// 而du统计的单位为1024B,因此需要除以2
}
int main(int argc, char * argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
exit(1);
}
printf("%ld\n", mydu(argv[1]));
exit(0);
}
2.2 如果 path 为目录
再考虑实现输出目录下所有文件大小之和的功能
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <glob.h>
#include <string.h>
#define PATHSIZE 1024
static int path_noloop(const char *path) { // 避免无限递归
char * pos = strrchr(path, '/');
if (pos == NULL)
exit(1);
if (strcmp(pos + 1, ".") == 0 || strcmp(pos+1, "..") == 0)
return 0;
return 1;
}
static int64_t mydu(const char *path) {
struct stat statbuf;
if (lstat(path, &statbuf) < 0) {
perror("lstat()");
exit(1);
}
if (!S_ISDIR(statbuf.st_mode))
return statbuf.st_blocks; // 当path为普通文件,不用后续递归了
//
// 下面情况考虑path为目录
//
char nextpath[PATHSIZE];
glob_t globbuf;
strncpy(nextpath, path, PATHSIZE);
strncat(nextpath, "/*", PATHSIZE); // 将path名拓展为"/dir/*"
glob(nextpath, 0, NULL, &globbuf); // 解析该path目录下的所有非隐藏名字
strncpy(nextpath, path, PATHSIZE);
strncat(nextpath, "/.*", PATHSIZE); // 将path名拓展为"/dir/.*"
glob(nextpath, GLOB_APPEND, NULL, &globbuf); // 解析该path目录下的所有隐藏名字,并添加到已解析的名字集
int64_t sum = 0;
for (int i = 0; i < globbuf.gl_pathc; ++i) {
if (path_noloop(globbuf.gl_pathv[i]))
sum += mydu(globbuf.gl_pathv[i]); // 递归,获取某个名字下的文件大小可以通过该函数本身实现
}
globfree(&globbuf);
return sum;
}
int main(int argc, char * argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
exit(1);
}
printf("%ld\n", mydu(argv[1])/2); // 打印的时候才除以2,避免递归过程中除多了
exit(0);
}
对比验证,针对目录统计出来的结果与命令 du 相同
tail -1 指的仅输出最后一行
补充
- 1、程序中 path_noloop 是干什么用的?
先想想我们处理 path 为目录时的递归思路:
解析某一个目录下的名字可以通过调用递归函数本身实现,用分解问题的思想遍历树,看似没啥问题
但是有一点需要注意:某个目录下的名字包含其自身和上一级菜单!
也就是如果我们不注意这一点,遍历树的过程就会像下面这样:
所以,需要通过下面的函数,判断 path 是不是以 "." 或者 ".." 结尾的(即是否指向路径所表示的目录本身或上一级),如果是,则不从这条路进入递归
static int path_noloop(const char *path) { // 避免无限递归
char * pos = strrchr(path, '/');
if (pos == NULL)
exit(1);
if (strcmp(pos + 1, ".") == 0 || strcmp(pos+1, "..") == 0)
return 0;
return 1;
}
- 2、代码有办法优化吗
有办法。因为递归调用需要频繁利用栈空间,而进程允许的栈空间大小是有上限的(可通过命令 ulimit -a 查看)。我们可以将某些栈空间的数据放在全局区(静态区), 节约栈空间
原则:如果一个变量的使用仅在递归点之前,则该变量可以放在静态区存放
优化代码如下
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <glob.h>
#include <string.h>
#define PATHSIZE 1024
static int path_noloop(const char *path) { // 避免无限递归
char * pos = strrchr(path, '/');
if (pos == NULL)
exit(1);
if (strcmp(pos + 1, ".") == 0 || strcmp(pos+1, "..") == 0)
return 0;
return 1;
}
static int64_t mydu(const char *path) {
static struct stat statbuf;
if (lstat(path, &statbuf) < 0) {
perror("lstat()");
exit(1);
}
if (!S_ISDIR(statbuf.st_mode))
return statbuf.st_blocks; // 当path为普通文件,不用后续递归了
//
// 下面情况考虑path为目录
//
static char nextpath[PATHSIZE];
glob_t globbuf;
strncpy(nextpath, path, PATHSIZE);
strncat(nextpath, "/*", PATHSIZE); // 将path名拓展为"/dir/*"
glob(nextpath, 0, NULL, &globbuf); // 解析该path目录下的所有非隐藏名字
strncpy(nextpath, path, PATHSIZE);
strncat(nextpath, "/.*", PATHSIZE); // 将path名拓展为"/dir/.*"
glob(nextpath, GLOB_APPEND, NULL, &globbuf); // 解析该path目录下的所有隐藏名字,并添加到已解析的名字集
int64_t sum = 0;
for (int i = 0; i < globbuf.gl_pathc; ++i) {
if (path_noloop(globbuf.gl_pathv[i]))
sum += mydu(globbuf.gl_pathv[i]); // 递归,获取某个名字下的文件大小可以通过该函数本身实现
}
globfree(&globbuf);
return sum;
}
int main(int argc, char * argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
exit(1);
}
printf("%ld\n", mydu(argv[1])/2); // 打印的时候才除以2,避免递归过程中除多了
exit(0);
}
哒咩哒咩哒咩哒咩哒咩哒咩~~~~