Posts Tagged ‘命令’

实现chmod命令

3 8 月, 2010

《LinuxC编程实战》一书中在介绍文件操作部分的时候,一开始就给我们举了个实现my_chmod的小程序。它只支持纯数字的权限方式,不支持参数是:权限范围+/-/=权限设置这样的形式。因此我们要实现的my_chmod要能够支持这种形式。具体的功能如下:

my_chmod能够依照权限设置改变文件的权限。权限既可以是代表性的数字,也可以是符号(权限范围+/-/=权限设置)。并且多个符号之间可以用逗号隔开。本文中的my_chmod只支持每个符号中只有一个+/-/=的情况。比如:./my_chmod u+g+o filename 这样的形式本程序并不支持(尽管chmod支持)。

鉴于本博客前文中已经详细讲解了my_cp,因此本文重点说明如何将符号权限设置的方式转化成数字形式。至于符号权限设置的合法性判断等等,无非是利用C语言基础知识来判断,当然这要建立在熟悉chmod命令的基础上。

好了,我们开始吧!

将符号权限转换成数字权限是通过check_option和get_mode两个函数完成的。check_option函数首先判断符号权限的位置合法性。由于多个符号参数可以用逗号隔开,因此在此函数中依次调用get_mode函数,最后将得到最终的mode。比如:./my_chmod u+rw,go+w test.c;那么在check_option函数中分别对两组符号参数(u+rw和go+w)调用get_mode函数。两次调用使得mode为:rw- -w- -w-(622)。但是这时的mode并不是文件最终的mode,还要看文件在执行my_chmod之前的权限,如果test.c 本身权限为644,那么my_chmod执行后,文件权限为666。

当这只是+情况,对于-和=的情况,我们下面会详细说明。

我们再来看get_mode函数的原型:

void get_mode(int u,int g,int o,int r,int w,int x,int sign,mode_t* mode);

这个函数一开始看好像很复杂,因为有众多的参数,其实一点都不复杂。参数u,g,o分别对应用户,用户组,其他成员;而r,w,x当然是可读,可写,可执行;sign根据值1,2,3分别对应的是+,-,=;末尾的mode是将源文件的mode传过来,因为要保存其值,所以传来的是指针。这些变量如果为1那么说明在参数中出现过,否则没有出现。这些变量的赋值在check_option函数中完成。

在get_mode函数中,用一个switch语句来区分+,-,=的不同功能。

	switch(sign)
	{
		case 1://+
			{
				//the code was omited
			}
			break;
		case 2://-
			{
				//the code was omited
			}
			break;
		case 3://=
			{
				//the code was omited
			}
			break;
		default:
			printf("sign error\n");
			exit(1);
	}

首先来看case1,它对应的是+。这里的+比较好理解,就是在文件原来的权限基础上再加上命令中的权限。这里u,g,o三个组分别对应三个if语句,再针对具体一个组,查看这个组具有r,w,x权限的那几个(再分别对应三个if语句)。比如:./my_chmod ug+rwx,o+r test.c;首先他会进入第一个if(u)语句,又因为参数中又出现了rwx,因此if(u)中的三个if语句都会被执行。接着,又会以类似的过程去执行if(g)。这样就完成了一次get_mode的调用,至于o+r,它属于第二次调用get_mode函数。

既然参数中出现了rwx,如何让mode具有对应的rwx权限?如何体现在具体的代码上?我们可以采用或运算,但是注意一定是文件本身的mode分别与相应的权限去或。这里采用了一系列的宏,极大的方便了代码的编写。如果你对此处采用或运算感到困惑,那么不妨手动去试试,即可明白。具体的实现代码如下:

		case 1:
			{
				if(u)
				{
					if(r)
						*mode|=S_IRUSR;
					if(w)
						*mode|=S_IWUSR;
					if(x)
						*mode|=S_IXUSR;
				}
				if(g)
				{
					if(r)
						*mode|=S_IRGRP;
					if(w)
						*mode|=S_IWGRP;
					if(x)
						*mode|=S_IXGRP;
				}
				if(o)
				{
					if(r)
						*mode|=S_IROTH;
					if(w)
						*mode|=S_IWOTH;
					if(x)
						*mode|=S_IXOTH;
				}
			}
			break;

接着看case2,对应的符号是-。它与上面的+刚好相反:在文件原来的权限上减去命令中所输入的权限。这个动作在代码中也很好体现,用异或即可。因为1^1=0,1^0=1,这样使得与相应宏异或的那个权限为0。

但是这里还应该注意一个问题,刚才我们所分析的是当这个权限存在的时候,我们可以用异或来做相应的“减”,如果命令中要减去的权限,文件本身就不具有,那么异或后又会使得文件具有了这个权限!因此我们应该在小if语句的判断处加上一个与运算:判断此时文件的权限是否含有要减去的权限。至于这里为何用异或,与运算,请自己举例,手动运算即可明白。

		case 2:
			{
				if(u)
				{
					if(r&&(*mode&S_IRUSR))
						*mode^=S_IRUSR;
					if(w&&(*mode&S_IWUSR))
						*mode^=S_IWUSR;
					if(x&&(*mode&S_IXUSR))
						*mode^=S_IXUSR;
				}
				if(g)
				{
					if(r&&(*mode&S_IRGRP))
						*mode^=S_IRGRP;
					if(w&&(*mode&S_IWGRP))
						*mode^=S_IWGRP;
					if(x&&(*mode&S_IXGRP))
						*mode^=S_IXGRP;
				}
				if(x)
				{
					if(r&&(*mode&S_IROTH))
						*mode^=S_IROTH;
					if(w&&(*mode&S_IWOTH))
						*mode^=S_IWOTH;
					if(x&&(*mode&S_IXOTH))
						*mode^=S_IXOTH;
				}

			}
			break;

最后是case3的情况。与上述两种情况不同的是,=并不是在文件原有的权限上增加或减少某个权限,而是对命令中出现的某个组的权限进行赋值。也就是说它会覆盖原组中的权限。比如;已知test.c文件的权限为–wx-w-r–(324)。那么./my_chmod ug=r test.c;后,他的权限为:-r–r–r–(444)。可以看到他将ug两组的原权限覆盖,而o组未在命令中出现,因此不受影响。

在进入某个组时候,比如进入U组时,我们会这样处理mode:*mode&=(S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH);为何要这么做?就如我们刚才分析的那样,我们要为某个组赋值,必须覆盖掉这个组原有权限,因此我们先得到另外两个组所有权限或的结果(在这个或的结果中,另外两个组所有权限对应的那个位均为1),和文件的原权限去与,便可以使得U组权限为0,而其他组的权限不受影响。这其实根据:1&1=1,1&0=0。

		case 3:
			{
				if(u)
				{
					*mode&=(S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH);
					if(r)
						*mode|=S_IRUSR;
					if(w)
						*mode|=S_IWUSR;
					if(x)
						*mode|=S_IXUSR;
				}
				if(g)
				{
					*mode&=(S_IRUSR|S_IWUSR|S_IXUSR|S_IROTH|S_IWOTH|S_IXOTH);
					if(r)
						*mode|=S_IRGRP;
					if(w)
						*mode|=S_IWGRP;
					if(x)
						*mode|=S_IXGRP;
				}
				if(o)
				{
					*mode&=(S_IRGRP|S_IWGRP|S_IXGRP|S_IRUSR|S_IWUSR|S_IXUSR);
					if(r)
						*mode|=S_IROTH;
					if(w)
						*mode|=S_IWOTH;
					if(x)
						*mode|=S_IXOTH;
				}
			}
			break;
		default:
			printf("sign error\n");
			exit(1);

好了,以上便是如何将符号权限转化为数字权限。最终的mode会在check_option函数中得到,这个mode会在chmod函数中用到。

实现cp命令(4)

28 7 月, 2010

现在我们已经实现了my_cp。那么我们来运行一下下面的命令吧:

gues@huangwei-desktop:~/code/shell_command$ ./my_cp -r dir/ newdir/ -r
my_cp: can't get file status of "-r" : no this file or directory.

问题出来了,我们并没有考虑到多个重复选项的情况,因此上面命令把末尾的-r当成了文件名。如果用cp执行上面的指令,那么是成功的,因为多个重复选项在cp命令下就相当于一个。因此我们下面来修改代码。
你可以让检查选项处的param_r=1;改为param+=1;然后再加入下面的代码,当出现这种情况的时候,让其出错。

if(param_r>1)
{
printf("my_cp:invalid options.\n");
exit(1);
}

为了完美一些,我们可以这样做。首先我们将原来index_r改成数组index,记录出现-r的位置。我们可以让这个数组全部初始化为0,如果参数中,第i个参数为-r或者-R,那么就将index[i]赋值为i。并且这时候的param_r就要累计出现合法(对于本程序,合法选项就是-r或-R了)选项的个数。

	//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[i]=i;
			}
			else
			{
				printf("my_cp:invalid options: %s\n",argv[i]);
				exit(1);
			}
		}
	}

那么在计算源文件数目的时候也相应的就有了小改动。

	if(param_r)
	{
		num=argc-1-param_r;
		src_num=num-1;
	}
	else
	{
		num=argc-1;
		src_num=num-1;
	}
	if(num<\2)
	{
		printf("my_cp: [option]  \n");
		exit(1);
	}

提取目标文件的时候,就有些小麻烦,但是也是可以解决的。我们从命令行参数末尾开始,找到那个不是选项的那个参数,因为目标文件总是靠近末尾的。比如:./my_cp dir/ newdir/ -r -r

	//extract the dest path
	for(i=argc-1;i>\0;i--)
	{
		if(!strcmp(argv[i],"-r")||!strcmp(argv[i],"-R"))
			continue;
		else
			break;
	}
	if(i==argc-1)
	{
		strcpy(dest_path,argv[i]);
	}
	else
	{
		strcpy(dest_path,argv[i]);
	}

好了,改完上面的代码,下面就和以前的一样了。这样就可以避免开始的时候我们所举的例子的错误。

实现cp命令–文件夹的拷贝

27 7 月, 2010

刚刚完成了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函数的实现过程,需要源码的同学留下邮箱即可。如果发现了不妥之处,欢迎指正。

实现cp命令–单个文件的拷贝

26 7 月, 2010

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

现在基本上完成了最基本的拷贝功能。如果上述代码有问题,欢迎留言指正。

实现cp命令–从主函数开始

25 7 月, 2010

今天陈老师对我们以后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&&param_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);
		}
	}

好了,主函数部分基本就是这样,这里主要说明整个主函数的整个流程。另外我们也应该不走寻常路,去尝试一些错误情况,这有利于我们编程。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key