copy命令的实现

Posted by Zreal on December 24, 2018

命令行copy命令的具体实现

要求

实现一个文件复制命令mycopy,能够支持多级目录(子目录)的复制,要能够支持Linux下的soft link和Windows下的快捷方式复制。

数据结构及算法流程

算法流程图:

img

数据结构:

copy_dir类

​ 首先定义了一个copy_dir类,文件夹所有的拷贝操作都封装在这个类之中,copy_dir给外界只提供一个copy的接口,传入源文件夹路径和目的文件夹路径,完成copy文件操作,copy_dir类定义的功能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef COPYDIR_H
#define COPYDIR_H
#include<string>
#include<vector>

class copy_dir
{
public:
	copy_dir();
	~copy_dir();
	//外部调用copy函数,进行文件夹拷贝
	void copy(const std::string& srcDirPath, const std::string &desDirPath);
private:
	//新建pathname下的文件夹
	bool make_dir(const std::string &pathname);
	//得到源文件夹下所有的文件路径
	void get_src_files_name(std::vector<std::string> &fileNameList,std::string path);
	//进行文件复制操作
	void do_copy(const std::vector<std::string>& fileNameList);
private:
	std::string srcDirPath, desDirPath;
};

#endif //COPYDIR_H

copy函数

​ copy是一个公有接口,供外界调用,传入源文件夹路径和目的文件夹路径。然后进行以下步骤:

  1. 对源文件夹路径进行解析,找到要拷贝的那个文件夹的文件名,和目的文件夹路径进行拼接,就是我们拷贝之后的新文件夹的完成路径。

  2. 因为新文件夹此时还不存在,调用私有函数make_dir,在该路径下新建一个新文件夹。

  3. 然后调用get_src_files_name接口,对源文件夹进行深度搜索,将所有文件的相对路径存储在fileNameList中。

  4. 调用do_copy,遍历fileNameList中的相对路径,通过路径的拼接,得到源文件的绝对路径和新的拷贝文件的绝对路径,进行文件的读写,完成文件夹的拷贝。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    
    void copy_dir::copy(const std::string& srcDirPath, const std::string &desDirPath)
    {
       
    	this->srcDirPath = srcDirPath;
    	std::string srcDir;
    /*解析源路径,得到要拷贝的文件夹的名字srcDir*/
    #ifdef _WIN32
    	size_t pos = 0;
    	//npos,string::type_size类型的,是find()返回的类型,当find找不到指定值的情况下返回npos
    	while (srcDirPath.find('\\', pos) != std::string::npos) {
    		pos = srcDirPath.find('\\', pos) + 1;
    	}
    	if (pos == 0) {
    		std::cout << "src path error" << std::endl;
    		return;
    	}
    	//最后一级目录,即为要复制的文件夹
    	srcDir = srcDirPath.substr(pos - 1, srcDirPath.size());
    #else
    	size_t pos = 0;
    	while (srcDirPath.find('/', pos) != std::string::npos) {
    		pos = srcDirPath.find('/', pos) + 1;
    	}
    	if (n == 0) {
    		std::cout << "src path error" << std::endl;
    	}
       
    	srcDir = srcDirPath.substr(pos - 1, srcDirPath.size());
    #endif
    /*创建目的路径下对应的新的文件夹*/
    	this->desDirPath = desDirPath+srcDir;
    	if (!make_dir(this->desDirPath)) {
    		std::cout << "make directory error!" << std::endl;
    		return;
    	}
    /*深度搜索源文件下的所有文件,搜索的同时在目的路径下创建新文件夹的子文件夹*/
    	std::vector<std::string>fileNameList(0);
    	try {
    		copy_dir::get_src_files_name(fileNameList, "");
    	}
    	catch (...) {
    		throw;
    	}
       
    	if(fileNameList.empty()){
    		std::cout << "source directory is empty" << std::endl;
    		return;
    	}
    /*遍历所有文件的相对路径,对其进行路径拼接得到绝对路径后进行文件的读写,完成拷贝*/
    	do_copy(fileNameList);
    }
    

    copy_dir::make_dir 函数

    ​ 该函数包装了一个创建文件夹的操作,主要函数是_mkdir和mkdir函数

    1
    2
    3
    4
    5
    
    //windows下
    int _mkdir(const char *pathname); 
    //linux 下 多一个参数为限制其权限的参数 mode,具体权限可查
    int mkdir(const char *pathname,mode_t mode)
    //函数创建成功返回 0,否则返回-1;
    

    ​ 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    //创建新的文件夹
    bool copy_dir::make_dir(const std::string &pathName) {
    #ifdef _WIN32
    	//创建成功返回0,创建成功返回-1;mkdir(const char *path)所以string类型要转换成char*类型(c语言里的字符串)
    	if (_mkdir(pathName.c_str()) < 0) {
    		std::cout << "create path error"+pathName << std::endl;
    		return false;
    	}
    #else
    	if (mkdir(pathName.c_str(), S_IREAD | S_IRGRP | S_IXGRP) < 0)
    	{
    		std::cout << "create path error" +pathName<< std::endl;
    		return false;
    	}
    #endif
    	return true;
    }
    

    copy_dir::get_src_files_name 函数

    ​ 这是该类的一个最为重要的函数,他对源文件夹进行了深度的搜索,将所有的文件相对路径存入了一个fileNameList,在搜索源文件夹的同时,根据相对路径进行路径拼接,在目标文件夹下建立对应的子文件夹。

    ​ 下面介绍具体的数据结构:

    _finddata_t结构体

    _finddata_t为文件的一个结构体,在io.h头文件中,其中文件属性用了宏定义有下面介个文件属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    struct  _finddata_t
    {
    unsigned attrib;       //文件属性
    time_t  time_create;   //文件创建时间
    time_t  time_access;   //文件上一次访问时间
    time_t  time_write;    //文件上一次修改时间
    _fsize_t size;         //文件字节数
    char  name[_MAX_FNAME];//文件名
    };   
    /*
    其中文件属性用宏定义
    _A_ARCH(存档)  _A_SUBDIR(文件夹)_A_HIDDEN(隐藏)
    _A_SYSTEM(系统) _A_NORMAL(正常),_A_RDONLY(只读)
    */
    

    long _findfirst( char *filespec, struct _finddata_t *fileinfo )

    int _findnext( long handle, struct _finddata_t *fileinfo );

    int _findclose( long handle );

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    long _findfirst( char *filespec, struct _finddata_t *fileinfo )
    /*  
    返回值:如果查找成功的話,將返回一個long型的唯一的查找用的句柄(就是一個唯一編號)。
    這個句柄將在_findnext函數中被使用。若失敗,則返回-1。
    参数:
    filespec表明文件名的字符串,支持通配符,例如 *.c ,则表示当前文件夹下所有c文件
    fileinfo,用来存放文件信息的结构体指针,该函数成功后,会把找到的文件信息放到fileinfo结构体中
    */
    int _findnext( long handle, struct _finddata_t *fileinfo );
    /*
    返回值:若成功返回0,否則返回-1。
    参数:
    handle:即由_findfirst函数返回回来的句柄
    fileinfo:文件信息结构体指针。找到文件后,将文件信息存放到此结构体重
    */	
    int _findclose( long handle );
    /*
    返回值:若成功返回0,否則返回-1。
    参数:handle:即由_findfirst函数返回回来的句柄
    */
    

    查找文件夹下的文件格式:

    ​ 用_findfirst查找第一个文件;若成功返回句柄调用_findnext函数查找其他文件,当查找完毕后,用_findclose函数结束查找。

    ​ 查找文件模板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    #include<stdio.h>
    #include<io.h>
    int main(){
        long handle;
        to_search="C:\\WINDOWS\\*.exe";
        //windows文件夹下的所有可执行文件,通常这个路径是被拼接而成
        struct _finddata_t fileinfo;
        handle=_findfirst();
        if(handle==-1){
            return -1;
        }
        else{
            do{
                //do something
            }while(_findnext(handle,&fileinfo)==0)
        }
        //循环结束后关掉句柄
        _findclose(handle);
    }
    

    Linux下查找当前目录文件的数据结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    	DIR *dir;
    	struct dirent *ptr;
    //打开文件夹
    	if ((dir = opendir(now_path.c_str())) == NULL)
    	{
    		std::cout << this->srcDirPath<< " not found" << std::endl;
    		return;
    	}
    //读取文件
    	while ((ptr = readdir(dir)) != NULL){
            //do something
        }
    

    两个关键的参数DIR类的指针dir,和struct dirent 的指针ptr

    DIR类的指针dir,用来打开要遍历的文件夹,ptr指向的是一个文件的对象

    其中要说明的是ptr的d_name属性标识文件名,d_type属性标识文件类型。

    如果d_type属性为10是链接文件例如快捷方式,d_type属性为8是文件,d_type属性为4为文件夹。在遍历过程中通过判断不同的文件类型来进行相应的操作。

    该函数的执行流程图如下:

    img

​ 函数中调用了自身迭代进行深度搜索,其中fileNameList中记录的全是在源文件路径下的相对路径,方便后面拷贝文件时的路径拼接,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//fileNameList为记录相对路径的vec,subpath为源文件夹下子文件夹的相对路径
void copy_dir::get_src_files_name(std::vector<std::string>&fileNameList,std::string subpath) {
#ifdef _WIN32
	_finddata_t fileinfo;
	//定义要筛选的文件格式 *.*代表所有文件
	std::string now_path = subpath.length() != 0 ? this->srcDirPath + "\\" + subpath : this->srcDirPath;
	std::string now_des_path= subpath.length() != 0 ? this->desDirPath + "\\" + subpath : this->desDirPath;
    
	std::string fileformat = now_path + "\\*.*";
	long handle = _findfirst(fileformat.c_str(), &fileinfo);
	if (handle == -1) {
		std::cout <<now_path<< "not Found" << std::endl;
		return;
	}
	else {
		//查找下一个文件,相当于一个迭代器
		do{
			if (strcmp(fileinfo.name, ".") == 0 || strcmp(fileinfo.name, "..")==0)
				continue;
			//如果该文件为文件夹,递归调用自身函数
			if (fileinfo.attrib & _A_SUBDIR) {
				std::string dirname = fileinfo.name;
				//先在目标文件夹下建立文件夹
				if (copy_dir::make_dir(now_des_path + "\\" + dirname)) {
					copy_dir::get_src_files_name(fileNameList, subpath + "\\" + dirname);
				}
				else {
					std::cout << "create dir failed"+now_path+"\\"+dirname << std::endl;
					return;
				}
				
			}
			else {
				fileNameList.push_back(subpath+"\\"+fileinfo.name);
			}
		
		} while (_findnext(handle, &fileinfo) == 0);
	}
	//最后要关掉句柄
	_findclose(handle);
#else
	DIR *dir;
	struct dirent *ptr;
	std::string now_path = subpath.length() != 0 ? this->srcDirPath + "/" + subpath : this->srcDirPath;
	std::string now_des_path= subpath.length() != 0 ? this->desDirPath + "/" + subpath : this->desDirPath;
	if ((dir = opendir(now_path.c_str())) == NULL)
	{
		std::cout << this->srcDirPath<< " not found" << std::endl;
		return;
	}

	while ((ptr = readdir(dir)) != NULL)
	{
		if ((ptr->d_name == ".") || (ptr->d_name == ".."))  //current / parent
			continue;
		else if (ptr->d_type == 8)  //file
			fileNameList.push_back(ptr->d_name,subpath+"/"+ptr->d_name);
		else if (ptr->d_type == 10)  //link file
			continue;
		else if (ptr->d_type == 4)  //dir
			if (copy_dir::make_dir(now_des_path + "/" + ptr->d_name)) {
				copy_dir::get_src_files_name(fileNameList, subpath + "/" + ptr->d_name);
			}
			else {
				std::cout << "create dir failed" << std::endl;
				return;
			}
	}
	closedir(dir);
#endif
	return;
}

copy_dir::do_copy函数

​ 该函数遍历fileNameList,对路径进行拼接后,进行文件拷贝,c++对文件的拷贝用到了fstream.h这个头函数。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void copy_dir::do_copy(const std::vector<std::string> &fileNameList) {
	for (unsigned int i = 0; i < fileNameList.size(); i++) {
		std::string nowSrcFilePath, nowDesFilePath;
#ifdef _WIN32
		nowSrcFilePath = this->srcDirPath + "\\" + fileNameList.at(i);
		nowDesFilePath = this->desDirPath + "\\" + fileNameList.at(i);
#else
		nowSrcFilePath = this->srcDirPath + "/" + fileNameList.at(i);
		nowDesFilePath = this->desDirPath + "/" + fileNameList.at(i);
#endif
		std::ifstream in;
		in.open(nowSrcFilePath,std::ios_base::binary);
		if (!in) {
			std::cout << "open src file:" << nowSrcFilePath << "failed" << std::endl;
			continue;
		}
		std::ofstream out;
		out.open(nowDesFilePath,std::ios_base::binary);
		if (!out) {
			std::cout << "create new file:" << nowDesFilePath << "failed" << std::endl;
			in.close();
			continue;
		}

		out << in.rdbuf();
		out.close();
		in.close();
	}
}

测试及结果

windows下

img

img

test第一层级目录

img

test2第一层级目录

img

test最底层目录

img

test2最底层目录

img

linux下(deepin 15.8)

因为整个代码在window vs ide中完成,所以在linux用g++编译时要渠道 stdax.h头文件

在命令行中执行

img

test第一层目录

img

test2第一层目录

img

test 最底层目录

img

test2最底层目录

img