昨天我们主要从主函数入手,对命令行参数进行合法性检测,并引导主程序进入相应的子函数。今天我们要实现一个最基本的复制功能,将一个源文件复制到指定路径。之所以说路径,是因为目的文件可能是一个存在的文件,也可能是一个不存在的文件或者是一个目录(不存在的目录会出错)。在我们详细分析代码前,先看看我做的这个my_cp的运行结果吧。
1.成功将一个已存在源文件复制到另一个指定文件名的文件中。
gues@huangwei-desktop:~/code/shell_command$ ls cptest dd dd1 ed ls ls1 my_cp my_cp1 my_cp.c my_ls.c my_shell.c newls.c tdir test tfile.c gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ttfile.c gues@huangwei-desktop:~/code/shell_command$ ls -l 总用量 124 -rw-r--r-- 1 gues gues 7378 2010-06-22 23:58 my_ls.c -rw-r--r-- 1 gues gues 6271 2010-07-17 14:29 my_shell.c -rw-r--r-- 1 gues gues 7378 2010-07-25 17:20 newls.c drwxr-xr-x 2 gues gues 4096 2010-07-25 18:03 tdir drwxr-xr-x 3 gues gues 4096 2010-07-25 18:03 test -rw-r--r-- 1 gues gues 6271 2010-07-25 16:35 tfile.c -rw-r--r-- 1 gues gues 6271 2010-07-26 10:14 ttfile.c
2.将已存在的源文件拷贝到一个不存在的目录下,会提示错误信息。
gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ~/nothisdirectory/ my_cp:can't create the file:"/home/gues/nothisdirectory/":it is a directory.
3.将不存在的源文件拷贝到一个目录或文件中,提示相应错误。这里的目标文件或指定目录是否存在不确定。因为只有一个源文件时,cp命令总先检查源文件是否存在。
gues@huangwei-desktop:~/code/shell_command$ ./my_cp nothisfile ~/nothisdirectory my_cp: can't get file status of "nothisfile" : no this file or directory.
4.成功将源文件拷贝到已存在的指定目录,由于指定路径没有文件名,因此目标文件名与源文件名相同。
gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ~/ gues@huangwei-desktop:~/code/shell_command$ ls ~/ code Documents EIOffice EIOffice_Personal_Lin.tar.gz Pictures tfile.c Yozo_Office cptest Downloads EIOfficelog.txt examples.desktop Public tmp Desktop edsionte EIOffice_Personal_Lin Music
5.之所以首先演示这些结果是因为我们在编写cp_single函数的时候都要考虑到这些情况,加之路径相对灵活可能少一个/就会产生不结果。比如下面的结果:
gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ~/nothisdirectory gues@huangwei-desktop:~/code/shell_command$ ls ~/ code Documents EIOffice EIOffice_Personal_Lin.tar.gz nothisdirectory Templates Videos cptest Downloads EIOfficelog.txt examples.desktop Pictures tfile.c Yozo_Office Desktop edsionte EIOffice_Personal_Lin Music
拷贝成功。这里我们输入的参数仅仅与2中输入的参数少一个/,为什么结果就大不相同?因为2中目标文件是一个不存在的目录(~/nothisdirectory/),而上面的命令是将已存在文件拷贝到已存在目录(~/)下,并且指定文件名为nothisdirectory。
好了,我们下面来分析代码。进入cp_single函数,我们将传递过来的路径拷贝到局部变量src_path和dest_path当中。因为cp_single函数可能在程序的一次运行中被调用多次,如果修改了传递过来的路径(指针)那么会导致下面的调用不正确。如果传递过来的源文件只是一个文件名,那么我们自动为其加上当前路径,这可以方便下面提取文件名。
void cp_single(char *temp_src_path,char* temp_dest_path) { struct stat buf; int len; char ch[10],filename[PATH_MAX+1],dest_dir[PATH_MAX+1]; int fdrd,fdwt,i,j,k; char src_path[PATH_MAX+1],dest_path[PATH_MAX+1]; strcpy(src_path,temp_src_path); strcpy(dest_path,temp_dest_path); for(k=0;k<\strlen(src_path);k++) { if(src_path[k]=='/') break; } char temp_path[PATH_MAX+1]="./"; if(k==strlen(src_path)) { strcat(temp_path,src_path); strcpy(src_path,temp_path); } //the following code be omited }
接着,从源文件路径中提取文件名。即提取最后一个/符号后面的字符串。
//extract the file name from src_path for(i=strlen(src_path)-1;i>\0;i--) { if(src_path[i]=='/') break; } j=k=0; for(j=i;j<\strlen(src_path);j++) { filename[k++]=src_path[j]; } filename[k]='\0';
如果目标文件路径存在,并且不含文件名,那么这时候就用到了我们上面提取的源文件名,用strcat连接即可。当然在连接之前还要检查目标文件夹是否包含/,如果包含则删除,否则会连接成这样:existeddir//filename。当不存在此目标路径,我们要检测这个路径末尾这是一个不存在的目录(上述举例2)还是一个已存在目录下不存在的文件(举例5)。我们先找到目标路径中出现的最后一个/,然后检测这个/之前的路径是否存在。比如对于路径:~/existdirectory/nothisdirectory/nothisfile。我们需要检测的是~/existdirectory/nothisdirectory/是否存在,若不存在那就显示出错信息。如果存在,那么按照完整路径:~/existdirectory/nothisdirectory/nothisfile打开文件即可。实现代码如下:
//check the if dest path has exsited if(stat(dest_path,&buf)==0) { //the dest_path exsited if(S_ISDIR(buf.st_mode)) { if(dest_path[strlen(dest_path)-1]=='/') dest_path[strlen(dest_path)-1]='\0'; strcat(dest_path,filename); } } else { //the dest_path didn't exsit for(i=strlen(dest_path)-1;i>=0;i--) { if(dest_path[i]=='/') break; } if(i>=0) { strncpy(dest_dir,dest_path,i+1); if(stat(dest_dir,&buf)==-1) { printf("my_cp:accessing:\"%s\" :it is't a directory.\n",dest_path); exit(1); } } }
下面是cp命令和本程序运行结果的比较。
gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ~/nothisdirectory/nothisfile my_cp:accessing:"/home/gues/nothisdirectory/nothisfile" :it is't a directory. gues@huangwei-desktop:~/code/shell_command$ cp tfile.c ~/nothisdirectory/nothisfile cp: 正在访问"/home/gues/nothisdirectory/nothisfile": 不是目录
完成上述功能,便进行真正的拷贝了。我们不仅要拷贝源文件的内容,还要拷贝相关文件属性,比如存取权限,用户ID,用户组ID等。下面的代码便是实现上述功能。如果你完成了my_ls,下面的代码并不困难理解,在此不在赘述。
//fistly the content which was read from srouce file will be write to dest file if((fdrd=open(src_path,O_RDONLY))==-1) { perror("open"); exit(1); } if(lseek(fdrd,0,SEEK_END)==-1) { perror("lseek"); exit(1); } if((len=lseek(fdrd,0,SEEK_CUR))==-1) { perror("lseek"); exit(1); } if(lseek(fdrd,0,SEEK_SET)==-1) { perror("lseek"); exit(1); } //open the dest file if((fdwt=open(dest_path,O_CREAT|O_TRUNC|O_RDWR,S_IRWXU))==-1) { perror("open"); exit(1); } close(fdwt); if((fdwt=open(dest_path,O_WRONLY|O_APPEND))==-1) { perror("open"); exit(1); } while(len-->\0) { //write all characters to dest file if(read(fdrd,ch,1)!=1) { perror("read"); exit(1); } if(write(fdwt,ch,1)!=1) { perror("write"); exit(1); } } //get src file's attributes if(fstat(fdrd,&buf)==-1) { perror("fstat"); exit(1); } //set the dset file's access right if(fchmod(fdwt,buf.st_mode)==-1) { perror("fchmod"); exit(1); } //set file's user id and group id if(fchown(fdwt,buf.st_uid,buf.st_gid)==-1) { perror("fchown"); exit(1); } close(fdwt); close(fdrd);
现在基本上完成了最基本的拷贝功能。如果上述代码有问题,欢迎留言指正。