【Linux】bash项目mybash的实现

文章目录


一、bash是什么

命令解释器:收到命令后解析它然后执行

用户运行./main、ls等命令时,就是交付给bash运行,当其结果呈现给用户

二、bash工作原理

先fork自己一份,复制出来一份子进程,然后子进程替换成要执行的命令。不断复制自身,然后用exec执行程序


三、项目实现mybash

1.项目框架

要先写个死循环,不执行exit退出就一直在bash内进行死循环

循环内执行步骤: 

(1)获取提示符信息,并打印:stu@stu-vertual-machin:~.....

(2)获取命令:fegts(buff);//从键盘上获取个命令,把命令放到Buff内

(3)分割命令 :分割命令和参数,如下所示,将cp a.c b.c进行分割

  • cp a.c b.c 
  • cp->argv[0]
  • a.c->argv[1]
  • b.c->argv[2]

        分割完,cmd接收命令:char * cmd=myargv[0];//获取命令 cmd接收

(4)进行命令分类

命令有2类分类(内置命令和普通命令),如下:

  • 内置命令:
    • cd、exit
    • 通过which找不到
  • 普通命令:

    • 除内置命令(cd、exit)外的命令

    • 通过which可以找到,如下所示,ls,ps就是普通命令,cd是内置命令

  • 普通命令一般子进程执行fork()+exec,内置命令由父进程内执行
  • 代码就如下所示:
    •         if(内置命令)//内置命令,父进程内执行

                      {....}

              else//普通命令,子进程执行

                      { fork()+exec//普通命令,谁执行都可以....}

2.项目伪代码

3.半成品代码

目前代码可以运行出ls,pwd,cd的结果,但是在输出提示符信息处选择的方式是打印出固定信息,这个可以进行进一步优化——自动打印,会在下面进行介绍。

先看代码 

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<pwd.h>

#define ARG_MAX 10

//分割命令
char *get_cmd(char* buff,char *myargv[])
{
	if(buff==NULL||myargv==NULL)
	{
		return NULL;
	}
	char * s=strtok(buff," ");
	int i=0;
	while(s!=NULL)
	{
		myargv[i++]=s;
		s=strtok(NULL," ");
	}
	return myargv[0];
}


void run_cmd(char *file,char* myargv[])
{
	if(file==NULL||myargv==NULL)
	{
		return ;
	}
	pid_t pid=fork();
	if(pid==-1)
	{
		printf("fork error");
		return;
	}
	if(pid==0)//子进程内执行execvp
	{
		//替换
		execvp(file,myargv);//file也就是主函数内的cmd,它的文件路径系统会自己找
		//如果没找到,失败就会执行下面代码:
		perror("execvp error\n");
		exit(0);//失败的话,子进程必须要退出
		//因为子进程不退出,会在main内执行程序打印提示符
	}
	else//父进程防止僵死进程
	{
		wait(NULL);//防止僵死
	}
}

int main()
{
	while(1)
	{
		
        //先把信息写死:
        printf("stu@stu-virtual-machine:~/桌面/223");
        //把信息自动输出调用该函数:printf_info();后面介绍
		fflush(stdout);//刷新

        //键盘输入命令
		char buff[128]={0};
		fgets(buff,128,stdin);
		buff[strlen(buff)-1]=0;//把最后一个置零
		//因为输入命令之后再输入回车运行时,回车符号会被记录

        //对命令进行识别
		char *myargv[ARG_MAX]={0};
		char *cmd=get_cmd(buff,myargv);//放在buff内
		if(cmd==NULL)//敲回车,什么也不执行,进入下一次循环
		{
			continue;
		}
        
        //执行命令
		//内置命令,在父进程执行
		else if(strcmp(cmd,"exit")==0)
		{
			break;//退出大循环
		}
		else if(strcmp(cmd,"cd")==0)
		{
    			if(myargv[1]!=NULL)//保证cd后面有东西 不能命令只有cd
    			{
        			if(chdir(myargv[1])==-1)//出错
        			{
            				perror("cd error");
        			}
       		    }
		}	
		else//普通命令,子进程内执行
		{
			//fork()+exec
			run_cmd(cmd,myargv);
		}

	}
	exit(0);
}

代码解释

代码段一:

//分割命令
char *get_cmd(char* buff,char *myargv[])
{
	if(buff==NULL||myargv==NULL)
	{
		return NULL;
	}
	char * s=strtok(buff," ");
	int i=0;
	while(s!=NULL)
	{
		myargv[i++]=s;
		s=strtok(NULL," ");
	}
	return myargv[0];
}

get_cmd函数的功能是实现对命令进行分割,其中需要用到strtok()函数。

  • 该函数的用法是用于分割字符串,如下图所示,将a/bbb///cc分割为‘a’,'bbb','ccc'

  • 需要引用的头文件:#include<string.h>
  • 该函数的使用
    • 函数在使用的时候只需要指向buff一次,因为该函数内部自定义了一个指针,专门用来记录把东西分割在哪里了,剩下的几次使用只用传个空NULL进行占位就行,内部指针已经帮我们定位到下一个字符的位置,注意需要用空隔开。如下所示:
char * s=strtok(buff," ");//分割buff数组内内容,以空格符做第一次分割
s=strtok(NULL," ");
s=strtok(NULL," ");
s=strtok(NULL," ");//最后一次调用时,s=NULL

代码段二:

void run_cmd(char *file,char* myargv[])
{
	if(file==NULL||myargv==NULL)
	{
		return ;
	}
	pid_t pid=fork();
	if(pid==-1)
	{
		printf("fork error");
		return;
	}
	if(pid==0)//子进程内执行execvp
	{
		//替换
		execvp(file,myargv);//file也就是主函数内的cmd,它的文件路径系统会自己找
		//如果没找到,失败就会执行下面代码:
		perror("execvp error\n");
		exit(0);//失败的话,子进程必须要退出
		//因为子进程不退出,会在main内执行程序打印提示符
	}
	else//父进程防止僵死进程
	{
		wait(NULL);//防止僵死
	}
}

        该函数的功能是在子进程中执行普通命令。上面讲到,普通命令一般就是在子进程执行fork()+exec来完成执行。因此,先复制出子进程,然后在子进程中使用execvp()函数替换进程,子进程开始执行,file对应主函数的cmd,系统会自己查找file文件所在路径,找到后就在该路径下执行myargv[],找不到打印下面的错误提示,然后子进程退出。

代码段三:

else if(strcmp(cmd,"cd")==0)
{
    if(myargv[1]!=NULL)//保证cd后面有东西 不能命令只有cd
    {
        if(chdir(myargv[1]==-1))//出错
        {
            perror("cd error");
        }
    }
} 

该代码是实现cd命令。需要引入对chdir系统调用和getcwd函数的知识:

如果只输入cd命令则不执行,因此myargv[1]!=NULL,保证不只是输入cd命令,确保命令后面有东西(就是输入cd ..或者cd+文件目录名等) ,然后调用chdir进行浏览切换目录。


4.优化代码

此处介绍,如何不把提示写死,写成可自动输出的?

修改printf代码为printf_info: 

//先把信息写死
		printf("stu@stu-virtual-machine:~/桌面/223");//stu@localhost ~$");//$普通用户
printf_info();
//用户名+@+主机名+路径+普通用户($)/管理员(#)

做法:

1.获取用户名

getpwuid(),通过把uid传给getwuid(),函数返回一个包含用户名信息的结构体。

2.获取主机名hostname

调用gethostname()函数,失败返回-1

3.判断用户是管理员还是普通用户

uid=0,则为root用户,用getuid()获取uid的值

用user_str存放"#"或者"$"

4.得到路径getcwd(),getcwd是确定当前工作目录,详情点击链接:【Linux】文件与目录的底层系统调用_又秃又弱的博客-CSDN博客

代码

#include<sys/types.h>
#include<pwd.h>

void printf_info()
{
    char *user_str="$";
    int user_id=getuid();//得到uid
    if(user_id==0)
    {
        user_str="#";
    }
    struct passwd*ptr=getpwuid(user_id);//用户名
    if(ptr==NULL)//判断getwuid的返回值
    {
        printf("mybash1.0>>    ");//失败就打印mybash版本号
        fflush(stdout);//刷新
        return;
    }
    //主机名
    char hostname[128]={0};
    if(gethostname(hostname,128)==-1)
    {
        printf("mybash1.0>>    ");//失败就打印mybash版本号
        fflush(stdout);
        return ;
    }
    //得到路径
    char dir[256]={0};
    if(getcwd(dir,256)==NULL)
    {
        printf("mybash1.0>>    ");//失败就打印mybash版本号
        fflush(stdout);
        return ;
    }
    printf("%s@%s %s%s ",ptr->pw_name,hostname,dir,user_str);
    fflush(stdout);
}

此时,运行可得到如下所示,第四行为mybash打印出来的命令提示,和前三行系统自动出来的已经时一摸一样了,唯一就是颜色不同。

下面介绍如何修改颜色

这个不太算重点,参照这篇文章照着写下就好:printf 颜色,关于printf如何输出颜色_XY LIU的博客-CSDN博客

举例:其中\033 开头,1是高亮,32是绿色,34是蓝色,结尾需要\033[0m关闭所有属性:

printf("%s@%s %s%s ",ptr->pw_name,hostname,dir,user_str);
printf("\033[1;32m%s@%s\0330m \033[1;34m%s%s\033[0m ",ptr->pw_name,hostname,dir,user_str);

运行结果:

5.完整mybash代码实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>

#define ARG_MAX 10

#include<sys/types.h>
#include<pwd.h>

//输出提示信息stu@stu-virtual-machine:...
void printf_info()
{
    char *user_str="$";
    int user_id=getuid();//得到uid
    if(user_id==0)
    {
        user_str="#";
    }
    struct passwd*ptr=getpwuid(user_id);//用户名
    if(ptr==NULL)//判断getwuid的返回值
    {
        printf("mybash1.0>>    ");//失败就打印mybash版本号
        fflush(stdout);
        return;
    }
    //主机名
    char hostname[128]={0};
    if(gethostname(hostname,128)==-1)
    {
        printf("mybash1.0>>    ");//失败就打印mybash版本号
        fflush(stdout);
        return ;
    }
    //得到路径
    char dir[256]={0};
    if(getcwd(dir,256)==NULL)
    {
        printf("mybash1.0>>    ");//失败就打印mybash版本号
        fflush(stdout);
        return ;
    }
    printf("\033[1;32m%s@%s\033[0m \033[1;34m %s\033[0m%s ",ptr->pw_name,hostname,dir,user_str);
    fflush(stdout);
}

//分割命令
char *get_cmd(char* buff,char *myargv[])
{
	if(buff==NULL||myargv==NULL)
	{
		return NULL;
	}
	char * s=strtok(buff," ");
	int i=0;
	while(s!=NULL)
	{
		myargv[i++]=s;
		s=strtok(NULL," ");
	}
	return myargv[0];
}

void run_cmd(char *file,char* myargv[])
{
	if(file==NULL||myargv==NULL)
	{
		return ;
	}
	pid_t pid=fork();
	if(pid==-1)
	{
		printf("fork error");
		return;
	}
	if(pid==0)
	{
		//替换
		execvp(file,myargv);
		//myargv[0]->path->cmd
		//如果失败会执行下面代码:
		perror("execvp error\n");
		exit(0);//失败的话,子进程必须要退出
		//因为子进程不退出会在main内执行程序打印提示符
	}
	else
	{
		wait(NULL);//防止僵死
	}
}

int main()
{
	while(1)
	{
		
        //把信息写死:printf("stu@stu-virtual-machine:~/桌面/223");
        //把信息自动输出:
		printf_info();
		fflush(stdout);//刷新

        //键盘输入命令
		char buff[128];
		fgets(buff,128,stdin);
		buff[strlen(buff)-1]=0;//把最后一个置零
		//因为输入命令之后再输入回车运行时,回车符号会被记录

        //对命令进行识别
		char *myargv[ARG_MAX]={0};
		char *cmd=get_cmd(buff,myargv);//放在buff内
		if(cmd==NULL)//敲回车,什么也不执行,进入下一次循环
		{
			continue;
		}
        
        //执行命令
		//内置命令,在父进程执行
		else if(strcmp(cmd,"exit")==0)
		{
			break;//退出大循环
		}
		else if(strcmp(cmd,"cd")==0)
		{
    			if(myargv[1]!=NULL)//保证cd后面有东西 不能命令只有cd
    			{
        			if(chdir(myargv[1])==-1)//出错
        			{
            				perror("cd error");
        			}
       		    	}
		}	
		else//普通命令,子进程内执行
		{
			//fork()+exec
			run_cmd(cmd,myargv);
		}

	}
	exit(0);
}

四、其他常用命令的实现

mybash.c执行时,命令是系统里面的,是在系统内部查找ps,ls,pwd等等命令,下面就讲解如何程序员自己写出ps,pwd等等这些常用命令。

1.myclear清屏的实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    printf("\033[2J");
}

清屏+移动光标

printf("\033[2J\033[0H");

2.mypwd的实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    char path[256]={0};
    if(getcwd(path,256)==NULL)
    {
        perror("getcwd error");
        exit(1);
    }
    printf("%s\n",path);
    exit(0);
}

执行结果:

3.myls的实现

gcc -o ls ls.c

#include<dirent.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    char path[256]={0};
    if(getcwd(path,256)==NULL)
    {
        perror("getcwd error");
        exit(1);
    }
    //得到目录流
    DIR *pdir=opendir(path);
    if(pdir==NULL)
    {
        perror("getcwd error");
        exit(1);
    }
    //读目录流信息
    /*读取目录流信息,指针s接收,打印 d->name文件名*/
    struct dirent *s=NULL; //定义结构体指针s
    while((s=readdir(pdir))!=NULL)
    {
        printf("%s    ",s->d_name); //输出文件名 
    }
    printf("\n");
    closedir(pdir);
    exit(0);  
}

执行结果:

 在执行myls时和系统的ls执行结果对比,在myls输出结果中会看到——当前位置"." 和上层位置" .."的两个隐藏文件(输入ls -a可以显示出隐藏文件)

如何在myls中也实现将这两个文件也隐藏起来?

答:如下代码所示

strncmp(s->d_name,".",1)==0)//比较s->d_name中第1个值与“.”相等,就不输入printf
{
        continue;
}
#include<dirent.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

int main()
{
    ...
    //读目录流信息
    struct dirent *s=NULL; //定义结构体指针s
    while((s=readdir(pdir))!=NULL)
    {
        strncmp(s->d_name,".",1)==0)
        //比较s->d_name中第1个值与“.”相等,就不输入printf
        {
            continue;
        }
        printf("%s    ",s->d_name); //输出文件名 
    }
    printf("\n");
    closedir(pdir);
    exit(0);  
}

运行结果

        此时就没有再显示出隐藏文件了,但是myls和ls还有个不一样就是文件颜色,如何把myls的文件颜色也标记出来呢?

仔细观察ls标记的颜色可以发现,如下文件类型和颜色的关系:

目录文件-->蓝色;普通文件有执行权限-->绿色;普通文件没有执行权限-->黑色

使用stat进行文件类型判断

stat:​​​​​

  • 头文件:include<sys/stat.h>
  • 是否是目录:S_ISDIR(m)

  • 普通文件:S_ISREG(m)

因此在输出文件的时候,使用stat函数对文件类型进行判断,不同类型的文件用不同颜色输出。补充代码如下:

int main()
{
    ...
    struct dirent *s=NULL; //定义结构体指针s
    while((s=readdir(pdir))!=NULL)
    {
        ...
        //printf("%s    ",s->d_name);//d_name是文件名
        struct stat filestat;
        stat(s->d_name,&filestat);
        if(S_ISDIR(filestat.st_mode))//是目录文件,则打印蓝色,st_mode是结构体包含了一些文件信息
        {
            printf("\033[1;34m%s\033[0m ",s->d_name);
        }
        //普通带执行权限的文件
        else
        {
            if(filestat.st_mode&(S_IXUSR|S_IXGRP|S_IXOTH))//状态信息,带执行权限
            {
                printf("\033[1;32m%s\033[0m ",s->d_name);
            }
           else
	        {
		         printf("%s ",s->d_name);
	        }
        }
    }
    printf("\n");
    closedir(pdir);
}

完整版本myls代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<dirent.h>
#include<sys/stat.h>

int main()
{
    char path[256]={0};
    if(getcwd(path,256)==NULL)
    {
        perror("getcwd error");
        exit(1);
    }
    DIR* pdir=opendir(path);
    if(pdir==NULL)
    {
        perror("opendir error");
        exit(1);
    }
    struct dirent * s=NULL;
    while((s=readdir(pdir))!=NULL)
    {
        if(strncmp(s->d_name,".",1)==0)
        {
            continue;
        }
        //printf("%s    ",s->d_name);
        struct stat filestat;
        stat(s->d_name,&filestat);
        if(S_ISDIR(filestat.st_mode))//是目录文件,则打印蓝色
        {
            printf("\033[1;34m%s\033[0m ",s->d_name);
        }
        //普通带执行权限的文件
        else
        {
            if(filestat.st_mode&(S_IXUSR|S_IXGRP|S_IXOTH))//状态信息,带执行权限
            {
                printf("\033[1;32m%s\033[0m ",s->d_name);
            }
            else
	        {
		         printf("%s ",s->d_name);
	        }
        }
    }
    printf("\n");
    closedir(pdir);
}

运行结果

 myls和系统的ls输出结果如下:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值