昨天我们主要从主函数入手,对命令行参数进行合法性检测,并引导主程序进入相应的子函数。今天我们要实现一个最基本的复制功能,将一个源文件复制到指定路径。之所以说路径,是因为目的文件可能是一个存在的文件,也可能是一个不存在的文件或者是一个目录(不存在的目录会出错)。在我们详细分析代码前,先看看我做的这个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);
现在基本上完成了最基本的拷贝功能。如果上述代码有问题,欢迎留言指正。