【APUE】文件系统 — 类 du 命令功能实现

一、du命令解析

Summarize disk usage of the set of FILEs, recursively for directories.  

du 命令用于输出文件所占用的磁盘空间

默认情况下,它会输出当前目录下(包括该目录的所有子目录下)的所有文件的大小总和,以 1024B 为单位

也可指定路径。若指定的路径为目录, 则输出该目录下所有文件大小的总和;若指定的路径为文件,则输出该文件大小。均以 1024B 为单位

二、类 du 命令实现

我们希望实现一个命令,该命令能够按照如下使用方式使用,统计 path 所占的磁盘空间(以1024B为单位)

mydu path

2.1 如果 path 为普通文件

先考虑实现输出普通文件大小的功能

#include 
#include 
#include 
#include 
#include 


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 \n", argv[0]);
                exit(1);
        }

        printf("%ld\n", mydu(argv[1]));

        exit(0);
}

【APUE】文件系统 — 类 du 命令功能实现_第1张图片

2.2 如果 path 为目录 

再考虑实现输出目录下所有文件大小之和的功能

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 \n", argv[0]);
                exit(1);
        }

        printf("%ld\n", mydu(argv[1])/2);    // 打印的时候才除以2,避免递归过程中除多了

        exit(0);
}

【APUE】文件系统 — 类 du 命令功能实现_第2张图片

对比验证,针对目录统计出来的结果与命令 du 相同

tail -1 指的仅输出最后一行


补充 

  • 1、程序中 path_noloop 是干什么用的?

先想想我们处理 path 为目录时的递归思路:

【APUE】文件系统 — 类 du 命令功能实现_第3张图片

解析某一个目录下的名字可以通过调用递归函数本身实现,用分解问题的思想遍历树,看似没啥问题

但是有一点需要注意:某个目录下的名字包含其自身和上一级菜单!

也就是如果我们不注意这一点,遍历树的过程就会像下面这样: 

【APUE】文件系统 — 类 du 命令功能实现_第4张图片

所以,需要通过下面的函数,判断 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 
#include 
#include 
#include 
#include 
#include 
#include 
#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 \n", argv[0]);
                exit(1);
        }

        printf("%ld\n", mydu(argv[1])/2);    // 打印的时候才除以2,避免递归过程中除多了

        exit(0);
}

 

哒咩哒咩哒咩哒咩哒咩哒咩~~~~

你可能感兴趣的:(UNIX环境高级编程,linux,运维,服务器)