日志标签 ‘文件操作’

实现cp命令(4)

2010年7月28日

现在我们已经实现了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命令–文件夹的拷贝

2010年7月27日

刚刚完成了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命令–单个文件的拷贝

2010年7月26日

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

2010年7月25日

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

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

my_shell.c代码分析

2010年7月16日

大三的时候,那时候因为课程需要,我们调试过my_shell.c程序。当时只是对整个程序的结构了解,在涉及linux系统调用函数的时候就不是很清楚”为什么会这样做?”。my_shell.c程序核心函数便是do_cmd函数,此函数是整个程序的核心,负责对用户输入的命令进行执行。

本文便是分析do_cmd函数,以达到对my_shell.c程序有深入的理解。此函数会涉及文件操作中的相关系统调用函数,如果你和我一样调试并分析过前面所说的my_ls.c程序,那么接下来所述的内容对你来说并不困难。

我们按照如下方式定义do_cmd函数:

void do_cmd(int argcount, char arglist[100][256]);

其中,argcount是统计用户输入命令中选项的个数,arglist数组存储每个选项。比如输入:ls -l /那么此时argcount=3,而arglist[0],arglist[1],arglist[2]中分别存储的是:ls,-l,/。

首先此函数会检查用户输入的命令行参数中是否存在后台运行符。利用for循环,对arglist数组中的每个参数与”&”进行比较。由于arglist中的元素是字符串,那么应该选用strcmp函数,而不是利用==来判断。此外即便找到了后台运行符,还应该检查是否”&”位于参数末尾。如果i==argcount-1为假,那么便出错。实现代码如下:

//check if the command include character of running in the background
	for(i=0;i<\argcount;i++)
	{
		if(strncmp(arg[i],"&",1)==0)
		{
			if(i==argcount-1)
			{
				background=1;
				arg[argcount-1]=NULL;
				break;
			}
			else
			{
				printf("wrong command\n");
				return;
			}
		}
	}//for

接下来,检测命令行参数是否含有>,<,|符号,检测方法与上出代码原理类似。由于本程序要求用户输入的命令行参数只能包含上述符号其中之一,所以当flag大于一时,错误。参数中符号合法,接下来提取文件名,存于字符串file中。程序中分别有三个if语句依次针对出现>,<,|时的情况。我们只要分析出现管道符号的情况。

在分析之前首先弄起出管道符号的作用。比如下面命令:

edsionte@edsionte-laptop:~$ ls -l / | wc -c
1513

管道符号前后分别是两个shell命令,前命令的输出作为后命令的输入。

如果检测出命令行中含有管道符号,即how=have_pipe。然后将命令行中两个shell命令分离出来,前命令依然存于arg数组中,而后命令存于argnext数组中。实现命令如下:

if(how==have_pipe)
	{
		for(i=0;arg[i]!=NULL;i++)
		{
			if(strncmp(arg[i],"|",1)==0)
			{
				arg[i]=NULL;
				int j;
				for(j=i+1;arg[j]!=NULL;j++)
				{
					argnext[j-(i+1)]=arg[j];
				}
				argnext[j-(i+1)]=arg[j];
				break;
			}
		}
	}

接下来就要用到进程控制的内容了。我们fork一个新的进程,让它去执行命令行参数中的命令。当命令行中不包含管道符号时,只需fork一个进程,比如ls -l / > a;我们只需fork一个新进程,让其(if(pid==0))执行ls命令即可;否则,我们必须fork两个信进程,前一个进程执行|前的命令,另外一个进程执行|后的命令。至于另个进程如何”通信”,如何让前一个命令的输出称为后者的输入?不着急,后面会详细说明。

1.如果所输命令中不包含<,>,|,how==normal(0);那么调用exe族函数即可让子进程去执行所输的命令。如下:

			if(pid==0)
			{
				if(!(find_command(arg[0])))
				{
					printf("%s: command not found\n",arg[0]);
					exit(0);
				}
				execvp(arg[0],arg);
				exit(0);
			}

find_command函数是在指定目录下去查找命令arg[0]对应的可执行程序。

2.如果命令行中包括输出重定向符号>,how==out_redirect(1);那么需在上述代码中exe函数前添加下面的代码:

				fd=open(file,O_RDWR|O_CREAT|O_TRUNC,0644);
				dup2(fd,1);//make the file as a standard output stream
				execvp(arg[0],arg);//execute the command

前面我们已经提到file中存储的是输出重定向(或输入重定向)的文件名,因此首先应以可读可写方式打开一个文件,并且档要打开的文件存在时,会清空源文件数据,这些我们在前面的博文中都有解释。我们知道标准输出输入的文件描述符为:1,0,利用dup2函数就可以将fd,1同时指向file文件,实现将标准输出重定向到我们刚已打开的文件。

3.如果命令行中包含输入重定向符号<,how==out_redirect(2);那么添加如下代码:

				fd=open(file,O_RDONLY);
				dup2(fd,0);//make the file as a standard output stream
				execvp(arg[0],arg);//execute the command

这里只涉及到读取文件,因此以只读方式打开文件。

4.如果命令行中包含管道符号,how==have_pipe(3);如同我们在上面所述的,子进程还需再fork一个进程(暂且叫它子子进程,child_child_process)。首先让子子进程去执行管道符号前面的shell命令,并将输出结果暂存于tempfile文件中;让子进程去执行管道符号后面的shell命令,并读取tempfile文件,将其作为第二个命令的输入。这样,子进程和子子进程实现了管道”通信”。具体代码实现其实是上述2和3的结合,具体如下:

			if(pid==0)//child_process
			{
				int pid2;
				int status2;
				int fd2;

				if((pid2=fork())<\0)
				{
					printf("fork2 error\n");
					return;
				}
				else if(pid2==0)//child_child_process
				{
					if(!(find_command(arg[0])))
					{
						printf("%s: command not found\n",arg[0]);
						exit(0);
					}
					fd2=open("/tmp/tempfile",O_WRONLY|O_CREAT|O_TRUNC,0644);
					dup2(fd2,1);
					execvp(arg[0],arg);
					exit(0);
				}

				if(waitpid(pid2,&status2,0)==-1)//waiting for child_child_process
				{
					printf("wait for child_child process error\n");
				}

				if(!(find_command(argnext[0])))
				{
					printf("%s:command not found\n",argnext[0]);
					exit(0);
				}

				fd2=open("/tmp/tempfile",O_RDONLY);
				dup2(fd2,0);
				execvp(argnext[0],argnext);

				if(remove("/tmp/tempfile"))
				{
					printf("remove error\n");
				}
				exit(0);
			}

上出1~4处代码需要一个switch语句来组织,根据how参数来选择相应代码,进行执行。另外,由于上述代码中设计两次fork进程,因此要弄清楚父子进程的关系,其次还需了解代码4中waitpid函数的作用。由于子子进程和子进程同组,因此子进程等待子子进程结束。

同样,父进程需要等待子进程执行完毕。但是如果命令行中包含后台运行父&,那么附近成可直接返回,不用等待子进程。这些功能的实现代码如下:

	if(background==1)
	{
		printf("process id %d\n",pid);
	}

	if(waitpid(pid,&status,0)==-1)
	{
		printf("wait for child process error\n");
	}

至此,我们分析完了do_cmd函数的功能,完整代码可以参考这里。

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