Linux实现简易shell

Linux实现简易shell_第1张图片

文章目录

    • 0. shell
    • 1. 交互及获取命令行
    • 2. 解析命令行
    • 3. 执行命令行
      • 3.1 普通命令
      • 3.2 内建命令
    • 4. 主函数逻辑及演示

本章代码gitee仓库:简易shell

0. shell

shell是操作系统外的一层外壳程序,负责将用户的指令执行,将指令获取到之后再交给操作系统,操作系统将指令执行完毕之后的结果通过shell交给用户。shell/bash也是一个进程,本质上也是通过创建子进程来执行这些指令。

1. 交互及获取命令行

我们先来开一下交互及我们输入的命令行格式

Linux实现简易shell_第2张图片

先来做交互的页面,这其实就是一个while循环,一直等着我们输入指令,我们可以通过获取环境变量来获取这些信息。

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
const char *getUserName()
{
  return getenv("USER");
}
const char *getHostName()
{
  return getenv("HOSTNAME");
}
void getPwd()
{
  getcwd(pwd,sizeof(pwd));
}
void interact(char *cline, int size)
{
  getPwd();
  printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getUserName(),getHostName(),pwd);
  char *s = fgets(cline,size,stdin);	//获取指令
  assert(s);
  (void)s;  //防止后面不使用s变量报警告,假装用一下
  cline[strlen(cline)-1] = '\0';  //将回车抵消
  //printf("%s\n",s);
}

我们可以输出我们获取的命令测试一下

Linux实现简易shell_第3张图片

2. 解析命令行

获取到命令之后,我们就要解析这个命令,这个解析的本质上,就是将获取的字符串进行分割,分割各个部分:要执行的命令所带的命令行参数

例如ls -a -l,我们就需要解析成:

  • 所需执行的命令:ls
  • 命令行参数:-a-l
#define DELIM " \t"
int splitString(char cline[], char *_argv[])
{
  int i = 0;
  _argv[i++] = strtok(cline,DELIM);
  while(_argv[i++] = strtok(NULL,DELIM));
  return i-1;
}

Linux实现简易shell_第4张图片

3. 执行命令行

获取到所需执行的命令和参数之后,其实就是创建子进程,然后程序替换来执行这个命令,但是这些命令分为普通命令和内建命令:

  • 普通命令:创建子进程直接程序替换
  • 内建命令:父进程自己执行

3.1 普通命令

这里没有什么高科技,就是简单的创建子进程、程序替换和进程等待

#define EXIT_CODE 11
void normalExcute(char *_argv[])
{
  pid_t id = fork();
  if(id < 0)
  {
    perror("fork fail");
    return; 
  }
  else if(id == 0)
  {
    //子进程执行命令
    //execvpe(_argv[0],_argv,environ);  //直接程序替换
    execvp(_argv[0],_argv);   //直接替换程序
    exit(EXIT_CODE);  //替换失败的退出码
  }
  else
  {
    //父进程等待子进程退出
    int status = 0;
    pid_t rid = waitpid(id,&status,0);  //阻塞等待
    if(rid == id)
    {
      lastcode = WEXITSTATUS(status);
    }
  }
}

3.2 内建命令

对应内建命令,需要我们自己去一个一个添加然后判断,这里做一个简单的演示

int buildCommand(char *_argv[], int _argc)
{
  if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
  {
    chdir(_argv[1]);
    getPwd();
    sprintf(getenv("PWD"),pwd);
    return 1;
  }
  else if(_argc == 2 && strcmp(_argv[0],"export") == 0) //导环境变量
  {
    putenv((char*)_argv[1]);
    return 1;
  }
  else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)                                 
  {
    if(strcmp(_argv[1],"$?")==0)
    {
      printf("%d\n",lastcode);
      lastcode = 0;
    }
    else if(*_argv[1] == '$')
    {
      char*val = getenv(_argv[1]+1);
      if(val) printf("%s\n",val);
    }
    else  printf("%s\n",_argv[1]);

    return 1;
  }
  //将ls命令显示颜色
  if(strcmp(_argv[0],"ls") == 0)
  {
    _argv[_argc++] = "--color";
    _argv[_argc] = NULL;
  }
  return 0;
}

4. 主函数逻辑及演示

这里我们全部都封装起来了,各个模块解耦,想要修改的话,也很方便,完整的代码可以去仓库里面查看。

#define LINE_SIZE 1024
#define ARGC_SIZE 32

int main()
{
  while(!quit)
  {
    //交互 获取命令行
    interact(commandline,sizeof(commandline));
    
    //解析命令行
    int argc = splitString(commandline,argv);
    if(argc == 0) continue;
    //for(int i=0;argv[i];i++) printf("%s\n",argv[i]);
    //printf("%s\n",argv);
    //普通命令执行
    int flag = buildCommand(argv,argc);
    if(!flag) normalExcute(argv);
  }
  return 0;
}

Linux实现简易shell_第5张图片
所以我们每次登录的时候,界面会显示这些信息,其实就是因为系统启动了一个shell进程。

你可能感兴趣的:(原创,Linux,linux,运维,服务器)