【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解释命令的过程。

相关文章
|
1天前
|
Linux 调度
Linux进程——Linux进程间切换与命令行参数
Linux进程——Linux进程间切换与命令行参数
|
1天前
|
监控 Linux 数据处理
探秘Linux命令行神器:head命令
`head`命令是Linux命令行中的利器,用于显示文件开头的部分内容,常用于快速检查文件类型、结构或日志分析。默认显示前10行,可通过`-n`指定行数或`-c`指定字节数。结合管道与其他命令如`grep`、`sed`、`awk`可实现更多功能。注意在处理大文件和自动化脚本时,合理使用能提高效率。
|
9天前
|
Linux 虚拟化 iOS开发
Linux系统——远程连接Linux系统(图形化、命令行、使用命令行学习Linux系统、FinalShell)
Linux系统——远程连接Linux系统(图形化、命令行、使用命令行学习Linux系统、FinalShell)
17 3
|
10天前
|
Ubuntu Java Linux
Linux centos7 ubuntu 一键安装Java JDK 脚本 shell 脚本
Linux centos7 ubuntu 一键安装Java JDK 脚本 shell 脚本
|
10天前
|
Ubuntu JavaScript Linux
linux 命令行下载BT种子和磁力链接 ubuntu linux 命令行下载种子
linux 命令行下载BT种子和磁力链接 ubuntu linux 命令行下载种子
|
10天前
|
Ubuntu Shell Linux
linux shell 后台执行脚本的方法 脚本后台运行 后台运行程
linux shell 后台执行脚本的方法 脚本后台运行 后台运行程
|
10天前
|
监控 Shell Linux
shell linux中用shell写一个占用CPU的脚本
shell linux中用shell写一个占用CPU的脚本
|
10天前
|
Shell Linux
shell linux中shell脚本编写俄罗斯方块
shell linux中shell脚本编写俄罗斯方块
|
8月前
|
Unix Shell Linux