打磨成顺手的工具

ccleaner现在还停留在硬编码清理阶段,从实用性及灵活性角度出发,我们需要考虑以下功能:

  1. 用户可以指定清理目录,未指定时则清理程序当前所在目录。
  2. 用户可以指定要清理的文件类型,指定"*.*"可以适配所有文件。作为量身打造的工具,要能方便的清理VS系列的C/C++项目;如果未指定,则默认以VS2022清理文件类型为基准。
  3. 用户可以指定是否删除空目录,未指定时则默认删除。

对于控制台应用程序,通过命令行参数定制程序功能是不二之选。ccleaner的三个命令项都是可选的,根据规则下面的命令都是合法的。

// 清理当前文件夹下,以VS2022为基准的所有文件,且删除空目录。
ccleaner

// 同上
ccleaner . vs2022

// 清理当前目录下的所有文件
ccleaner . *.*

// 清理指定目录下,指定类型的文件,但不删除空目录。
ccleaner "c:\program files\tuts" ".tmp .tpl .log" 0

实现

ccleaner的一种使用场景是将其拷贝到某个目录下,然后双击执行自动清理。此时命令行只有一个参数,就是ccleaner的路径,我们可以通过解析此路径取得当前目录,也可以通过getcwd()函数获取。除此之外,程序会获得多于1个的参数,如代码所示,我们假设命令按正确的顺序与格式给出,然而一一解析。

int main(int argc, char *argv[])
{
    char cpath[BUFSIZ];
    const char *suffix = NULL;
    int rmdir = 1;

    const char *vs2015_suffix = ".obj .ilk .ipdb .tmp .log .pch .exp .idb .rep .xdc .pdb .bsc";
    const char *vs2019_suffix = ".pch .pdb .tmp .obj .log .exp .ilk .txt .idb .exe .tlog .recipe .lastbuildstate";
    const char *vs2022_suffix = ".tlog .recipe .ilk .txt .obj .pdb .idb .log .vsidx .lock .db .suo .db-shm .db-wal .opendb .ipch";

    // 直接运行程序时,使用默认选项
    if (argc == 1)
    {
        // 取得当前文件夹
        _getcwd(cpath, BUFSIZ);
        suffix = vs2022_suffix;
        rmdir = 1;
    }
    else
    {
        // 解析第1个选项参数
        if (argc > 1 && argv[1] != NULL)
        {
            // 显示使用指南
            if (_stricmp(argv[1], "-h") == 0)
            {
                print_usage();
                return 0;
            }
            // 取得要清理的文件夹路径
            else
            {
                if (strcmp(argv[1], ".") == 0)
                    _getcwd(cpath, BUFSIZ);
                else
                    strcpy_s(cpath, BUFSIZ, argv[1]);
            }
        }

        // 取得文件类型列表
        if (argc > 2 && argv[2] != NULL)
        {
            if (stricmp(argv[2], "VS2015") == 0)
                suffix = vs2015_suffix;
            else if (stricmp(argv[2], "VS2019") == 0)
                suffix = vs2019_suffix;
            else
                suffix = argv[2];
        }
        else
        {
            suffix = vs2022_suffix;
        }

        // 取得文件删除配置选项
        if (argc > 3 && argv[3] != NULL)
        {
            rmdir = atoi(argv[3]);
            if (rmdir != 0)
                rmdir = 1;
        }
    }

    //...
}

作为特例,程序支持\"-h\"选项,当执行命令ccleaner -h时,程序将会打印出使用指南,这是通过print_usage()实现的。

void print_usage()
{
    printf("usage: ccleaner [path] [suffix] [rmdir]\n\n");
    printf("三个参数均为可选参数,被指定参数前的可选参数不可省略。\n\n");

    printf("path     指定要清理的文件夹\n");
    printf("         未指定 或 指定为\".\"时,当前文件夹将为清理目标。\n\n");

    printf("suffix   空格分隔的文件类型列表,如: \".tmp .obj .log\"\n");
    printf("         指定为 vs2015 时,配置为VS2015 C++ 项目可清理的文件类型列表。\n");
    printf("         指定为 vs2019 时,配置为VS2019 C++ 项目可清理的文件类型列表。\n");
    printf("         指定为 vs2022 时,配置为VS2022 C++ 项目可清理的文件类型列表。\n");
    printf("         未指定时将以 vs2022 的配置执行。\n\n");

    printf("rmdir:   是否删除空文件夹\n");
    printf("         1:删除 0:忽略\n");
    printf("         未指定时,默认将删除空目录。\n\n");
}

抽取为函数

命令解析的部分可以抽离到parse_cmdline()函数中,它接收两个输入三个输出参数。由于\"-h\"参数的存在,我们选择在这种情况下,主函数直接退出,因此选择返回int,代表不同的解析结果。另一个变动是关于suffix的存储,前面我们是使用const char *suffix指向目标,这里使用char suffix[BUFSIZ]存储副本。

/**
 * 解析命令行参数
 * argc: 参数个数
 * argv:参数列表
 * path: 解析的清理路径,大小BUFSIZ
 * suffix: 解析的待清理文件类型,大小BUFSIZE
 * rmdir:解析清理文件空文件夹标记
 * ret: 返回0表示解析成功;1表示需要显示使用指南
 */
int parse_cmdline(int argc, char *argv[], char *path, char *suffix, int *rmdir)
{
    const char *vs2015_suffix = ".obj .tmp";
    const char *vs2019_suffix = ".pch .pdb .tmp .obj .log .exp .ilk .txt .idb .exe .tlog .recipe .lastbuildstate";
    const char *vs2022_suffix = ".tlog .recipe .ilk .txt .obj .pdb .idb .log .vsidx .lock .db .suo .db-shm .db-wal .opendb .ipch";

    // 直接运行程序时,使用默认选项
    if (argc == 1)
    {
        // 取得当前文件夹
        _getcwd(path, BUFSIZ);
        strcpy_s(suffix, BUFSIZ, vs2022_suffix);
        *rmdir = 1;
    }
    else
    {
        // 解析第1个选项参数
        if (argc > 1 && argv[1] != NULL)
        {
            // 显示使用指南
            if (_stricmp(argv[1], "-h") == 0)
            {
                return 1;
            }
            // 取得要清理的文件夹路径
            else
            {
                if (strcmp(argv[1], ".") == 0)
                    _getcwd(path, BUFSIZ);
                else
                    strcpy_s(path, BUFSIZ, argv[1]);
            }
        }

        // 取得文件类型列表
        if (argc > 2 && argv[2] != NULL)
        {
            if (_stricmp(argv[2], "VS2015") == 0)
                strcpy_s(suffix, BUFSIZ, vs2015_suffix);
            else if (_stricmp(argv[2], "VS2019") == 0)
                strcpy_s(suffix, BUFSIZ, vs2019_suffix);
            else if (_stricmp(argv[2], "VS2022") == 0)
                strcpy_s(suffix, BUFSIZ, vs2022_suffix);
            else
                strcpy_s(suffix, BUFSIZ, argv[2]);
        }
        else
        {
            strcpy_s(suffix, BUFSIZ, vs2022_suffix);
        }

        // 取得文件删除配置选项
        if (argc > 3 && argv[3] != NULL)
        {
            *rmdir = atoi(argv[3]);
            if (*rmdir != 0)
                *rmdir = 1;
        }
    }

    return 0;
}

后续

最后一点要考虑的是,在窗口环境下,当用户双击执行清理时,伴随程序的退出控制台窗口一闪而过,使得我们精心准备的报告信息没有机会展示,这可以通过加入"Press any key to continue..."功能实现。

int main(int argc, char *argv[])
{
    //...

    // 直接双击运行程序时给予查看结果的机会
    if (argc == 1)
    {
        printf("Press any key to continue...\n");
        _getch();
    }

    return 0;
}

经过这样的改造,ccleaner可以算是一个有趣的工具。当然,它还没有各种错误检测,如输入路径的有效性,输入后缀是否合法等。不过,这并不能阻拦我使用它的决心。

glimix.com

最后要提醒的是,ccleaner会直接将文件删除,而不是放到回收站,在执行它之前,确定知道自己在做什么!

陕ICP备2025078817号-1 陕公网安备61011202001108号