Linux实现简易shell

28 篇文章 2 订阅

在这里插入图片描述

本章代码gitee仓库:简易shell

🦄0. shell

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

🐮1. 交互及获取命令行

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

image-20231103220238498

先来做交互的页面,这其实就是一个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);
}

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

image-20231103221103250

🐷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;
}

image-20231103221728909

🐯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;
}

GIF 2023-11-3 22-37-06
所以我们每次登录的时候,界面会显示这些信息,其实就是因为系统启动了一个shell进程。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加法器+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值