今天陈老师对我们以后Linux的学习做了些指导,一针见血的指出我们学习中经常会遇到的问题。我大概记了一下:
1.从有需求做起,不要盲目看书。
2.有计划,有步骤的学习。
3.主动学习,学会分享。
暂不说其他,我觉得第1条在我们学习过程中很值得注意。前段时间做了my_ls.c和my_shell.c。虽然都调试成功,但还是极大的参考了《linuxC编程实战》这本书。因为文件系统在linux中占据着重要地位,所以还是得多通过实践来理解这部分的内容,不妨大家和我一起做my_cp,练习一下文件操作这部分的系统调用函数。
既然我们要实现cp命令,必须得了解cp命令的基本使用方法。这方面的资料网上很多,并且可以参考man手册自己试试,在此我不再详细说明。my_cp将要实现以下基本的功能:
1.将一个文件拷贝到指定路径。(目的文件可存在也可不存在)。这是my_cp中最基本的功能,事实上2,3,4功能最终都会被分解成此功能。
2.将一个目录拷贝到指定目录下,此时必须加-R或-r选项。
3.将多个目录拷贝到指定目录下,此时必须加-R或-r选项。
4.将多个文件拷贝到指定目录下。这里的源文件即包含目录文件也包含普通文件。
个人建议,最好多在终端试试以上各种情况,重点看一看cp命令是如何处理非法命令的,这对我们下面的编程有帮助。
请注意,原文件中如果存在目录文件,并且输入的命令行参数未加-r或-R选项,则会自动忽略此源目录文件,而其他文件的拷贝不受影响。如:
gues@huangwei-desktop:~/code/shell_command$ ls ls ls1 my_cp my_cp1 my_cp.c my_ls.c my_shell.c newls.c t.c tdir test tfile.c ttfile.c gues@huangwei-desktop:~/code/shell_command$ cp my_shell.c tdir/ test/ cp: 略过目录"tdir/" gues@huangwei-desktop:~/code/shell_command$ ls test/ my_shell.c
如果你完成了my_ls或者my_shell,你一定会知道,这类实现系统命令的程序在main函数中首先会对命令行参数进行解析,判断其合法性,然后再根据用户的输入(是否包含某选项或者参数个数等)来“引导”程序进入其他子函数,以便完成相应功能。想想my_ls程序,难道不是这样吗?
好了,我们开始吧!
首先进入主函数。由于本程序只支持-r或者-R选项,因此如果用户输入选项,我们来判断是否合法。如果合法让其标志param_r为真。由于-r或-R选项位置不一定限制在cp命令之后(事实上放在命令行参数末尾也可以),所以用index_r来保存其下标。实现代码如下:(因为插件原因,下面的代码在出现<或>符号的后面均加入了 \ )
char dest_path[PATH_MAX+1]; char src_path[256][PATH_MAX+1]; int i,k,num,src_num,index_r; struct stat buf; //check the legality of the options,only -r or -R for(i=1;i<\argc;i++) { if(argv[i][0]=='-') { if((!strcmp(argv[i],"-r")||!strcmp(argv[i],"-R"))) { param_r=1; index_r=i; } else { printf("my_cp:invalid options: %s\n",argv[i]); exit(1); } } }
接下来计算命令行参数中参数的个数num以及源文件的个数src_num,这两个变量备用。如果num小于2,肯定不合法。上述要求都合法后,提取目标文件的路径,上面已经说过因为R和r选项后可以出现在参数末尾,因此得多加一次判断,并不能直接认为argv[argc-1]就是目标文件路径。
if(param_r) { num=argc-1-1; src_num=num-1; } else { num=argc-1; src_num=num-1; } if(num<\2) { printf("my_cp: [option] (source) (dest)\n"); exit(1); } //extract the dest path if(index_r!=argc-1) { strcpy(dest_path,argv[argc-1]); } else { strcpy(dest_path,argv[argc-2]); }
接下来提取源文件的路径,由于源文件可以有多个,因此我们用一个字符串数组来存储源文件路径。
//extract the src path k=0; for(i=1;i<\argc-1;i++) { if(i==index_r&¶m_r) continue; else { strcpy(src_path[k++],argv[i]); } }
以上工作都做好后,我们可以”分流“了,即根据不同要求进入不同的子函数。我的“分流”原则是根据源文件数src_num。当其大于1时,说明源文件是多个。首先判断此目的文件是否存在,如果存在那么接着判断它是否为一个目录文件,因为多个源文件不可能拷贝到一个非目录文件当中。
确定了目的文件是一个目录后,我们要将这个目录下的所有文件依次调用子函数进行“分流”。
这里有两个重要的子函数,cp_single函数针对这样的情况:将单个文件拷贝到另一文件。(功能1)。cp_directory函数针对这样的情况:将单个文件夹拷贝到指定目录(功能2)。进入这两个子函数的依据就是,如果源文件是一个文件进入前者,源文件是目录进入后者。而我们用for循环将上述两函数有效的结合起来,就可以实现上述功能的3,4。
if(src_num>\1) { if(stat(dest_path,&buf)==-1) { printf("my_cp: \"%s\" is not a directory.\n",dest_path); exit(1); } //the dest path is valid if(S_ISDIR(buf.st_mode)) { strcpy(temp_dest_path,dest_path); //the dest path is directory for(i=0;i<\src_num;i++) { if(stat(src_path[i],&buf)==-1) { printf("my_cp: can't get file status of \"%s\" : no this file or directory.\n",src_path[i]); continue; } //the src_path exist if(!S_ISDIR(buf.st_mode)) { cp_single(src_path[i],dest_path); } else if(param_r) { cp_directory(src_path[i],dest_path); } else { printf("my_cp: skip the directory: \"%s\".\n",src_path[i]); } strcpy(dest_path,temp_dest_path); } } else { printf("my_cp: \"%s\" is not a directory.\n",dest_path); exit(1); } } else { //The code here be omited }
我们接下来主要来看源文件数目为1的情况。其实这属于cp命令最基本的功能,cp_directory函数也会调用这个函数。
源文件存在的时候,如果源文件是一个目录并且有r或R选项,那么进入cp_directory函数(至于目标文件是否为目录,进入此函数可以判断)。如果源文件不是目录文件,那么进入cp_single函数即可。
if(src_num>\1) { //The code here be omited } else { //only one src path if(stat(src_path[0],&buf)==-1) { printf("my_cp: can't get file status of \"%s\" : no this file or directory.\n",src_path[0]); exit(1); } if(S_ISDIR(buf.st_mode)) { if(param_r) { cp_directory(src_path[0],dest_path); exit(0); } else { printf("my_cp: skip the directory: \"%s\".\n",src_path[0]); exit(1); } } else { cp_single(src_path[0],dest_path); } }
好了,主函数部分基本就是这样,这里主要说明整个主函数的整个流程。另外我们也应该不走寻常路,去尝试一些错误情况,这有利于我们编程。