Linux C编程实战——第七章 进程控制_项目实现_自写shell

自写shell

基本介绍:该shell命令目前实现了cd命令tab补全history历史,和外部命令


基本实现原理 : 根据输入命令,解析参数,然后再fork一个进程,在进程中利用execvp运行外部命令,其cd命令根据chdir()函数实现,tab补全和历史利用readdir()函数及库实现。


这是大概思想,下面直接上代码(已经加上详细注释):

注意:在运行该程序前要安装readline对应的库,安装流程如下:

http://blog.csdn.net/chudongfang2015/article/details/52068026



myshell.c:

/*************************************************************************
	> File Name: meshell.c
	> Author:chudongfang 
	> Mail:1149669942@qq.com 
	> Created Time: 2016年07月28日 星期四 09时31分43秒
 ************************************************************************/
#include <pwd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/resource.h>	
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <stdlib.h>
#define  MAX_SIZE 256

#define normal          0  //正常命令
#define out_redirect    1  //重定向输出
#define in_redirect     2  //重定向输入
#define have_pipe       3  //管道命令

void do_cmd(int argcout,char arglist[100][256],int his_count,char history[][256]);
int find_command(char *command);           
void explain_input(char *buf,int *argcout,char arglist[100][256]);
void get_input(char *buf,char *path,int *his_count,char history[][256]);


struct passwd *pw;  //用户信息结构体



int main(int argc,char *argv[])
{

	int i;
    
    char history[256][256];//存储历史
    int his_count=0;       //历史的数量
	
    int argcout = 0;        //命令计数器
	char arglist[100][256]; //储存命令
	char **arg = NULL;      
	char *buf  = NULL;      //临时字符数组
	char path [MAX_SIZE];   //进程所在路径
    memset(history,0,sizeof(history));

	buf = (char *)malloc(256);   
	
	if(buf == NULL){
		perror("malloc failed");
		exit(-1);
	}
	


	while(1){
		memset(buf,0,256);             
		memset(path,0,MAX_SIZE);  
		if (getcwd( path, MAX_SIZE) == NULL)    //得到当前路径
		{
    		printf("read path error!\n");
    		exit(-1);
		}
		//获取当前目录绝对路径,即去掉程序名

		get_input(buf,path,&his_count,history);	                 //输出路径提示,并得到输入命令   

		while(strlen(buf) == 0)                  
		{ 
		 	get_input(buf,path,&his_count,history);                //无输入,继续接受输入		
		}	
		
		if( strcmp(buf,"exit\n") == 0 || strcmp(buf,"logout\n") == 0)  //exit 和 logout 退出shell
			break;
		
		for(i=0;i<100;i++)                   //初始化命令         
		{
			arglist[i][0]='\0';           
		}

		argcout = 0;                         //命令个数为0
		explain_input(buf,&argcout,arglist); //把输入解析成命令 

		do_cmd(argcout,arglist,his_count,history);             //对命令进行分析,操作
	}

	if(buf != NULL){
		free(buf);
		buf = NULL;
	}

    exit(0);
}



/****************************************
 * 函数功能: 实现提示,接受输入,tab 补全,记录输入历史
 * 
 * 函数参数: 
 *  参数1 :输入
 *  参数2 :解析后的命令
 ***************************************************/

void get_input(char *buf,char *path,int *his_count,char history[][256])
{

    int count=0,i,len;
	uid_t  uid;
    char  *temp;
  	char  str[500];
    uid = getuid ();  //得到uid   
    pw = getpwuid (uid);    //得到用户信息
    if (!pw)  
    {  
        printf ("Couldn't find out about user %d./n", (int)uid);  
        return ;  
    }  
	sprintf(str,"\033[;31m %s@\033[0m\033[;31mmyshell\033[0m\033[;32m %s\033[0m\033[;33m$\033[0m",pw -> pw_name,path);    //加上颜色,合成提示语句str
    temp = (char* )malloc(sizeof(256));  //malloc 申请空间,用于readline 注意: readline 返回值只能用malloc申请的指针接受
    memset(temp,0,sizeof(temp));  //初始化空间
    temp = readline(str);         //调用readline库函数 实现补全   ,调用方法在另一篇博客
    strcpy(buf, temp);            //把接受到的存入到buf里面

    //历史记录,如果输入的全部是空格或是换行,则不存储 
    len = strlen(buf);           
    for(i=0;i < len; i++)
        if(buf[i] == ' '||buf[i] == '\n') 
            count++;
    
    if(count != len)
    {
        //如果输入的命令与前一个重复,则不把其记录到history里面
        if(*his_count != 0){
            if(strcmp(history[*his_count-1],buf) != 0){
                strcpy(history[*his_count],buf);
                *his_count = *his_count + 1;
                add_history(buf);
            }
        }
        else
        {
            strcpy(history[*his_count],buf);
            *his_count = *his_count + 1;
            add_history(buf);
        }
    }
    
    free(temp);
}

/*************************************************
 * 函数功能 :根据输入的内容解析出命令
 * 函数参数:
 *  1 :输入的内容
 *  2 :命令个数
 *  3 :存储命令数组
 *************************************************/

void explain_input(char *buf,int *argcout,char arglist[100][256])
{
	int number = 0 ;
	char *p = buf;
	char *q = buf;
	while(1){
		if(p[0] == '\n' || p[0] == '\0')
			break;
		if(p[0] == ' ')
			p++;
		else{
			q=p;
			number = 0;
			while( (q[0] != ' ') && q[0] != '\0' &&q[0] != '\n'){
				number++;//代表单个命令的长度
				q++;
			}
			strncpy(arglist[*argcout],p,number+1);   //把单个命令解析出来
			arglist[*argcout][number] = '\0';         //最后加上结尾
			*argcout = *argcout + 1; 
			p=q;
		}
	}
}

/*****************************************************
 * 函数功能 :命令分类,并执行命令
 * 参数:
 *  1:命令个数
 *  2:存储命令数组
 * **************************************************/
void do_cmd(int argcout,char arglist[100][256],int his_count,char history[][256])
{
	int flag=0;
	int how =0;
	int backgroud = 0;
	int status;
	int i;
	int fd;
	char * arg[argcout + 4];
	char * argnext[argcout + 4];
	char *file;
	char home[256];
	pid_t pid;
	

	for(i=0;i<argcout;i++)
		arg[i]=(char *)arglist[i];    

	arg[argcout] = NULL;	

    //解析是否是后台运行
	for(i=0;i < argcout;i++){
		if(strncmp(arg[i],"&",1) == 0){
			if(i == argcout - 1){
				backgroud = 1;
				arg[argcout - 1] =NULL;
				break;
			}
			else {
				printf("wrong command\n");
				return ;
			}
		}
	}

	
	for(i=0 ;arg[i] != NULL; i++){
		//解析重定向输入
        if(strcmp(arg[i],">") == 0){
			flag++;
			how = out_redirect;
			if (arg[i+1] == NULL)
				flag++;
		}
        //解析重定向输出
		if( strcmp(arg[i],"<") == 0){
			flag++;
			how = in_redirect;
			if(i == 0)
				flag++;
		}
        //解析管道命令
		if( strcmp(arg[i],"|") == 0){
			flag++;
			how = have_pipe;
			if(arg[i+1] == NULL)
				flag++;
			if( i == 0)
				flag++;
		}
	}

	if(flag > 1){
		printf("wrong command !\n");
		return ;
	}

/**************解析重定向输出文件名*******************/
	if( how == out_redirect){
		for(i=0;arg[i]!=NULL;i++){
			if(strcmp(arg[i],">") == 0){
				file = arg[i+1];
				arg[i] = NULL;
			}
		}
	}
/*******************解析重定向输入文件名**********************/
	if(how == in_redirect){
		for(i=0; arg[i] != NULL;i++){
			if(strcmp(arg[i],"<") == 0){
				file = arg[i+1];
				arg[i] = NULL;
			}
		}
	}

/***************解析管道命令参数*******************/
	if(how == have_pipe){
		for( i=0; arg[i]!=NULL ;i++){
			if(strcmp(arg[i],"|") == 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;
			}
		}
	}
  // 如果命令为history 则输出历史。
    if(strcmp(arg[0],"history") == 0&& argcout == 1){
        printf("tolal num %d :\n",his_count);
        for(i=0;i<his_count;i++){
            printf("%d\t%s\n",i+1,history[i]);
        }
        goto loop;
    }


/*********************CD命令****************************/
//cd命令为内嵌命令,,因为chdir只能改变其本身进行的目录 
 
    if(strcmp(arg[0],"cd") == 0){
		sprintf(home,"/home/%s/",pw->pw_name);
        if(argcout == 1 || argcout == 2 && strcmp(arg[1],"~") == 0) //特殊情况判断
		{
		    chdir(home);
            goto loop;
		}
        if(chdir(arg[1]) == -1){
            perror("chdir");
        }
        goto  loop;
	}

//fork一个子进程,执行参数
	if( (pid = fork()) < 0){
		printf("fork error\n");
		return ;
	}
	
	switch(how){
/***************************普通命令*************************/		
	case 0:
        //子进程
		if(pid == 0){
			if( !(find_command(arg[0])) ){
				printf("%s :command not found !\n",arg[0]);
				exit(0);
			}
			execvp(arg[0],arg);
			exit(0);
		}
		break;
		
/************************重定向输入命令**************************/
	case 1:
		if(pid == 0){
			if( !(find_command(arg[0])) ){
				printf("%s :command not found !\n",arg[0]);
				exit(0);
			}
            //打开一个文件
			fd = open(file,O_RDWR | O_CREAT | O_TRUNC,0644);
			//把屏幕输出流给文件
            dup2(fd,1);

			execvp(arg[0],arg);//加载参数命令
			exit(0);
		}
		break;
/**********************重定向输出命令**************************/        
	case 2:
		if(pid == 0){
			if( !(find_command(arg[0])) ){
				printf("%s :command not found !\n",arg[0]);
				exit(0);
			}
            //打开文件
			fd = open(file,O_RDONLY);
			//把键盘输入流给文件
            dup2(fd,0);
			execvp(arg[0],arg);
            close(fd);
			exit(0);
		}
		break;
/**********************管道命令*************************/
	case 3:
		if(pid == 0){
			int pid2;
			int status2;
			int fd2;

            //fork一个子进程,运行管道符前命令
			if( (pid2 = fork()) < 0){
				printf("fork2 error\n");
				return ;
			}
			else if(pid2 == 0){
				if(!(find_command)(arg[0])){
					printf("%s :command not found !\n",arg[0]);
					exit(0);
				}
                
                
                //打开文件,将屏幕输出流1 给 fd2,其向屏幕输出的数据就存储到文件当中
                if((fd2 = open("/tmp/tempfile",O_WRONLY | O_CREAT | O_TRUNC ,0644)) == -1)
                    printf("open /tmp/tempfile failed!");
				dup2(fd2,1);
                execvp(arg[0],arg);
                close(fd2);
                exit(0);
			}

			
            //等待管道符前面命令执行完
            if(waitpid(pid2,&status2,0) == -1){
				printf("wait for child process error\n");
			}

            //管道后命令开始执行
            if(!(find_command)(argnext[0])){
					printf("%s :command not found !\n",argnext[0]);
					exit(0);
			}
           
            if((fd2 = open("/tmp/tempfile",O_RDONLY)) == -1) {
                perror("Open");
            }
			dup2(fd2,0);

			execvp(argnext[0],argnext);
			if(remove("/tmp/tempfile") == -1)//移除文件
				perror("remove");
			exit(0);

		}
		break;
		
	default :
		break;
	}
	
/**********************杀死父进程,后端运行********************************/    
    if(backgroud ==1  && pid != 0){
        printf("[process id %d]\n",pid);
        exit(0);
    }

/************************父进程等待子进程退出******************************/    

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


	loop: ;

}


int find_command(char *command)
{
	DIR *dp;
	struct dirent*  dirp;
	char *path[]={"./","/bin","/usr/bin",NULL};//在一下路径下,找可执行程序

	if(strncmp(command,"./",2) == 0)
		command =command + 2;
	int i=0;
	while(path[i]!=NULL){
		if((dp = opendir(path[i])) == NULL){//打开目录
			printf("can not open /bin\n");
		}
		while((dirp = readdir(dp)) != NULL){       //比较是否有相同的文件
			if(strcmp(dirp -> d_name,command) == 0){
				closedir(dp);
				return 1;
			}
		}
		closedir(dp);   
		i++;
	}
	return 0;
}




Makefile:


cc = gcc
OBJ = myshell
all :
	$(cc)  -c  ./*.c
	$(cc)  -o $(OBJ)  ./*.o  -I /usr/lib/x86_64-linux-gnu/libreadline.so  -lreadline -ltermcap  -g
	rm -rf  *.o
clean:
	rm -rf *.o






  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值