目录

一,跟正宗的bash见个面

二,实现一个山寨的bash

1.提示符

2.输入命令与回显命令

 3.解析命令

4.执行命令

5.执行逻辑

三,全部代码


一,跟正宗的bash见个面

 在这篇文章中,我会写一个myshell小程序这个程序其实就是一个解释器。在我的机器上它长这样:

   

二,实现一个山寨的bash

1.提示

在图:

中。这个提示符的信息可以分为四类:

1.用户名    2.主机名   3.当前地址   4.其他字符

在这个图片里:cq就是用户名  VM-8-9-centos就是主机名   mybash就是当前所在路径

我们如何获取呢?两条路:1.其它字符直接打印  2.用户名等用环境变量获取代码如下

 #include<stdio.h>    
#include<stdlib.h>//getenv头文件    
    
const char* Username()//获取用户名    
{    
  const char* user = getenv("USER");    
  if(user) return user;    
  return "none";    
}    
    
const char* Hostname()//获取主机名    
{    
  const char* host = getenv("HOSTNAME");    
  if(host) return host;    
  return "none";    
}    
    
const char* Pwd()  //获取当前地址  
{    
  const char* pwd = getenv("PWD");                                                                                                                                                         
  if(pwd) return pwd;    
  return "none";    
}    
    
    
int main()    
{    
  printf("[%s@%s %s]#n",Username(),Hostname(),Pwd());    
  return 0;    
}    

效果

可以看到我们当前的提示符显示可以成功的。

2.输入命令回显命令

    想到输入显示命令时,我猜很多同学的脑子里第一个想到的便是scanfprintf。但是在这里我们是不能使用scanf的。因为我们在输入命令时候一定会遇到输入空格的情况,如:lsa -l命令等等。但是scanf在遇到空格时候便会停下。所以我们不能使用scanf进行读取数据。所以我们采用gets或者fgets来读取数据

   这两个函数介绍如下

gets函数是将键盘上的输入读取str缓冲区里存起来。

fgets的功能gets一样,这三个参数的意思如下:1.str表示存储读取数据的地方

2.num 存储的最大数据量  。 3.stream表示从何读取键盘读取则为stdin)。

写出代码如下

 char buff[1024]\一般将这个数定义全局的
 fgets(buff,sizeof(buff),stdin);                                                                                                                                                          
  buff[strlen(buff)-1] = ''; \将回车符给吞掉  
  printf("%sn",buff);  

封装函数如下

void  getCommand()    
{    
  fgets(buff,sizeof(buff),stdin);    
  buff[strlen(buff)-1] = '';    
}  

效果

 3.解析命令

  在这里解析命令的意思便是将一个长字符串空格分隔分割成一个一个短的字符串比如lsa -l”就应该分成”ls” “-a” “-l”。在这里我们使用到一个字符串分割函数:

这个函数的参数str表示分割字符串  delimiters表示分割符。并且要注意的是,当我的第一次分割成功以后,我后面的连续分割可以将str用NULL表示。先在写出代码如下

void splictCommand(char* in,char* out[]) \注意这里的参数inbuff,out是char* argv。这两个参数定义全局   
  {    
    int argc = 0;    
    out[argc++]= strtok(in,SEP);    
  while(out[argc++]= strtok(NULL,SEP));   
 
  #ifdef Debug  \用来测试  
    for(int i = 0;out[i];i++)    
      printf("%d:%sn",i,out[i]);    
  #endif                                                                                                                                                                                   
  }   

效果分割完成!!!

4.执行命令

完成输入和解析命令以后我们就得来执行命令了。我们如何实现命令的执行呢?

1.创建进程    2.使用程序替换

在这里要了解的是,有一些命令是必须要让父进程来执行的。比如cd export echo等。这些命令叫做内建命令。还有一些命令则不需要由父进程来来执行而是要交由子进程来执行。所以我们得创建子进程。 在执行命令时候步骤如下

1.先检查是否是内建命令:若是便执行并且返回一个1。若不是便返回0。

代码

int dobuildin(char* argv[])
  {
    if(strcmp(argv[0],"cd")== 0)//cd是内建命令
    {
      char* path = NULL;
      if(argv[1] == NULL)  path =getHome();
      else  path = argv[1];
       cd(path);  
       return 1;
    }
    else if(strcmp(argv[0],"export") == 0)//export是内建命令
    {
       if(argv[1]== NULL) return 1; 
       strcpy(enval,argv[1]);
       putenv(enval);
       return 1;
    }
    else if(strcmp(argv[0],"echo")==0)//echo是内建命令
    {                                                                                                                                                                                      
      if(argv[1] == NULL)
      {
        printf("n");
      }
      else{
       if(argv[1] == NULL) 
       {
         printf("n");
         return 1;
       }
   if(*(argv[1])=='$'&&strlen(argv[1])>1)
       {
         
          char* val = argv[1]+1;
          if(strcmp(val,"?")==0)
          {
            printf("%dn",lastcode);
            lastcode = 0;
          }
          else printf("%sn",getenv(val));
       }
       else
       {
         printf("%sn",argv[1]);
       }
       return 1;
    }
    }
  
  
    return 0;//不是内建命令便返回0
  
  }

然后才是执行其它命令:

void excute(char* argv[])
  {
     
    pid_t id = fork();//创建子进程
    if(id == 0)//子进程执行程序替换
    {
     execvp(argv[0],argv);
     exit(1);//执行完便可以退出
    }
    else 
    {
      int status = 0;
      pid_t  rid = waitpid(id,&status,0);//等待子进程
      if(rid>0){//等待成功
         lastcode = WEXITSTATUS(status);//获取最后一次退出码
      }
    }
  }

执行逻辑

 n = dobuildin(argv);//检查并执行内建命令
 if(n) continue;
 excute(argv);//子进程执行命令

这两个函数的执行顺序如上。如果内建命令执行成功在这一次便可以不再执行下面的普通命令的代码。如果不成功便可以执行下面的普通命令的代码。

5.执行逻辑

int main()
  {
   while(1){   
      char Usercommand[NUM];
      int n  =  getCommand(Usercommand,sizeof(Usercommand));//获取命令
      if(n<=0) continue;
      char* argv[SIZE];
      splictCommand(Usercommand,argv);//将命令打散放到数组中                                                                                                                               
      n = dobuildin(argv);//检查并执行内建命令
      if(n) continue;
      excute(argv);//子进程执行命令
   }
      return 0;
  }

三,全部代码

#include<stdio.h>
#include<stdlib.h>//getenv头文件
#include<string.h>
#include<unistd.h>//fork头文件
#include<sys/types.h>//要使用pid_t必须包含头文件  
#include<wait.h>

#define Debug 1
char buff[1024];
char* argv[64];
char enval[1024];//用来存储全局环境变量
char cwd[1024];
int lastcode = 0;

#define SEP " "
const char* Username()//获取用户名
{
  const char* user = getenv("USER");
  if(user) return user;
  return "none";
}

const char* Hostname()//获取主机名
{
  const char* host = getenv("HOSTNAME");
  if(host) return host;
  return "none";
}

const char* Pwd()
{
  const char* pwd = getenv("PWD");
  if(pwd) return pwd;
  return ".";
}

 char* getHome()
{
   char*  home = getenv("HOME");
  if(home) return home;
  return(char*) "none";
}

int  getCommand()
{

  printf("[%s@%s %s]#",Username(),Hostname(),Pwd());
  char* str = fgets(buff,sizeof(buff),stdin);
  buff[strlen(buff)-1] = '';
  if(str) return strlen(str)-1;
  return -1;
}

void splictCommand(char* in,char* out[])
{
  int argc = 0;
  out[argc++]= strtok(in,SEP);
  while(out[argc++]= strtok(NULL,SEP));
#ifdef Debug
  for(int i = 0;out[i];i++)
    printf("%d:%sn",i,out[i]);
#endif
}

void cd( char* path)
{
  if(path == NULL)
  {
     path = getHome();
  }
   
  int i= chdir(path);
  printf("%dn",i);
   char temp[1024];
   getcwd(temp,sizeof(temp));//获取pwd并放到临时变量tempsprintf(cwd,"PWD=%s",temp);将pwd放到全局变量cwd中
   putenv(cwd);//用cwd替换掉PWD内的内容实现改变PWD的目的
}

int  dobuildin( char* argv[])
{
   if(strcmp(argv[0],"cd") == 0)
   {
     char* path = argv[1];      
      cd(path);
      return 1;
   }
   else if(strcmp(argv[0],"export")== 0)
   {
        
        char* val = argv[1];
        if(val == NULL) return 1;
        strcpy(enval,val);
        putenv(enval);
        return 1;
   }
   else if(strcmp(argv[0],"echo")== 0)
   {
     
      if(*argv[1]=='$'&amp;&amp;strlen(argv[1])>1)
      {
        
      char* val = argv[1]+1;//$?,$PATH
      if(strcmp(val,"?")==0)
        {
        printf("%dn",lastcode);//显示最近一次错误码
        lastcode = 0;
        return 1;
        }    
        else {
           printf("%sn",getenv(val));
        } 
     }
      else
      {
        printf("%sn",argv[1]);
      }
      return 1;
   }

   return 0;
}

void excute(char* argv[])
{
  
    pid_t id =fork();
    if(id == 0)//子进程 
    {
      execvp(argv[0],argv);
      exit(1);
    }
  
    else//父进程
    {
      int status = 0;
      pid_t rid = waitpid(id,&amp;status,0);//等待子进程
      if(rid>0){
        lastcode = WEXITSTATUS(status); //获取退出码
      } 

    } 
}

int main()
{
  while(1)
  {
    int n = getCommand();
    if(n<=0) continue;
    splictCommand(buff,argv);
     n =  dobuildin(argv);
    if(n) continue;
    excute(argv); 
    
  }
    return 0;
}

   

原文地址:https://blog.csdn.net/qq_41934502/article/details/134725749

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_49551.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注