Linux19 实现shell基本功能

🔥个人主页: Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

<<Git>><<MySQL>>

🌟心向往之行必能至

目录

前言:分析shell的实现原理

一.获取命令行的各个数据

二.打印命令行

三.得到命令符

四.分析命令符

五.执行命令符

六.实现内建命令--cd


前言:分析shell的实现原理

例子

bash 复制代码
[lcb@hcss-ecs-1cde ~]$ ls
1  code  install.sh
[lcb@hcss-ecs-1cde ~]$ ll
total 12
drwxrwxr-x 3 lcb lcb 4096 Dec 13 23:21 1
drwxrwxr-x 3 lcb lcb 4096 Dec 13 23:23 code
-rw-rw-r-- 1 lcb lcb  827 Dec 12 17:02 install.sh
[lcb@hcss-ecs-1cde ~]$ ps
  PID TTY          TIME CMD
18153 pts/0    00:00:00 bash
18174 pts/0    00:00:00 ps

⽤下图的时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为sh的⽅块代表,它随着时 间的流逝从左向右移动。shell从⽤⼾读⼊字符串"ls"。shell建⽴⼀个新的进程,然后在那个进程中运⾏ls程序并等待那个进程结束。

然后shell读取新的⼀⾏输⼊,建⽴⼀个新的进程,在这个进程中运⾏程序 并等待这个进程结束。
所以要写⼀个shell,需要循环以下过程:(使用子进程执行,与我们前面提到的王婆故事类似,保护shell父进程)

  1. 获取命令⾏
  2. 解析命令⾏
  3. 建⽴⼀个⼦进程(fork)
  4. 替换⼦进程(execvp)
  1. ⽗进程等待⼦进程退出(wait)
    获取命令行

一.获取命令行的各个数据

bash 复制代码
[lcb@hcss-ecs-1cde 6-myshell]$

仔细看这个,我们会发现,分为4部分,用户名+主机名+工作用户+$

根据我们前面学过的环境变量,学过了获取这个的接口,此处直接使用

bash 复制代码
 const char * GetHomeName()
{
 22   char*homename = getenv("HOSTNAME");
 23     return homename == NULL?"none":homename;
 24 }
 25 const char* GetUser()
 26 {
 27   char * user=getenv("USER");
 28   return user==NULL?"none":user;
 29 }
 30 const char*GetPwd()
 31 {
 32   char*Pwd=getenv("PWD");
 33   return Pwd==NULL?"node":Pwd;
 34 }

我们将每个获取都写成一个函数,让所写代码逻辑更加清晰,且如果后续需要使用,直接调用即可,方便

二.打印命令行

此处需要注意,不能直接使用printf,而是使用snprintf

两个目的

1.杜绝缓冲区溢出

2.一次格式化多段信息,无需再strcat等等

bash 复制代码
 void MakeCommand( char*cmd_promt,int size)        
 45 {                      
 47  snprintf(cmd_promt,size,FORMAT,GetHomeName(),GetUser(),GetPwd());
 48 } 
2.  打印命令行
 50 void PrintCommandline()
 51 {   
 52   char promt[MAXARGC];
 53   MakeCommand(promt,sizeof(promt));
 54   printf("%s",promt);                                                     
 55   fflush(stdout);       
 56 }

此处的MAXARGC与FORMAT,我们直接宏定义,这样方便后续一键修改

bash 复制代码
 14 #define FORMAT "[%s@%s %s]#"                                         
 15 #define MAXARGC 128 

打印结果

bash 复制代码
[lcb@hcss-ecs-1cde 6-myshell]$ make
g++ -o myshell  myshell.cc
[lcb@hcss-ecs-1cde 6-myshell]$ ./myshell
[hcss-ecs-1cde@lcb /home/lcb/code/linux/first/6-myshell]#ll

再仔细观看,会发现我们的工作目录都是绝对完善的,而Linux的工作目录只是当前的

优化:再对GerPwd的返回结果套上一层函数,进行切割

bash 复制代码
 string DirName(const char *pwd)
 36 {
 37 #define SLASH "/"
 38    std::string dir = pwd;
 39    if(dir == SLASH) return SLASH;
 40    string::size_type pos = dir.rfind(SLASH);                                                                                                                                                                                     
 41    if(pos == std::string::npos) return "BUG?";       
 42    return dir.substr(pos+1);                              
 43    }               
 44 void MakeCommand( char*cmd_promt,int size)    
 45 {                    
 46   snprintf(cmd_promt,size,FORMAT,GetHomeName(),GetUser(),DirName(GetPwd()).c_str());
 47  // snprintf(cmd_promt,size,FORMAT,GetHomeName(),GetUser(),GetPwd());
 48 }                      
bash 复制代码
[lcb@hcss-ecs-1cde 6-myshell]$ ./myshell
[hcss-ecs-1cde@lcb 6-myshell]#ll

但为了我们分清后续的命令行是OS的shell还是我们自定义的shell,所以此处我们还是不对工作目录进行切割

三.得到命令符

如果命令符只是ls,那还好,但经常还会接入 -l -s等等,中间有空格隔开

我们知道scanf与cin会以空格为结束符,所以此处不可用,我们使用fgets

此处将argv与argc定义为全局变量,是为了后续的其他函数使用

bash 复制代码
7 char * argv[MAXARGC];
 18 int argc = 0;
bash 复制代码
 bool Getcommadchar(char*out,int size)
 58 {
 59   char *c =fgets(out,size,stdin);
 60   if(c == NULL) return false;
 61 
 62   if(strlen(out)==0) return false;
 63   out[strlen(out)-1]=0;//输入会按回车,最后一个会换行符,变为0
 64   return true;
 65 }
bash 复制代码
 //3.得到命令符
 98      char  madchar[COMMAND_SIZE];
 99      if(! Getcommadchar(madchar,MAXARGC))//没有输入
100          continue;
101     printf("%s\n",madchar);

使用结果

bash 复制代码
[hcss-ecs-1cde@lcb 6-myshell]#ll
ll
[hcss-ecs-1cde@lcb 6-myshell]#\ls -l
\ls -l

四.分析命令符

上面的获取命令符已经达到预期了,那么就该分析执行了

与得到一样,我们全局变量的字符数组存储的命令也有空格,此时要进行切割

bash 复制代码
 bool Analysecommad(char*amchar)                                                                                                                                                                                                  
 67 {  
 68   //使用strtok                   
 69 #define SEP " "  
 70   //第一次调用,防止\ 为根目录 
 71   argc = 0;             
 72  argv[argc++]=strtok(amchar,SEP);  
 73  while((bool)(argv[argc++]=strtok(NULL,SEP)));  
 74 --argc;                                         
 75  return true;                                   
 76 }              

此处的argv获取strtok的切割字符,不强制bool值也行,但逻辑上不清晰,容易误认为是赋值操作,而不是逻辑判断

strtok使用用法

五.执行命令符

根据我们前面学过的进程替换,可以知道,直接系统调用exec即可,而我们在获取命令符时,使用的是全局变量的字符数据来进行存储,此处刚好可以调用execpv

根据我们前面分析shell的实现原理得知,我们需要创建子进程来执行命令

bash 复制代码
 int  Docommad()                               
 79 {                                                                                                                                    
 80   pid_t id =fork();   
 81   if(id==0)
 82   {
 83     //child
 84     execvp(argv[0],argv);
 85     exit(1);
 86   }
 87   //father
 88  pid_t ret = waitpid(id,NULL,0);
 89  (void) ret;//使用,防止报错
 90   return 0;
 91 }

效果

bash 复制代码
6-myshell  makefile  myshell  myshell.cc  shell.cc
[hcss-ecs-1cde@lcb /home/lcb/code/linux/first/6-myshell]#pwd
pwd
/home/lcb/code/linux/first/6-myshell

我们发现确实执行了,但一次就结束了,而系统的shell会不断执行,这不就是死循环吗?

因此,我们给自己的shell也加上个死循环,直到异常退出(ctrl+c)才结束

bash 复制代码
 int main()
 93 {
 94   while(true)//shell其实也是如此,死循环
 95   {
 96      PrintCommandline();
 97       //3.得到命令符
 98      char  madchar[COMMAND_SIZE];
 99      if(! Getcommadchar(madchar,MAXARGC))//没有输入
100          continue;
101     printf("%s\n",madchar);
102     //4.命令符分析
103     Analysecommad(madchar);
104     //5.执行命令符
105     Docommad();
106   }
107   return 0;
108 }

但这里我们基本的要求就已经完成了

六.实现内建命令--cd

bash 复制代码
[hcss-ecs-1cde@lcb /home/lcb/code/linux/first/6-myshell]#cd
cd
[hcss-ecs-1cde@lcb /home/lcb/code/linux/first/6-myshell]#cd
cd

我们发现,当我们的程序执行内建命令时,无结果

原因在于

  • 子进程是独立的进程,它的工作目录修改仅在自身生效,子进程退出后,父进程(你的 shell 主进程)的工作目录不会被改变。
  • 而系统 Shell(如 bash)的cd内建命令(直接在 Shell 主进程中执行),所以能真正修改 Shell 的工作目录

解决办法,在创建子进程执行命令之前,对命令符进行判断,如果为内建命令,则不再创建子进程

相关推荐
HIT_Weston2 小时前
121、【Ubuntu】【Hugo】首页板块配置:list 模板(一)
linux·ubuntu·list
Ivanqhz2 小时前
向量化计算
开发语言·c++·后端·算法·支持向量机·rust
小虾米vivian2 小时前
达梦使用dmfldr和外部表导入txt数据(windows环境)
java·服务器·数据库
The森2 小时前
万字长文外加示例:进入内核理解Linux 文件描述符(fd) 和 “一切皆文件” 理念
linux·经验分享·笔记
ffqws_2 小时前
进阶搜索:迭代加深搜索(IDS)埃及分数题解
算法·迭代加深
格林威2 小时前
相机的“对焦”和“变焦”,这二者有什么区别?
开发语言·人工智能·数码相机·opencv·算法·计算机视觉·视觉检测
LXS_3572 小时前
常用算法(下)---拷贝、替换、算术生成、集合算法
开发语言·c++·算法·学习方法
wdfk_prog2 小时前
[Linux]学习笔记系列 --[drivers]mmc]mmc
linux·笔记·学习
鲨辣椒100862 小时前
算法也能降低时间复杂度???—————算法延伸
数据结构·算法·排序算法