一、需求描述
在Linux系统的某几个目录下有一些前缀和后缀相同的文件,编写程序将它们移动到同一个目录下。
例如,有三个源目录FileDir1、FileDir2和FileDir3,里面分别存放有文件File_1.txt、File_2.txt和File_3.txt。由于它们有相同的前缀(File_)和后缀(txt),所以要将这三个文件移动到同一个结果目录(假设为GatherDir)中。
二、算法设计
基于需求,可以采用如图1所示的程序流程:
图1 程序总体流程
三、特殊流程考虑
在编写程序的过程中,对于某些特殊流程的考虑如下:
1.如果扫描某个目录出错,则直接停止程序的运行,而不用继续扫描下一个目录。
2.对于某些空文件(即文件的大小为0),直接在源目录中将其删除,而不用移动到结果目录中。
3.为了随时能够处理放到源目录中的文件,程序每隔一段时间(如一分钟)扫描一次源目录。也就是说,如果不人为操作,程序启动之后会不停地运行。
四、程序代码
- /**********************************************************************
- * 版权所有 (C)2016, Zhou Zhaoxiong。
- *
- * 文件名称:FileGather.c
- * 文件标识:无
- * 内容摘要:将各个目录中前缀相同的文件集中到一个目录中
- * 其它说明:无
- * 当前版本:V1.0
- * 作 者:Zhou Zhaoxiong
- * 完成日期:20160513
- *
- **********************************************************************/
- #include <stdio.h>
- #include <string.h>
- #include <dirent.h>
- #include <ftw.h>
- #include <time.h>
- // 重定义数据类型
- typedef signed int INT32;
- typedef unsigned int UINT32;
- typedef unsigned char UINT8;
- // 全局变量定义
- UINT8 g_szGatherDir[256] = {0}; // 汇总文件的目录
- UINT8 g_szFilePrefix[20] = {0}; // 需要汇总的文件的前缀
- UINT8 g_szFileSuffix[20] = {0}; // 需要汇总的文件的后缀
- // 宏定义
- #define DIRNUM 3 // 需要扫描的目录数
- // 函数声明
- INT32 SelectFlies(struct dirent *pDir);
- void ScanDirAndGather(void);
- void Sleep(UINT32 iCountMs);
- /****************************************************************
- * 功能描述: 主函数
- * 输入参数: 无
- * 输出参数: 无
- * 返 回 值: 0-执行完成
- * 其他说明: 无
- * 修改日期 版本号 修改人 修改内容
- * -------------------------------------------------------------
- * 20160513 V1.0 Zhou Zhaoxiong 创建
- ****************************************************************/
- INT32 main(void)
- {
- // 获取汇总文件的目录
- snprintf(g_szGatherDir, sizeof(g_szGatherDir)-1, "%s/zhouzx/TestDir/GatherDir", getenv("HOME"));
- // 获取需要汇总的文件的前缀
- snprintf(g_szFilePrefix, sizeof(g_szFilePrefix)-1, "File_");
- // 获取需要汇总的文件的后缀
- snprintf(g_szFileSuffix, sizeof(g_szFileSuffix)-1, ".txt");
- // 调用函数执行文件的汇总
- while (1)
- {
- ScanDirAndGather();
- Sleep(60 * 1000); // 每一分钟执行一次文件的汇总
- }
- return 0;
- }
- /**********************************************************************
- * 功能描述:根据前缀和后缀选择文件
- * 输入参数:dir-目录
- * 输出参数:无
- * 返 回 值:0-失败 1-成功
- * 其它说明:无
- * 修改日期 版本号 修改人 修改内容
- * --------------------------------------------------------------------
- * 20160513 V1.0 ZhouZhaoxiong 创建
- ***********************************************************************/
- INT32 SelectFlies(struct dirent *pDir)
- {
- INT32 iPrefixLen = 0;
- INT32 iLoopFlag = 0;
- INT32 iSelectResult = 0;
- if (pDir == NULL)
- {
- printf("SelectFlies:input parameter is NULL!\n");
- return 0;
- }
- // 匹配文件前缀和后缀
- iPrefixLen = strlen(g_szFilePrefix); // 前缀为g_szFilePrefix
- iSelectResult = ((0 == strncmp(pDir->d_name, g_szFilePrefix, iPrefixLen))
- && ((strncmp(&pDir->d_name[strlen(pDir->d_name) - strlen(g_szFileSuffix)], g_szFileSuffix, strlen(g_szFileSuffix)) == 0)));
- if (iSelectResult == 1) // 找到了匹配前缀的文件
- {
- return 1;
- }
- else
- {
- return 0;
- }
- }
- /**********************************************************************
- * 功能描述:扫描目录并汇总前缀相同的文件
- * 输入参数:无
- * 输出参数:无
- * 返 回 值:无
- * 其它说明:无
- * 修改日期 版本号 修改人 修改内容
- * --------------------------------------------------------------------
- * 20160513 V1.0 ZhouZhaoxiong 创建
- ***********************************************************************/
- void ScanDirAndGather(void)
- {
- INT32 iScanDirRet = 0;
- UINT32 iDirIdx = 0;
- UINT32 iFileIdx = 0;
- UINT32 iFileCount = 0;
- UINT32 iScanedNoFileDirCount = 0;
- UINT32 iFileSize = 0;
- INT32 iRetVal = 0;
- UINT8 szFileDir[256] = {0};
- UINT8 szScanedFile[512] = {0};
- UINT8 szCmdBuf[256] = {0};
- FILE *fp = NULL;
- struct dirent **ppDirEnt = NULL;
- // 依次扫描各个目录, 并汇总文件
- for (iDirIdx = 1; iDirIdx <= DIRNUM; iDirIdx ++)
- {
- memset(szFileDir, 0x00, sizeof(szFileDir));
- snprintf(szFileDir, sizeof(szFileDir)-1, "%s/zhouzx/TestDir/FileDir%d", getenv("HOME"), iDirIdx);
- iScanDirRet = scandir(szFileDir, &ppDirEnt, SelectFlies, alphasort);
- if (iScanDirRet < 0) // 扫描目录出错
- {
- printf("ScanDirAndGather:exec scandir failed, path=%s\n", szFileDir);
- return;
- }
- else if (iScanDirRet == 0) // 目录下无文件
- {
- printf("ScanDirAndGather:no satisfied file in directory %s\n", szFileDir);
- iScanedNoFileDirCount ++;
- if (iScanedNoFileDirCount >= DIRNUM) // 表示所有目录下均无满足条件的文件
- {
- printf("ScanDirAndGather:scaned no satisfied files in all %d dirs\n", iScanedNoFileDirCount);
- return;
- }
- }
- else // 将满足条件的文件移动到汇总目录中
- {
- for (iFileIdx = 0; iFileIdx < iScanDirRet; iFileIdx ++)
- {
- // 先判断扫描到的文件是否为空文件, 是则直接删除, 不是才执行移动的操作
- memset(szScanedFile, 0x00, sizeof(szScanedFile));
- snprintf(szScanedFile, sizeof(szScanedFile) - 1, "%s/%s", szFileDir, ppDirEnt[iFileIdx]->d_name);
- fp = fopen(szScanedFile, "r");
- if (fp == NULL) // 打开文件失败, 直接返回
- {
- printf("ScanDirAndGather:open file %s failed, please check!\n", szScanedFile);
- return;
- }
- fseek(fp, 0, SEEK_END);
- iFileSize = ftell(fp);
- if (iFileSize == 0) // 该文件为空文件
- {
- printf("ScanDirAndGather:%s is an empty file, so delete it directly!\n", szScanedFile);
- memset(szCmdBuf, 0x00, sizeof(szCmdBuf));
- snprintf(szCmdBuf, sizeof(szCmdBuf) - 1, "rm %s", szScanedFile);
- system(szCmdBuf);
- }
- else
- {
- memset(szCmdBuf, 0x00, sizeof(szCmdBuf));
- snprintf(szCmdBuf, sizeof(szCmdBuf) - 1, "mv %s %s", szScanedFile, g_szGatherDir);
- system(szCmdBuf);
- printf("ScanDirAndGather:now, %s\n", szCmdBuf);
- iFileCount ++;
- }
- }
- }
- }
- printf("ScanDirAndGather:this time,totally moved %d file(s) to %s\n", iFileCount, g_szGatherDir);
- return;
- }
- /**********************************************************************
- * 功能描述: 程序休眠
- * 输入参数: iCountMs-休眠时间(单位:ms)
- * 输出参数: 无
- * 返 回 值: 无
- * 其它说明: 无
- * 修改日期 版本号 修改人 修改内容
- * ------------------------------------------------------------------
- * 20160513 V1.0 Zhou Zhaoxiong 创建
- ********************************************************************/
- void Sleep(UINT32 iCountMs)
- {
- struct timeval t_timeout = {0};
- if (iCountMs < 1000)
- {
- t_timeout.tv_sec = 0;
- t_timeout.tv_usec = iCountMs * 1000;
- }
- else
- {
- t_timeout.tv_sec = iCountMs / 1000;
- t_timeout.tv_usec = (iCountMs % 1000) * 1000;
- }
- select(0, NULL, NULL, NULL, &t_timeout); // 调用select函数阻塞程序
- }
五、程序测试
将编写好的程序“FileGather.c”上传到Linux机器,并使用“gcc -g -o FileGather FileGather.c”命令对该程序进行编译,生成“FileGather”文件。下面对程序进行详细的测试。
1.在启动程序之前,分别在源目录FileDir1、FileDir2和FileDir3中放入文件File_1.txt、File_2.txt和File_3.txt,程序运行情况如下:
- ScanDirAndGather:now, mv /home/zhou/zhouzx/TestDir/FileDir1/File_1.txt /home/zhou/zhouzx/TestDir/GatherDir
- ScanDirAndGather:now, mv /home/zhou/zhouzx/TestDir/FileDir2/File_2.txt /home/zhou/zhouzx/TestDir/GatherDir
- ScanDirAndGather:now, mv /home/zhou/zhouzx/TestDir/FileDir3/File_3.txt /home/zhou/zhouzx/TestDir/GatherDir
- ScanDirAndGather:this time,totally moved 3 file(s) to /home/zhou/zhouzx/TestDir/GatherDir
可以看到,源目录中的三个文件已经没有了,它们被移动到了结果目录GatherDir中:
- ~/zhouzx/TestDir/GatherDir> ll
- -rw——- 1 zhou users 12 2016-05-13 15:14 File_1.txt
- -rw——- 1 zhou users 12 2016-05-13 15:14 File_2.txt
- -rw——- 1 zhou users 12 2016-05-13 15:14 File_3.txt
2.一段时间之后,在源目录FileDir1中放入文件File1_4.txt,程序运行情况如下:
- ScanDirAndGather:no satisfied file in directory /home/zhou/zhouzx/TestDir/FileDir1
- ScanDirAndGather:no satisfied file in directory /home/zhou/zhouzx/TestDir/FileDir2
- ScanDirAndGather:no satisfied file in directory /home/zhou/zhouzx/TestDir/FileDir3
- ScanDirAndGather:scaned no satisfied files in all 3 dirs
可以看到,因为前缀不匹配,File1_4.txt文件仍然在源目录FileDir1中:
- ~/zhouzx/TestDir/FileDir1> ll
- -rw——- 1 zhou users 36 2016-05-13 15:19 File1_4.txt
3.一段时间之后,在源目录FileDir2中放入文件File_5.txt,在源目录FileDir3中放入文件File_11.c,程序运行情况如下:
- ScanDirAndGather:no satisfied file in directory /home/zhou/zhouzx/TestDir/FileDir1
- ScanDirAndGather:now, mv /home/zhou/zhouzx/TestDir/FileDir2/File_5.txt /home/zhou/zhouzx/TestDir/GatherDir
- ScanDirAndGather:no satisfied file in directory /home/zhou/zhouzx/TestDir/FileDir3
- ScanDirAndGather:this time,totally moved 1 file(s) to /home/zhou/zhouzx/TestDir/GatherDir
可以看到,源目录FileDir2中的文件File_5.txt已经没有了,因为后缀不匹配,源目录FileDir3中的文件File_11.c还存在:
- ~/zhouzx/TestDir/FileDir3> ll
- -rw——- 1 zhou users 4 2016-05-13 15:23 File_11.c
File_5.txt已经被移动到了结果目录GatherDir中:
- ~/zhouzx/TestDir/GatherDir> ll
- -rw——- 1 zhou users 12 2016-05-13 15:14 File_1.txt
- -rw——- 1 zhou users 12 2016-05-13 15:14 File_2.txt
- -rw——- 1 zhou users 12 2016-05-13 15:14 File_3.txt
- -rw——- 1 zhou users 36 2016-05-13 15:23 File_5.txt
4.一段时间之后,在源目录FileDir2中放入空文件File_7.txt,程序运行情况如下:
- ScanDirAndGather:no satisfied file in directory /home/zhou/zhouzx/TestDir/FileDir1
- ScanDirAndGather:/home/zhou/zhouzx/TestDir/FileDir2/File_7.txt is an empty file, so delete it directly!
- ScanDirAndGather:no satisfied file in directory /home/zhou/zhouzx/TestDir/FileDir3
- ScanDirAndGather:this time,totally moved 0 file(s) to /home/zhou/zhouzx/TestDir/GatherDir
可以看到,源目录FileDir2中的文件File_7.txt已经被删除掉了:
- ~/zhouzx/TestDir/FileDir2> ll
- total 0
六、需求扩展
基于本文中的需求和程序,可考虑对需求进行以下扩展:
1.在移动文件之前,先查看相同文件名的文件在结果目录中是否存在,如果存在,则直接将该文件在源目录中删除掉;如果不存在,才将该文件移动到结果目录中。
2.为避免结果目录中的文件过多,可以在程序中添加清理机制,即将存放时间超过一定时长的文件删除掉。
3.为了体现程序的灵活性,可将部分文件信息(如文件前缀、后缀、存放目录、扫描间隔时长等)存放到配置文件中,程序在启动时读取相关的配置项的值来执行后续目录扫描和文件移动的操作。
【本文是51CTO专栏作者周兆熊的原创文章,作者微信公众号:周氏逻辑(logiczhou)】