改进:打磨成顺手的工具
ccleaner现在还停留在硬编码清理阶段,从实用性及灵活性角度出发,我们需要考虑以下功能:
- 用户可以指定清理目录,未指定时则清理程序当前所在目录。
- 用户可以指定要清理的文件类型,指定"*.*"可以适配所有文件。作为量身打造的工具,要能方便的清理VS系列的C/C++项目;如果未指定,则默认以VS2022清理文件类型为基准。
- 用户可以指定是否删除空目录,未指定时则默认删除。
对于控制台应用程序,通过命令行参数定制程序功能是不二之选。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可以算是一个有趣的工具。当然,它还没有各种错误检测,如输入路径的有效性,输入后缀是否合法等。不过,这并不能阻拦我使用它的决心。

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