1. 程式人生 > >C語言實現cp -r --parents拷貝檔案和資料夾

C語言實現cp -r --parents拷貝檔案和資料夾

    linux下cp -r --parents為回溯的拷貝資料夾,同時複製時保留檔案的目錄結構。下面用posix標準的C語言來實現它。

    filetype函式用來檢測指定目錄下面的檔案是否存在,如果存在的話,是那種型別的檔案。函式返回'n'表示檔案不存在,返回b,c,d,p,l,f,s,u分別表示block檔案,字元裝置檔案,資料夾,管道,連結,常規檔案,socket檔案以及未知型別檔案。程式碼如下:

char filetype(char * file_name){

	if(access(file_name,R_OK)!=0){
		printf("file %s not exist!",file_name);
		return 'n';
	}else{
		struct stat filestat={};
		int return_state=stat(file_name,&filestat);
		if(return_state<0){
			perror("stat");
			exit(1);
		}

		switch (filestat.st_mode & S_IFMT) {
			case S_IFBLK:  printf("%s:block device\n",file_name);            return 'b';      
			case S_IFCHR:  printf("%s:character device\n",file_name);        return 'c';  
			case S_IFDIR:  printf("%s:directory\n",file_name);               return 'd'  ;
			case S_IFIFO:  printf("%s:FIFO/pipe\n",file_name);               return 'p';
			case S_IFLNK:  printf("%s:symlink\n",file_name);                 return 'l';
			case S_IFREG:  printf("%s:regular file\n",file_name);            return 'f';
			case S_IFSOCK: printf("%s:socket\n",file_name);                  return 's';
			default:       printf("%s:unknown?\n",file_name);                return 'u';
		}
	}
}

   copy_file_posix函式是linux c語言的單個檔案複製函式,使用open,write函式,符合posix標準。
void copy_file_posix(char* source_path,char *destination_path){ 
	char buffer[1024];  
	int fdin,fdout;
	fdin=open(source_path,O_RDONLY);
	if(fdin<0){
		perror("open");  
		exit(1);  
	} 
	fdout=open(destination_path,O_CREAT|O_WRONLY,777);
	if(fdout<0){
		perror("open");
		exit(1);  
	}  
	int len;
	while((len=read(fdin,buffer,sizeof(buffer)))>0){  
		write(fdout,buffer,len);
	}  
	close(fdout);  
	close(fdin);  
}  

同時它的相似實現copy_file_ansi標準函式如下,其中使用了fopen,fwrite等,程式碼如下:
void copy_file_ansi(char* source_path,char *destination_path){
	char buffer[1024];  
	FILE *in,*out;
	if((in=fopen(source_path,"r"))==NULL){
		perror("fopen");
		exit(1);  
	}  
	if((out=fopen(destination_path,"w"))==NULL){
		perror("fopen");
		exit(1);  
	}  
	int len;
	while((len=fread(buffer,1,1024,in))>0){
		fwrite(buffer,1,len,out);
	}  
	fclose(out);  
	fclose(in);  
}

複製資料夾不可避免的需要建立資料夾,下面的函式可以建立單個資料夾,也可以建立多級資料夾,實現了mkdir -p功能。
int make_dir_recursively(char* name){

	const char * split = "/"; 
	char * p; 
	p = strtok(name,split); 
	char  basepath[1024]="";
	while(NULL!=p) { 
		sprintf(basepath, "%s%s/", basepath,p);
		printf("create dir :%s\n",basepath);
		if(mkdir(basepath,0777)<0){
			if(EEXIST!=errno){
				perror("mkdir");
				return -1;
			}
		}

		p = strtok(NULL,split); 
	} 
	return 0;	

}

下面幾個函式都是工具函式,用法和Java同名函式相同。
int lastIndexOf(char *source_str,char *target_str)  
{  
	char *p=source_str;  
	int i=0,len=strlen(target_str);  
	p=strstr(source_str,target_str);  
	if(p==NULL)return -1;  
	while(p!=NULL)  
	{  
		for(;source_str!=p;source_str++)i++;  
		p=p+len;  
		p=strstr(p,target_str);  
	}  
	return i;  
} 

void substring(char * src,char * dest,int start_index,int len)  
{  
	if(start_index > strlen(src))
		return ;
	len = (strlen(src) - start_index) > len ? len:(strlen(src) - start_index);
	printf("len:%d\n",len);

	int slowwalker=0,i;
	for(i=start_index;i<start_index+len;i++,slowwalker++)  
	{  
		dest[slowwalker]=src[i];  

	}  
	dest[slowwalker]='\0';  

} 

下面的函式將帶目錄的檔案格式的目錄和檔名稱分隔開,例如檔案hello/world/a.txt,我們將這個字串分割為"hello/world/"和"a.txt"。
void split_path_and_name(char path[],char basepath[],char  filename[]){

	int index=lastIndexOf(path,"/");
	if(index<0){
		printf("path %s incorrect",path);
	}

	substring(path,basepath,0,index);
	substring(path,filename,index+1,strlen(path));

}


以下兩個函式是拷貝資料夾的主題,其中copy_dir主要用於複製檔案的準備工作,例如複製的源目錄和目標目錄,如果源目錄是一個檔案會怎麼處理,如果源目錄不存在會怎麼處理,如果源目錄是一個資料夾會怎麼處理,如果目標目錄是一個檔案會怎麼處理,如果目標目錄不存在會怎麼處理,如果目標目錄和源目錄包含目錄分隔符"/"又該怎麼處理等。裡面情況太多,寫到最後越寫越迷糊,整個程式就下面的兩個函式欠考慮的情況很多,BUG也很多,請輕拍。

void walk_dir(char *dir,char *dest_path)
{
	char name[MAX_PATH];
	char tmp[512]="";
	struct dirent *dp;
	DIR *dfd;
	if ((dfd = opendir(dir)) == NULL) {
		fprintf(stderr, "dirwalk: can't open %s\n", dir);
		return;
	}
	while ((dp = readdir(dfd)) != NULL) {
		if (strcmp(dp->d_name, ".") == 0
				|| strcmp(dp->d_name, "..") == 0)
			continue; /* skip self and parent */
		if (strlen(dir)+strlen(dp->d_name)+2 > sizeof(name))
			fprintf(stderr, "dirwalk: name %s %s too long\n",
					dir, dp->d_name);
		else {
			if(lastIndexOf(dir,"/")==(strlen(dir)-1)){
				sprintf(name, "%s%s", dir, dp->d_name);
			}else{
				sprintf(name, "%s/%s", dir, dp->d_name);
			}
			char tmp[512]="";
			sprintf(tmp,"%s%s/%s",tmp,dest_path,name);
			if(filetype(name)=='d'){


				make_dir_recursively(tmp);
				
			}else{
				
				copy_file_posix(name,tmp);
			}
		}
	}
	closedir(dfd);
}



void copy_dir(char* source_path,char *destination_path){ 
	int is_source_directory=0;
	int dest_file_status=0;
	if(filetype(source_path)=='n'){
		printf("file %s not exist!\n",source_path);
		exit(1);
	}else if(filetype(source_path)=='d'){
		is_source_directory=1;
	}
	printf("is_source_directory:%d\n",is_source_directory);
	if(filetype(destination_path)=='n')
	{
		printf("dest file %s not exist\n",destination_path);
	}else if (filetype(destination_path)=='d'){
		printf("dest file %s  exist,and is a directory\n",destination_path);
		dest_file_status=1;
	}else{
		printf("dest file %s  exist,copy failed\n",destination_path);
		exit(1);

	}

	printf("dest_file_status:%d\n",dest_file_status);
	char  basepath[512]="";
	if(is_source_directory==1){
		
		if(lastIndexOf(destination_path,"/")==(strlen(destination_path)-1)){
			sprintf(basepath,"%s%s%s",basepath,destination_path,source_path);
			printf("copy_dir:mkdir:%s\n",basepath);
		}else{
			sprintf(basepath,"%s%s/%s",basepath,destination_path,source_path);
			printf("copy_dir:mkdir %s\n",basepath);

		}	
		printf("mkdir %s recursively\n",basepath);
		make_dir_recursively(basepath);
		printf("walk_dir  source_path:%s  basepath:%s\n",source_path,basepath);
		walk_dir(source_path,basepath);

	}else{
		copy_file_posix(source_path,destination_path);
	}
}  

最後是main函式,如下所示:
int main(int argc,char ** argv){
	if(argc<3){
	printf("./a.out source dest");
	return 0;
	}
	copy_dir(argv[1],argv[2]);

}
 第一次寫部落格,疏漏很多,同時因為以前主要接觸的是Java程式碼,程式裡面Java經典的駝峰命名法和c/c++的下劃線命名法交替出現,請讀者忽略這些瑕疵,大家一起進步。