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

目录

一、du命令解析

二、类 du 命令实现

2.1 如果 path 为普通文件

2.2 如果 path 为目录 

补充 


一、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);
}

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

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林沐华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值