【Linux】实现一个简单的shell命令解释器

文章目录

      • 【1】实现的功能
      • 【2】代码
      • 【3】运行结果

【1】实现的功能

我们所做的这个简单的shell命令解释器可以实现简单的常用的基本命令,如ls、pwd、cd、cd - 、cd ~ 等

根据简单命令的定义,它的第一个参数是要执行的命令,后面的参数作为该命令的参数。
要执行的命令有两种情况:

一种是外部命令: 也就是对应着磁盘上的某个程序,例如 pwd、ls等等。对于这种外部命令,我们首先要到指定的路径下找到它,然后再执行它。

另一种是内部命令:内部命令并不对应磁盘上的程序,例如cd等等,它需要shell自己来决定该如何执行。例如对 cd 命令,shell就应该根据它后面的参数改变当前路径。

对于外部命令,需要创建一个子进程来执行它,本质就是fork+exec

【2】代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MAX 10
#define STRLEN 128

#define PATH "/bin/"	//系统bin路径位置

char OLDPWD[STRLEN]={0};	//记录上一次的命令,为了cd -这条命令

//================================================================================
//每次敲回车输出当前所在用户信息
//普通用户和root用户的提示符区别
void Printf_Info()	
{
	char flag='$';
	struct passwd *pw=getpwuid(getuid());
	assert(pw!=NULL);

	//uid为0则为root用户
	if(pw->pw_uid==0)	
	{
		flag='#';
	}
	struct utsname hostname;	//主机名
	uname(&hostname);

	char node[STRLEN]={0};
	strcpy(node,hostname.nodename); //获取网络上的名称

	char* name=strtok(node,".");

	//获取绝对路径
	char path[STRLEN]={0};
	getcwd(path,STRLEN-1);

	char*p=path+strlen(path);	//p指向绝对路径的末尾

	while(*p!='/')
	{
		p--;
	}

	//p指向路径末尾往前的第一个‘/’位置处
	if(strlen(path)!=1)
	{
		p++;	//++前,p->'/'
	}
	if(strcmp(path,pw->pw_dir)==0)
	{
		p="~";
	}
	printf("\033[;32mMyBash[%s@%s %s]%c\033[0m",pw->pw_name,name,p,flag);
	//  \033[47;31mThis is a color test\033[0m   设置打印结果的颜色
    fflush(stdout);

}
//================================================================================
void Mycd(char*path)
{
	//第一个字符串为cd而第二为空 如:cd 则结束本轮循环
	if(path==NULL)	
	{
		exit(0);
	}

	//cd ~ 回到用户根目录
	if(strcmp(path,"~")==0) 	
    {
        struct passwd*pw=getpwuid(getuid());
        path=pw->pw_dir;
    }

    //cd - 回到上一次的位置
    if(strcmp(path,"-")==0)   
    {
       	//若是第一次输入命令,则cd -命令不存在!
        if(strlen(OLDPWD)==0)
        {
            printf("\033[;31mMyBash:cd:OLDPWD not set\n\033[0m");
            return ;
        }
        //否则把上一次的命令给path
        path=OLDPWD;
    }

    //getpwd记录当前工作目录的绝对路径
    char oldpwd[STRLEN]={0};
    getcwd(oldpwd,STRLEN-1);

	if(-1==chdir(path))//反之则不是空,则通过chdir系统调用进入到该目录中
    {
        char err[128]="\033[;31mMybash: cd \033[0m";
        strcat(err,path);

        perror(err);
    }
    //每次执行完cd命令后,把工作路径赋给OLDPWD	
    strcpy(OLDPWD,oldpwd);
}
//================================================================================
//命令分割函数
void Strtok_cmd(char*buff,char*myargv[])
{
	char *s=strtok(buff," ");	//分割输入的字符串
	if(s==NULL)		//如果s为空,则进入下一轮循环
	{
		exit(0);
	}

	myargv[0]=s;	//把分割出来的第一个字符串放在myargv[0]中
	int i=1;
	while((s=strtok(NULL,""))!=NULL)	//把后续分割出来的字符串依次存放在数组中
	{
		myargv[i++]=s;
	}
}
//===============================================================
int main()
{
	while(1)
	{
		char buff[128]={0};

		Printf_Info();

		//从终端获取命令存入buff中
		fgets(buff,128,stdin);	
		buff[strlen(buff)-1]=0;

		char *myargv[MAX]={0};

		//分割输入的命令
		Strtok_cmd(buff,myargv);

		//如果输入exit则退出循环
		if(strcmp(myargv[0],"exit")==0)		
		{
			exit(0);
		}

		//如果分割出来的第一个字符串为cd 
		else if(strcmp(myargv[0],"cd")==0)	
		{
			Mycd(myargv[1]);
			continue;
		}
		
		//若是系统调用,直接替换fork+exec
		pid_t pid=fork();
		assert(pid!=-1);

		if(pid==0)
		{
			char path[256]={0};
			if(strncmp(myargv[0],"./",2)!=0 && strncmp(myargv[0],"/",1)!=0)
			{
				//先把路径放入path中
				strcpy(path,PATH);
			}

			//进行命令拼接,路径+名称
			strcat(path,myargv[0]);
			//替换进程	例如:/bin/ls
			execv(path,myargv);		
			perror("\033[;31mexecv error\033[0m");
		}
		//处理僵死进程
		else
		{
			wait(NULL);
		}
	}
}

【3】运行结果

运行结果如下
【Linux】实现一个简单的shell命令解释器_第1张图片
【Linux】实现一个简单的shell命令解释器_第2张图片【Linux】实现一个简单的shell命令解释器_第3张图片
异常处理如下:

若是第一次运行程序,则不能使用cd - 命令,因为此时还没有历史路径
若进入一个不存在的目录则会报错,没有这个文件或目录
若直接输入一个不存在的无法识别的命令,也会报错。
【Linux】实现一个简单的shell命令解释器_第4张图片

你可能感兴趣的:(网络编程,linux)