ubuntu的功能非常强大,但是有时候还是会用到XP下的某些软件。那么就在ubuntu下安装个虚拟XP吧。
下载好文件(这里只针对.run格式),解压至主文件夹运行:
sudo sh VirtualBox-3.2.4-62431-Linux_x86.run
安装完毕后,也许你会觉得原始屏幕太小,那么安装增强包。在虚拟窗口点击:设备–安装增强包。然后打开虚拟XP的光驱,点击那里的VBoxWindowsAdditions.exe文件就可安装。重启XP以后,你就可以随意拖动虚拟窗口大小了。
ubuntu的功能非常强大,但是有时候还是会用到XP下的某些软件。那么就在ubuntu下安装个虚拟XP吧。
下载好文件(这里只针对.run格式),解压至主文件夹运行:
sudo sh VirtualBox-3.2.4-62431-Linux_x86.run
安装完毕后,也许你会觉得原始屏幕太小,那么安装增强包。在虚拟窗口点击:设备–安装增强包。然后打开虚拟XP的光驱,点击那里的VBoxWindowsAdditions.exe文件就可安装。重启XP以后,你就可以随意拖动虚拟窗口大小了。
刚刚完成了my_cp的另一个功能:将一个目录拷贝到指定目录。加上昨天实现的将一个文件拷贝到指定地址下,现在已经完成了我们实现前所定下的要求。也许你会有疑问,那多个文件的拷贝的实现呢?我面前面已经说过,只要完成上述两个功能,并且你在主函数中“分流”正确,那么只要在合适的位置调用这两个函数即可,具体办法我们下面会讨论。
在详解如何实现将一个目录拷贝到指定目录(cp_directory函数)之前,我们首先应该弄明白下面的内容:
1.如果目标目录中的最低级目录不存在,则会新建这个目录,并把源目录中的所有文件拷贝到此新建的目录下。比如cp -r dir ./newdir。我们可以看到./newdir(这个路径中最低级的目录是newdir)在cp前是不存在的,但是cp后新建了这个目录,并且将dir中的所有文件拷贝到这个新建的目录下。
gues@huangwei-desktop:~/code/shell_command$ ls cptest ls my_cp my_cp.c my_ls_plus my_shell.c nothisdirectory tdir test dir ls1 my_cp1 my_ls.c my_ls_plus.c newdirectory nothisfile tdirmy_ls.c ttfile.c gues@huangwei-desktop:~/code/shell_command$ ls dir ed my_cp1 test ttfile.c gues@huangwei-desktop:~/code/shell_command$ cp -r dir ./newdir gues@huangwei-desktop:~/code/shell_command$ ls newdir ed my_cp1 test ttfile.c
2.如果最低级的目标目录存在,则会将源目录(当然也包含源目录下的所有文件)拷贝到这个目标目录。我们仍执行上面那个命令:cp -r dir ./newdir。但是这次结果是不一样的,由于1的操作,newdir目录已经存在,这次cp后将dir目录拷贝到了已存在的newdir目录下(即./newdir/dir/)。
gues@huangwei-desktop:~/code/shell_command$ ls newdir ed my_cp1 test ttfile.c gues@huangwei-desktop:~/code/shell_command$ cp ./dir -r ./newdir gues@huangwei-desktop:~/code/shell_command$ ls newdir dir ed my_cp1 test ttfile.c
如果我说的还不够明白,你也可以自己亲自验证一下cp命令。
下面我们来详解。还是先保留传递过来的路径。然后如果源文件夹不包含/,则添加。
void cp_directory(char* original_src_path,char* original_dest_path) { struct stat buf; DIR *dir; struct dirent *ptr; char path[PATH_MAX+1]; char src_path[PATH_MAX+1],dest_path[PATH_MAX+1]; strcpy(src_path,original_src_path); strcpy(dest_path,original_dest_path); if(src_path[strlen(src_path)-1]!='/') { strncat(src_path,"/",1); } //the following code be omited }
如果目标目录中最低级的目录不存在,则创建它。如果次低级目录也不存在,则在创建的时候就发生错误。如果目标目录存在,并且是目录文件,那么就如同上面举例2中所述,我们需要将源路径中最低级的目录拷贝到目标目录中。这里面设计到提提取源路径最低级的目录,以及将其连接在目标目录后等。这些都不难理解。注意当完成目标路径的拼接后,如果这个目录本身就存在,那么我们将其删除,创建新目录。
if(stat(dest_path,&buf)==-1) { //create a directory which name is dest_path stat(src_path,&buf); if(mkdir(dest_path,buf.st_mode)==-1) { printf("my_cp:create the directory \"%s\" error.\n",dest_path); return ; } } else { //exist if(!S_ISDIR(buf.st_mode)) { printf("my_cp:the directory \"%s\" can't cover the no-directory \"%s\".\n",src_path,dest_path); return ; } else { if(dest_path[strlen(dest_path)-1]!='/') { strncat(dest_path,"/",1); } //extract the lowest directory int i,k=0; char lowestdir[PATH_MAX+1]; for(i=strlen(src_path)-1-1;i>\0;i--) { if(src_path[i]=='/') { i=i+1; break; } } for(;i<\strlen(src_path);i++) { lowestdir[k++]=src_path[i]; } strncat(dest_path,lowestdir,strlen(lowestdir)); struct stat temp_buf; char temp_path[PATH_MAX+1]="rm -rf "; if(stat(dest_path,&temp_buf)==0) { strcat(temp_path,dest_path); system(temp_path); } if(mkdir(dest_path,buf.st_mode)==-1) { printf("my_cp:create the directory \"%s\" error.\n",dest_path); return ; } } }
接着我们打开源目录,读取其下的所有文件名。这个方法在my_ls的时候就已经使用过。我们将这些文件名与目的路径拼接后,检查他们是否是目录文件。如果是普通文件那么就调用cp_single函数,否则调用cp_directory函数。
if((dir=opendir(src_path))==NULL) { printf("my_cp:open the srouce path \"%s\" error.\n",src_path); return ; } char temp_dest_path[PATH_MAX+1]; strcpy(temp_dest_path,dest_path); while((ptr=readdir(dir))!=NULL) { if(!strcmp(ptr->\d_name,".")) continue; if(!strcmp(ptr->\d_name,"..")) continue; strcpy(path,src_path); strcat(path,ptr->\d_name); if(stat(path,&buf)==-1) { printf("my_cp:open the file \"%s\" error.\n",path); return ; } strcpy(dest_path,temp_dest_path); //get the right dest_path if(S_ISDIR(buf.st_mode)) { cp_directory(path,dest_path); } else { cp_single(path,dest_path); } }
其实这是一个递归的过程,对于递归,最重要的是能返回到调用函数。对于任何目录,最终要么这个目录是空的,要么全是普通文件,所以肯定能返回到上一级函数中,不会无限的去嵌套。
以上就是my_cp函数的实现过程,需要源码的同学留下邮箱即可。如果发现了不妥之处,欢迎指正。
昨天我们主要从主函数入手,对命令行参数进行合法性检测,并引导主程序进入相应的子函数。今天我们要实现一个最基本的复制功能,将一个源文件复制到指定路径。之所以说路径,是因为目的文件可能是一个存在的文件,也可能是一个不存在的文件或者是一个目录(不存在的目录会出错)。在我们详细分析代码前,先看看我做的这个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);
现在基本上完成了最基本的拷贝功能。如果上述代码有问题,欢迎留言指正。
今天陈老师对我们以后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); } }
好了,主函数部分基本就是这样,这里主要说明整个主函数的整个流程。另外我们也应该不走寻常路,去尝试一些错误情况,这有利于我们编程。
今天在看结构体和共用体部分的时候,遇到了一个新名词“内存对齐”。先引入问题吧。如下:
struct student { char name[20]; int age; char sex; char phone[15]; }; struct student p1;
sizeof(p1)=?
这个很简单得出答案,即20+4+1+15=40Byte。如果将phone[15]改为phone[16],结果是44。难道不是41吗?
这里便要引入内存对齐的概念。内存为了提高访问效率,便规定以结构体中最大的基本单位长度为对齐标准。即实际分配的内存大小是对齐标准的整数倍(必要条件)。在上面的结构体中,最大的基本类型是int。因此以4Byte为对其标准。所以实际内存大小应该为4的整数倍,即为44Byte.
也许你有疑惑:name[20]不是要20Byte吗,为什么以4Byte为对齐标准?请注意这里的基本类型。name[20]是一个字符串数组,数组属于复合的数据类型。复合的数据类型还有结构体,共用体。基本的数据类型是整型,字符型,浮点型。如果你还有不解,那么看下题:
struct score { float english; float math; float computer; }; struct student { char name[10]; int age; char sex; struct score st_score; };
在student结构体中含有数据类型为struct score这样的变量。struct score的大小为12Byte,也是struct student结构体中最大的数据类型,但是我们的对齐标准是4Byte,就如我们刚说的那样,内存的对齐标准是取结构体中最大的基本数据类型,这里我们取sizeof(int)。
再看下面的问题:
union data1 { int i; char c; char str[9]; }; struct data2 { int i; char c; char str[9]; };
sizeof(struct data1)=? sizeof(struct data2)=?
对于前者,由于共用体的存储大小由最大的成员来决定,因此上题中共用体的存储大小为9Byte,考虑到内存对齐,它以sizeof(int)=4Byte来对齐,因此实际内存分配大小是12Byte。对于后者,存储大小是4+1+9=14Byte,这个结构体以4对齐,因此实际分配大小为16Byte。
现在你应该对于共用体和结构体的内存对齐有所了解了。那么再接着往下看:
struct data { char c; double d; char ch; }
sizeof(struct data)=?
有了上面的理解,10肯定不会是答案,那么会是16吧。也错。正确答案是24。你可能会感到莫名其妙:按照上面所说的方法,以8为对齐标准,那么10不是8的倍数,那就是16呀。为什么呢?请你耐心看下面。
首先我们知道结构体变量是分配的连续内存空间。d毫无疑问是分配8Byte,对于d“前面”的c,按照对齐标准我们应该分配8Byte,而d后面的ch,按照对齐标准也应该分配8Byte。因此结果是24。
如果这样:
struct data { double d; char c; char ch; }
sizeof(struct data)=16。
因为内存没必要分别为c和ch分配8Byte来进行对齐,将它们放在一个8byte里就可以实现内存对齐。上面在引入内存对齐概念的时候,我用括号特别注释必要条件便是说明此处内容。