c/c++ 命令行小工具实现列举目录下子文件、获取文件属性

before|正文之前:

c++实验代码及学习笔记(一)

你好! 这是一个高程实验课的代码记录及学习笔记。我将记录一些重要的知识点、易错点。但是作为大学生,水平很低,敬请指点教导、优化代码。

1问题

首先我们看一下实验目标及要求:
c/c++ 命令行小工具实现列举目录下子文件、获取文件属性_第1张图片

2思路

实际上这个实验的描述非常清晰,使用主函数参数(argc,argv[])传递文件夹路径
读取路径–列举文件(可能需要判断子目录等等)
额外的功能是递归列举(方法我们可以选择DFS)列举隐藏文件(读取文件属性)

3查找相关函数

首先我们借鉴的是百度知道下的一篇回答。
参考: 如何使用c语言列举出某个目录下的文件
原答案:

用C语言列出目录下的文件,在linux下可采用readdir()函数来实现,代码实现过程为:
1.打开目录
2.循环读目录,输出目录下文件
3.关闭目录指针

参考代码:

#include 
#include 
int main()
{
    DIR *dirp; 
    struct dirent *dp;
    dirp = opendir("."); //打开目录指针
    while ((dp = readdir(dirp)) != NULL) { //通过目录指针读目录
        printf("%s\n", dp->d_name );
    }      
    (void) closedir(dirp); //关闭目录
    return 0;
}

只用opendir() readdir()就能实现了,多么简洁!
然鹅,我们是windows系统……(留下了Linux大法好的眼泪)
Windows下,代码如下

#include 
#include 
 
void printDir( const char* path )
{
    struct _finddata_t data;
    long hnd = _findfirst( path, &data );    // 查找文件名与正则表达式chRE的匹配第一个文件
    if ( hnd < 0 )
    {
        perror( path );
    }
    int  nRet = (hnd <0 ) ? -1 : 1;
    while ( nRet >= 0 )
    {
        if ( data.attrib == _A_SUBDIR )  // 如果是目录
            printf("   [%s]*\n", data.name );
        else
            printf("   [%s]\n", data.name );
        nRet = _findnext( hnd, &data );
    }
    _findclose( hnd );     // 关闭当前句柄
}
void main()
{
    printDir("d:/*.*");
}

这一片代码就够使了,当然,要想正确使(抄)出来,需要对该函数的一定理解。

解读函数

参考文章:
【主要参考】C++用 _findfirst 和 _findnext 查找文件
【函数内部结构解释很详细】C语言windows目录操作

重要函数:
文件遍历 _findfirst( ) _findnext( )

一、这两个函数均在io.h里面。
二、首先了解一下一个文件结构体

struct _finddata_t {
    unsigned    	attrib;		 	//文件属性的存储位置。它存储一个unsigned单元,用于表示文件的属性。
    time_t      	time_create; 	//这里的time_t是一个变量类型,用来存储文件创建时间。
    time_t      	time_access;	//文件最后一次被访问的时间。
    time_t      	time_write;  	//文件最后一次被修改的时间。
    _fsize_t   		size;		 	//文件的大小。这里的_fsize_t应该可以相当于unsigned整型,表示文件的字节数。
    char       		name[MAX_FNAME];//文件的文件名。这里的_MAX_FNAME是一个常量宏,它在头文件中被定义,表示的是文件名的最大长度。
};

Attrib属性

文件属性 含义
_A_ARCH 存档
_A_HIDDEN 隐藏
_A_NORMAL 正常
_A_RDONLY 只读
_A_SUBDIR 文件夹
_A_SYSTEM 系统

注意:文件属性用表示,既然是位表示,那么当一个文件有多个属性时,它往往是通过位或的方式,来得到几个属性的综合。例如只读+隐藏+系统属性, 应该为:_A_HIDDEN | _A_RDONLY |_A_SYSTEM 。

用 _findfirst 和 _findnext 查找文件

1、_findfirst函数:long _findfirst(const char , struct _finddata_t );
第一个参数为文件名,可以用"
.
“来查找所有文件,也可以用”*.cpp"来查找.cpp文件。
第二个参数是_finddata_t结构体指针。若查找成功,返回文件句柄,若失败,返回-1。

2、_findnext函数:int _findnext(long, struct _finddata_t *);
第一个参数为文件句柄,第二个参数同样为_finddata_t结构体指针。若查找成功,返回0,失败返回-1。

3、_findclose函数:int _findclose(long);

只有一个参数,文件句柄。若关闭成功返回0,失败返回-1。

long _findfirst( char *filespec, struct _finddata_t *fileinfo );
// 功  能 : 提供与filespec指定入口泛式匹配的第一个文件.通常后继用_findnext函
//          数后续使用来完成某泛式下的文件遍历.
// 头文件 : #include 
// 参  数 : filespec - 目标文件规范,可以包含通配符
//          fileinfo - 文件信息buffer
// 返回值 : 成功返回唯一的搜索句柄
//          出错返回-1,且设置errno为如下值:
//            ENOENT 该泛式无法匹配
//            EINVAL 无效文件名
int _findnext( long handle, struct _finddata_t *fileinfo );
// 功  能 : 按照前面_findfirst中的泛式规则,查找下一个符合该泛式的文件,并以此为依据
//          修改fileinfo中的值
// 头文件 : #include 
// 参  数 : long handle - 搜索句柄(通常由紧靠其前的_findfirst()返回)
//          fileinfo    - 文件信息buffer
// 返回值 : 成功返回0
//          出错返回-1,且设置errno为如下值:
//            ENOENT 没有更多的符合该泛式的文件
 
int _findclose( long handle );
// 功  能 : 关闭搜寻句柄并释放相应资源
// 头文件 : #include 
// 参  数 : long handle - 搜索句柄(通常由紧靠其前的_findfirst()返回)
// 返回值 : 成功返回0
//          出错返回-1,且设置errno为如下值:
//            ENOENT 没有更多的符合该泛式的文件

到这里我们大概清楚函数的使用方法了,开始试验

4发现问题

  1. 主函数参数的应用
int main(int argc,char *argv[]) 
{	 
	printDir(argv[1]);
	return 0;
}
  1. findfirst函数
void printDir( const char* path )
{
    struct _finddata_t data;
    long hnd = _findfirst( path, &data );    // 查找文件名与正则表达式chRE的匹配第一个文件
    if ( hnd < 0 )
    {
        perror( path );	//如果无效/错误,返回-1,输出错误perror
    }
    int  nRet = (hnd <0 ) ? -1 : 1;	//三目运算式,当括号内成立,输出-1,若不满足,输出冒号后的1
    while ( nRet >= 0 )	//也即是hnd>=0,文件名有效
    {
        if ( data.attrib == _A_SUBDIR )  // 如果是目录
            printf("   [%s]*\n", data.name );
        else
            printf("   [%s]\n", data.name );
        nRet = _findnext( hnd, &data );
    }
    _findclose( hnd );     // 关闭当前句柄
}

这个应用起来最大的问题是该函数遍历的是文件名,识别"*.* ",而不是文件夹路径
其实解决方法非常简单,但是我想了好一会,被其他博客启发才弄明白
就是在路径后加//*.* 就可以遍历该路径下所有文件了
具体操作方法有很多种,如果输入的参数是long的,可以直接赋值,如果是char型,则需要strcat函数

	struct _finddata_t data;
	char file[100];
	strcpy(file,path);
	_chdir(path);
	strcat(file,"\\*.*"); //直接反斜杠/*.*也可以,否则转义符号要两个\\

为了控制台中更加美观,做了一些小修改,下面效果图是改之前的。

long hnd = _findfirst(file, &data);
	if(hnd < 0)
	{
		perror(file);
	}
	int nRet = (hnd < 0) ? -1 : 1; 
	while(nRet >= 0)
	{
		if(data.attrib & _A_SUBDIR) 		//我这里都改成&,用==为什么不行?
		{
			printf("\n	[%s]*\n",data.name);//这里是后来改的,与效果图不同
		} 
		else
			printf("	 %s\n",data.name);
		nRet = _findnext(hnd,&data);  
	}
	_findclose(hnd);

效果如下:(这里还有个小问题,就是[.]*被识别了,我们之后会解决)
c/c++ 命令行小工具实现列举目录下子文件、获取文件属性_第2张图片

5遍历子目录

第一段我们只是完成了基础功能。识别了出子目录,但并未进入子目录中列举子文件。下面我们通过DFS深度优先搜索来递归遍历子文件

遍历文件夹及其子文件夹下所有文件。操作系统中文件夹目录是树状结构,使用深度搜索策略遍历所有文件。用到_A_SUBDIR属性

刚才我们提到有一个小问题,这个小问题在遍历时就会麻烦:

在判断有无子目录的if分支中,由于系统在进入一个子目录时,匹配到的头两个文件(夹)是"."(当前目录),". ."(上一层目录)。需要忽略掉这两种情况。当需要对遍历到的文件做处理时,在else分支中添加相应的代码就好

标准代码参考引用文章[^1]
本人初学c++(其实c语言也是入门),不太熟悉,代码以c语言为主
特别提一下DFS,这个算法应用应该是基础了,萌新还不太会哈,会记录下来之后专门学习的。

#include
#include
#include
#include
using namespace std;

void dfsFolder(const char *path,ofstream &fout)
{
	struct _finddata_t data;
	char file[100];
	strcpy(file,path);
	_chdir(path);
	strcat(file,"\\*.*");
	
	long hnd = _findfirst(file, &data);
	if(hnd < 0)
	{
		perror(file);
	}
	int nRet = (hnd < 0) ? -1 : 1;
	while(nRet >= 0)
	{
		if(data.attrib & _A_SUBDIR) 
		{
			if((strcmp(data.name,".") != 0)&&(strcmp(data.name,"..")!=0)) //这个是筛除的一种方法哈,还有很多种
			{
				printf("\n	[%s]*\n",data.name);
				char newPath[100];
				strcpy(newPath,path);
				strcat(newPath,"\\");
				strcat(newPath,data.name);
				dfsFolder(newPath,fout);		//这里递归遍历哈
			} 
		}	
		else
			
			printf("	%s\n",data.name);
			
		nRet = _findnext(hnd,&data);  
	}
	_findclose(hnd);//关闭当前句柄 
	fout.close();
}


int main(int argc,char *argv[]) 
{	 
	
	ofstream o_fstream;
	
	dfsFolder(argv[1],o_fstream);
	
	return 0;
}

效果如下:(有一些瑕疵,遍历的比较随意,层级关系不是很明显。但是我觉得已经差不多了,可以进一步优化)
c/c++ 命令行小工具实现列举目录下子文件、获取文件属性_第3张图片
这个其实思路上非常简单。那么,我们接下来实现第三个功能,获取文件属性。

6获取文件属性

前面提到了attrib的多个属性,我们正好可以利用这些值
关于这个功能的函数有很多种,我们这里选择适合win系统的最简单的方法

void allDir(const char *path){
	struct _finddata_t data;
	char file[100];
	strcpy(file,path);
	_chdir(path);
	strcat(file,"\\*.*");
	
	long hnd = _findfirst(file, &data);
	if(hnd < 0)
	{
		perror(file);
	}

		printf("文件列表:\n");
	while(_findnext(hnd, &data) == 0)
	{
	
		string tempName = data.name;
		if(tempName[0] == '.')
			continue;
		if(data.attrib & _A_NORMAL)
		{
			printf("	普通文件	");
		}	
		else if(data.attrib & _A_RDONLY)
		{
			printf("	只读文件	");
		}
		else if(data.attrib & _A_HIDDEN)
		{
			printf("	隐藏文件	");
		}
		else if(data.attrib &_A_SYSTEM)
		{
			printf("	系统文件	");
		}
		else if(data.attrib & _A_SUBDIR)
		{
			printf("	子目录  	");
		}
		else{
			printf("	存档文件	");
		}
			
		if(data.attrib & _A_SUBDIR)
		{
			if((strcmp(data.name,".") != 0)&&(strcmp(data.name,"..")!=0))
			{
			printf("	[%s]*\n",data.name);
			}
		}else
			printf("	%s\n",data.name);
			
	}
	
	_findclose(hnd);
	
}

本来也是很简单的,但是在实现过程中发现,所有的data.attrib == 号都必须改成&号。否则会出现不理想的结果。这个我很不解,参考答案是可以实现的。思考了一下,&是位运算,刚才我们提到attrib这些属性是以位储存的值,这些都是在中定义的宏,可以直接使用,而本身的意义其实是一个无符号整型 (只不过这个整型应该是2的几次幂,从而保证只有一位为1,而其他位为0)。既然是位表示,那么当一个文件有多个属性时,它往往是通过位或的方式,来得到几个属性的综合。例如只读+隐藏+系统属性,应该为:_A_HIDDEN | _A_RDONLY |_A_SYSTEM 。
但具体原理我也不清,希望大佬可以点拨一下?

效果:
c/c++ 命令行小工具实现列举目录下子文件、获取文件属性_第4张图片

7聚合功能

到这儿您就可以右上角叉了,这个臃肿代码比较多余。
老师要求我们可以做个可供选择的,本来写了个一键聚合版,除主函数外只有一个函数,简洁但能实现所有功能,但是为了实现选择功能(想打印打印、想遍历遍历),做了个最终聚合版

#include
#include
#include
#include
#include
using namespace std;

//final版 全功能 
/*
  1、控制台输入flist C : \Windows 即可列举出文件夹下所有文件,遍历子目录文件,同时显示文件信息 
  2、flist C:\Windows print可以只打印文件名
  3、flist C:\Windows all 可以显示文件信息并打印文件名 
*/

void dfsFolder(const char *path,ofstream &fout)
{
	struct _finddata_t data;
	char file[100];
	strcpy(file,path);
	_chdir(path);
	strcat(file,"\\*.*");
	
	long hnd = _findfirst(file, &data);
	if(hnd < 0)
	{
		perror(file);
	}
	
	while(_findnext(hnd, &data) == 0)
	{
		string tempName = data.name;
		if(tempName[0] == '.')
			continue;
		if(data.attrib & _A_NORMAL)
		{
			printf("	普通文件	");
		}	
		else if(data.attrib & _A_RDONLY)
		{
			printf("	只读文件	");
		}
		else if(data.attrib & _A_HIDDEN)
		{
			printf("	隐藏文件	");
		}
		else if(data.attrib &_A_SYSTEM == _A_SYSTEM)
		{
			printf("	系统文件	");
		}
		else if(data.attrib & _A_SUBDIR)
		{
			printf("	子目录  	");
		}
		else{
			printf("	存档文件	");
		}
			
		if(data.attrib & _A_SUBDIR) 
		{
			if((strcmp(data.name,".") != 0)&&(strcmp(data.name,"..")!=0))
			{
				printf("\n	[%s]*\n",data.name);
				char newPath[100];
				strcpy(newPath,path);
				strcat(newPath,"\\");
				strcat(newPath,data.name);
				dfsFolder(newPath,fout);
			} 
		}	
		else
			
			printf("	%s\n",data.name);
			 
	}
	_findclose(hnd);
	fout.close();
}

void printDir(const char *path){
	struct _finddata_t data;
	char file[100];
	strcpy(file,path);
	_chdir(path);
	strcat(file,"\\*.*");
	
	long hnd = _findfirst(file, &data);
	if(hnd < 0)
	{
		perror(file);
	}
	int nRet = (hnd < 0) ? -1 : 1; 
	while(nRet >= 0)
	{
		if(data.attrib & _A_SUBDIR) 
		{
			printf("\n	[%s]*\n",data.name);
		} 
		else
			printf("	 %s\n",data.name);
		nRet = _findnext(hnd,&data);  
	}
	_findclose(hnd);
}

void allDir(const char *path){
	struct _finddata_t data;
	char file[100];
	strcpy(file,path);
	_chdir(path);
	strcat(file,"\\*.*");
	
	long hnd = _findfirst(file, &data);
	if(hnd < 0)
	{
		perror(file);
	}

		printf("文件列表:\n");
	while(_findnext(hnd, &data) == 0)
	{
	
		string tempName = data.name;
		if(tempName[0] == '.')
			continue;
		if(data.attrib & _A_NORMAL)
		{
			printf("	普通文件	");
		}	
		else if(data.attrib & _A_RDONLY)
		{
			printf("	只读文件	");
		}
		else if(data.attrib & _A_HIDDEN)
		{
			printf("	隐藏文件	");
		}
		else if(data.attrib &_A_SYSTEM)
		{
			printf("	系统文件	");
		}
		else if(data.attrib & _A_SUBDIR)
		{
			printf("	子目录  	");
		}
		else{
			printf("	存档文件	");
		}
			
		if(data.attrib & _A_SUBDIR)
		{
			if((strcmp(data.name,".") != 0)&&(strcmp(data.name,"..")!=0))
			{
			printf("	[%s]*\n",data.name);
			}
		}else
			printf("	%s\n",data.name);
			
	}
	
	_findclose(hnd);
	
}

int main(int argc,char *argv[]) 
{		
	ofstream o_fstream;
	
	if(strcmp(argv[2],"all") == 0)
	{
		allDir(argv[1]);
	}
	else if(strcmp(argv[2],"print") == 0)
	{
		printDir(argv[1]);
	}
	else
	dfsFolder(argv[1],o_fstream);
	
	return 0;
}

坚持到这里的小伙伴不容易呀,感谢您的阅读,嘻嘻~
鸣谢所有无私奉献的博主,面向百度编程的萌新给您拜年了 感谢您的付出!
我的gayhub
[1]: http://www.cnblogs.com/ranjiewen/p/5960976.html

你可能感兴趣的:(学习笔记,c/c++)