【linux课设】自主实现shell命令行解释器

简介: 【linux课设】自主实现shell命令行解释器

shell和bash的关系

shell是命令解释器,它接收用户的命令并将其传递给内核去执行。bash,即GNU Bourne-Again Shell,是shell的一种实现方式,也是大多数linux系统下默认的shell。

bash的原理

大多数的指令进程(除了内建命令)都是bash的子进程。当我们要执行一条类似ls -a指令时,bash会提前fork出一个子进程,然后让子进程去执行指令。这是我们进程程序替换的思想。当然,中间的过程涉及到进程创建、虚拟内存、进程替换的细节,本篇文章不做叙述,感兴趣的可以去看我之前的博客,希望能对你有帮助。

我们可以画出bash进程执行指令的过程图来帮助理解:

在上图中,bash几乎一直在循环做以下动作:

1.获取指令

2.解析命令行

3.fork创建子进程

4.命令程序替换子进程

5.等待子进程终止

既然知道了bash的基本原理,我们同样也可以模拟以上动作来写一个mini版的shell

代码实现

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<errno.h>
#include<sys/stat.h>
#include<fcntl.h>

#define SIZE 100
#define COMMAND_SIZE 200
#define ZEOR '\0'
#define ARGV_NUM 32
#define SKIP " "
#define SkipPath(p) do{p+=strlen(p)-1; while(*p!='/'){p--;}}while(0)
#define SkipSpace(command,pos) do{\
  while(command[pos]==' ')pos++;\
}while(0)

#define None_Redir 0
#define In_Redir 1
#define Out_Redir 2
#define App_Redir 3

char* gArgv[ARGV_NUM];//指令参数
char cwd[SIZE*2];//当前路径的环境变量键值对
int lastcode=0;
char* filename=NULL;//重定向文件名
int Redir_Type=0;

const char* GetUserName(){//获取环境变量中的值
  const char* name=getenv("USER");
  if(name==NULL){
    return "None";
  }
  return name;
}

const char* GetHome(){//获取家目录
  const char* home=getenv("HOME");
  if(home==NULL){
    return "/";
  }
  return home;
}

const char* GetHostHome(){//获取主机名
  const char* hostname=getenv("HOSTNAME");
  if(hostname==NULL){
    return "None";
  }
  return hostname;
}

const char* GetCwd(){//获取当前路径
  const char* cwd=getenv("PWD");
  if(cwd==NULL){
    return "None";
  }
  return cwd;
}


//输出命令行
void MakeCommandLineAndPrint(){
    char line[SIZE];
    const char* name=GetUserName();
    const  char* hostname=GetHostHome();
    const char* cwd=GetCwd();
    SkipPath(cwd);
    snprintf(line,sizeof(line),"[%s@%s %s]> ",name,hostname,cwd=strlen(cwd)==1?"/":cwd+1);
    printf("%s",line);
    fflush(stdout);
}
//获取用户命令行
int GetUserCommand(char command[],size_t n){
  char* s=fgets(command,n,stdin);
  if(s==NULL)return -1;
  command[strlen(command)-1]=ZEOR;
  return (int)strlen(command);
}


//命令行分割,获取命令行参数列表
void SplitCommand(char command[],size_t n){
   gArgv[0]=strtok(command,SKIP);
   size_t index=1;
   char* t=gArgv[0];
   while(t!=NULL){
     t=strtok(NULL,SKIP);
      gArgv[index++]=t;
   }
   //size_t i=0;
   //for(;i<index;i++){
   //  printf("%s\n",gArgv[i]);
   //}
}

//cd内建命令
void Cd(){
  const char* path=gArgv[1];
  if(path==NULL){
    path=GetHome();
  }
  //更新当前工作目录
  chdir(path);//修改当前进程的工作路径

  //更新环境变量
  char t[SIZE*2];
  getcwd(t,sizeof(t));
  snprintf(cwd,sizeof(cwd),"PWD=%s",t);
  putenv(cwd);
}

//查看是否是内建命令
int CheckBuiltIn(){
  int yes=0;
  const char* cmd=gArgv[0];
  if(strcmp(cmd,"cd")==0){
    yes=1;
    Cd();
  }
  return yes;
}
// 处理创建子进程失败
void Die(){
  exit(1);
}

//区别重定向类型
void CheckRedir(char* command){
   int pos=0;
   int end=strlen(command);

   while(pos<end){
     if(command[pos]=='>'){
       if(command[pos+1]=='>'){
         Redir_Type=App_Redir;     
         command[pos++]='\0';
         pos++;
         SkipSpace(command,pos);
         filename=command+pos;
       }else{
         Redir_Type=Out_Redir;
         command[pos++]='\0';
         SkipSpace(command,pos);
         filename=command+pos;

       }
     }else if(command[pos]=='<'){
         Redir_Type=In_Redir;
         command[pos++]='\0';
         SkipSpace(command,pos);
         filename=command+pos;
     }else{
       pos++;
     }
   }
}



//执行指令
void ExeCommand(){ 
   pid_t id=fork();
   if(id<0){
     Die();
   }
   if(id==0){
     //child
     if(filename!=NULL){
       if(Redir_Type==In_Redir){
        int fd= open(filename,O_RDONLY,0666);
        dup2(fd,0);
       }else if(Redir_Type==Out_Redir){
         int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
         dup2(fd,1);
       }else if(Redir_Type==App_Redir){
         int fd=open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
         dup2(fd,1);
       }else{}

     }
     execvp(gArgv[0],gArgv);
     exit(errno);
   }else{
     int status=0;
     pid_t res=waitpid(id,&status,0);
     if(res>0){
       lastcode=WEXITSTATUS(status);//获取子进程退出码信息
       if(WIFEXITED(status)){
        if(lastcode!=0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);
       }
     }
   }
}

int main(){
  int quit=0;
  while(!quit){
     //1.输出一个命令行
     MakeCommandLineAndPrint();
    
     //2.获取用户命令
     char command[COMMAND_SIZE];
     int n= GetUserCommand(command,sizeof(command));
     if(n<=0)return 1;//输入指令不合法
    // printf("%s\n",command);
    //2.1检查是否有重定向符,并分割
     CheckRedir(command);
    //3.命令行字符串分割
     SplitCommand(command,sizeof(command));
    
     //4.查看命令是否是内建命令
     int flag=CheckBuiltIn();
     if(flag)continue;
     
     //5.执行指令
     ExeCommand();
  }
  return 0;
}



运用进程创建,进程替换的原理,基本模拟了shell解释命令的过程。

相关文章
|
12天前
|
安全 Shell Linux
探索Linux命令chsh:更改用户的默认shell
`chsh`是Linux命令,用于更改用户的默认登录shell。它涉及用户环境配置和系统安全,允许用户选择更适合自己的shell以提升效率。命令有交互式选项和参数如`-s`来指定新shell。在使用时要注意新shell的可执行性、权限问题及选择合适的shell。例如,要更改为bash,用户可运行`chsh`后按提示操作,而root用户能用`sudo chsh -s /bin/zsh john`为用户`john`设定zsh。在更改前,确认shell路径、权限,并了解不同shell的特点。
|
2天前
|
监控 Unix Shell
探秘GNU/Linux Shell:命令行的魔法世界
探秘GNU/Linux Shell:命令行的魔法世界
6 0
|
3天前
|
机器学习/深度学习 Unix Java
技术笔记:Linux之Shell脚本编程(一)
技术笔记:Linux之Shell脚本编程(一)
|
4天前
|
Shell Linux
【linux】进程替换的应用|shell解释器的实现
【linux】进程替换的应用|shell解释器的实现
9 0
|
9月前
|
Unix Shell Linux
|
2月前
|
Shell Linux C语言
Linux中执行Shell的函数(popen,system,exec)介绍:分享一些常用的执行Shell的函数及其相关编程技巧和经验
Linux中执行Shell的函数(popen,system,exec)介绍:分享一些常用的执行Shell的函数及其相关编程技巧和经验
63 0
|
9月前
|
Shell Linux