Linux —— 进程控制 - mini shell

目录

5.自主Shell命令行解释器

[以下是mini shell的代码实现,仅供参考:](#以下是mini shell的代码实现,仅供参考:)

[6. 总结](#6. 总结)


5.自主Shell命令行解释器

到此为止,代码结构搞定!!

将上面以 .c 结尾的文件,重命名为 .cc 文件。Makefile文件做以下调整:

运行结果:

所以命令行解释器是一个软件 --------- 是一个死循环的软件

运行结果:

但是此时打印出来的结果就不对了,所以要获取用户输入的命令。

此时,在运行程序的话,就会停下来等待用户的输入:

从运行的结果来看,会多一个空行,原因如下:

再次,make:

我们在写打印的时候是没有加 "\n"的,但是从上面的输出结果却自动换行了,是自动加上了 "\n" 的。

这个是因为我们自己在输入 :"ls -a -l -n + 回车",用户按了回车键

此时就是一个纯净的命令行字符串了。

图中的 cmd_str_buff[] 绝对不可能是空字符串的,因为至少要按一下回车键。如果是空串的话,需要进一步进行判断:

运行结果:

这样看,不好看。你给我什么我就返回什么,但是我要给你手动加一个 "\n"

解析出来的命令行参数表必须也得被子进程继承下去,所以将此命令行参数表定义成全局的。

修改myshell.h:

运行结果:

用全局变量的话,需要注意的是,在每次用之前都要进行清空的处理:

运行结果:[基本正确]

但是还是有一点错误:

修改代码:

不想调试了,直接注释掉语句:#define DEBUG

此时将不再回显:

命令行解释器,本质上就是输出命令行,阻塞着等待你做输入,输入对应的命令做解析,解析完之后做fork,让子进程去执行。

最基础的命令行解释器就做好了,结构、完整代码如下:

cpp 复制代码
//Makefile
myshell:main.cc myshell.cc
    g++ -o $@ $^ -std=c++11                                      
.PHONY:clean
clean:
    rm -f myshell
cpp 复制代码
//myshell.h
#ifndef __MYSHELL_H__
#define __MYSHELL_H__

#include <stdio.h>

#define ARGS 64

void InitGlobal();
void PrintCommandPrompt();
bool GetCommandString(char cmd_str_buff[],int len);
bool ParseCommandString(char cmd[]);
void ForkAndExec();                                              
#endif
cpp 复制代码
//myshell.cc
#include "myshell.h"
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

//命令行参数表,故意定义为全局的
char *gargv[ARGS] = {NULL};
int gargc = 0;

void Debug()
{
    printf("Hello Shell!\n");
}

static std::string GetUserName()   //static不用在.h文件进行声明,仅在本文件使用
{
     std::string username = getenv("USER");
     return username.empty() ? "None" : username;
     //if(username.empty())
     //    return "None";
     //return username;
}
                                                                                                                                                                       
static std::string GetHostName()
{
     std::string hostname = getenv("HOSTNAME");
     return hostname.empty() ? "None" : hostname;
}
static std::string GetPwd()
{
     std::string pwd = getenv("PWD");
     return pwd.empty() ? "None" : pwd;
}

 //输出提示符
 void PrintCommandPrompt()                                                                                                                                              
 {
     std::string user = GetUserName();
     std::string hostname = GetHostName();
     std::string pwd = GetPwd();
      printf("[%s@%s %s]# ",user.c_str(),hostname.c_str(),pwd.c_str());
 }
  
 //获取用户的键盘输入
  bool GetCommandString(char cmd_str_buff[],int len)
  {
      if(cmd_str_buff == NULL || len < 0)
          return false;
      char *res = fgets(cmd_str_buff,len,stdin);
      if(res == NULL)
          return false;
      // ls -a -l\n  --->  ls -a -l\0
      cmd_str_buff[strlen(cmd_str_buff)- 1] = 0;
      return strlen(cmd_str_buff) == 0 ? false :true;
  }
  
  bool ParseCommandString(char cmd[])
  {
      if(cmd == NULL)
          return false;
#define SEP " "
      //"ls -a -l" -> "ls" "-a" "-l"
      gargv[gargc++] = strtok(cmd,SEP);
      // 整个数组,最后以NULL结尾
      while((bool)(gargv[gargc++] = strtok(NULL,SEP)));
      // 上一行代码为空时,也加了一次,回退一次,命令行参数的个数
      gargc--;                                                                                                                                                           
  
  //#define DEBUG
  #ifdef DEBUG
      printf("gargc:%d\n",gargc);
      printf("-----------------------------------\n");
      for(int i = 0; i < gargc ;i++)
      {
          printf("gargv[%d]:%s\n",i,gargv[i]);
      }
      printf("-----------------------------------\n");
      for(int i = 0;gargv[i];i++)
      {
          printf("gargv[%d]:%s\n",i,gargv[i]);
      }
  
  #endif 
  
      return true;
  }
  
  void InitGlobal()
  {
      gargc = 0;
      memset(gargv,'0',sizeof(gargv));
  }
void ForkAndExec()
 {
     pid_t id = fork();                                                                                                                                                 
     if(id < 0)
     {
         perror("fork");  //error ---> errstring;
         return;
     }
     else if(id == 0)
     {
         //子进程 --- 执行对应的命令,程序替换 execvp
         execvp(gargv[0],gargv);
         exit(0);
     }
     else
     {
         //父进程 --- 进程等待 
         pid_t rid = waitpid(id,nullptr,0);
         if(rid > 0)
         {
             //等待成功
         }
     }
 
 }
cpp 复制代码
//main.cc
#include "myshell.h"

 #define SIZE 1024
 
 int main()
 {
     char commandstr[SIZE];
     while(true)
     {
       //0.初始化操作
       InitGlobal();
       //1.输出命令行提示符
        PrintCommandPrompt();
  
        //2.获取用户输入的命令
       if(!GetCommandString(commandstr,SIZE))
           continue;
  
       // printf("%s\n",commandstr);
  
       //3. "ls -a -l"  --> 拆解为 "ls" "-a" "-l"
       //对命令字符串,进行解析 -> 解析成命令行参数表
       ParseCommandString(commandstr);
  
       //4.执行命令,让子进程来执行,这也是在shell当中创建子进程的原因
       //(不敢直接就程序替换,替换之后谁来进行初始化的操作之类呢?) 
       ForkAndExec();
  
      }
      return 0;
  }                         

基于现在的代码来理解历史知识:

为什么 cd .. 命令无法进行回退??

因为是在让子进程执行cd .. ,父进程路径根本就没有变化。

我们之前进行路径切换,本质是父进程bash在进行切换,fork,路径就会被子进程继承下去,pwd,查到新路径!!!

所以像 cd 这样的命令,就不能让子进程运行,而应该让 shell 自己运行!!!

输入cd .. 之后 pwd ,路径确实发生了变化,但是命令行提示符的路径却是一直没有变化的。

此时的pwd如下:

但是在env中查看pwd变量时,并没有变化:

应该如何更新环境变量呢?

再次查看env,同样 pwd也是不变的:

pwd环境变量需要bash自己动态的更新,新的路径

环境变量本质上是一张指针数组的表,由我们的进程真实的记录下来的,有对应的pwd。

曾经说过:环境变量是你的 shell 从配置文件加载进来的,原则上是你自己对应的shell内部就应该有一张 char *genv[]这样的环境变量所对应的表,再加载配置文件的时候,给每个指针new空间,再将每个指针指向对应的空间,把字符串往里面拷,但是在myshell.cc中没有用环境变量表,因为当前的shell环境变量表继承自他的父进程真正的bash进程的环境变量表,没有从配置文件中加载进来。但是又要实现出来一个环境变量更新的情况:

成功!!!

系统的命令行提示的内容只有当前路径的最后一个单词,但是我们自己实现的却有一长串,如何更改代码?

运行结果:

内建命令:是shell自己执行的命令。如同shell执行一个自己的函数

shell是会拿到最近的一个子进程退出时的退出码,我们当前写的命令行解释器还没有支持此项功能,那么echo ? 中的 ? 到底是什么?

echo 命令也是内键命令:

a是本地变量,是无法被子进程继承的。但是,echo $a,也能把子进程访问出来。这说明echo命令也是内键命令,不然无法访问shell内部的变量。

运行结果:

mini shell支持打印环境变量:

运行结果:

以上就是我们mini shell的实现,但是只支持基本功能,还有一些功能未能实现,例如:重定向功能,还会在后续的文章中进行功能的添加。

以下是mini shell的代码实现,仅供参考:

cpp 复制代码
//myshell.h
#ifndef __MYSHELL_H__
#define __MYSHELL_H__

#include <stdio.h>

#define ARGS 64

void InitGlobal();
void PrintCommandPrompt();
bool GetCommandString(char cmd_str_buff[],int len);
bool ParseCommandString(char cmd[]);
void ForkAndExec();   
bool BuiltInCommandExec();                                            
#endif
cpp 复制代码
//myshell.cc
#include "myshell.h"
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
 
//命令行参数表,故意定义为全局的
char *gargv[ARGS] = {NULL};
int gargc = 0;
char pwd[1024];  //全局变量空间,保存当前shell进程的工作路径
int lastcode = 0;

void Debug()
{
     printf("Hello Shell!\n");
}

static std::string GetUserName()   //static不用在.h文件进行声明,仅在本文件使用
{
     std::string username = getenv("USER");
     return username.empty() ? "None" : username;
    //if(username.empty())
     //    return "None";
	 //returnusername;                                                                                                                                                                                                                  
}

static std::string GetHostName()
{
     std::string hostname = getenv("HOSTNAME");
     return hostname.empty() ? "None" : hostname;
}
 
static std::string GetPwd()
{
     //环境变量的变化,可能会依赖于进程,pwd需要shell自己更新环境变量的值
     //std::string pwd = getenv("PWD");
     //return pwd.empty() ? "None" : pwd;
 
     char temp[1024];
     getcwd(temp,sizeof(temp));
     //顺便更新一下shell自己的环境变量pwd
     snprintf(pwd,sizeof(pwd),"PWD=%s",temp);
     putenv(pwd);  //导出,导成环境变量
 
     // /home/sy/linux
     std::string pwd_lable = temp;
     const std:: string pathsep = "/";
     auto pos = pwd_lable.rfind(pathsep);  //倒着去找,找到第一个分割符就停下来
     if(pos == std::string::npos)     {                                                                                                                                                                                                                                   
         return "None";
     }
 
     //              /linux,从l开始,截取linux这么长       
     pwd_lable = pwd_lable.substr(pos+pathsep.size());
     
     return pwd_lable.empty() ? "/" :pwd_lable;
 
 }
 
 static std::string GetHomePath()
 {
     std::string home = getenv("HOME");
     return home.empty() ? "/" :home;
 }
 
 //输出提示符
 void PrintCommandPrompt()
 {
     std::string user = GetUserName();
     std::string hostname = GetHostName();
     std::string pwd = GetPwd();
     printf("[%s@%s %s]# ",user.c_str(),hostname.c_str(),pwd.c_str());
 }
 
 //获取用户的键盘输入
 bool GetCommandString(char cmd_str_buff[],int len)
 {
     if(cmd_str_buff == NULL || len < 0)
         return false;
     char *res = fgets(cmd_str_buff,len,stdin);
     if(res == NULL)
         return false;
     // ls -a -l\n  --->  ls -a -l\0
     cmd_str_buff[strlen(cmd_str_buff)- 1] = 0;
     return strlen(cmd_str_buff) == 0 ? false :true;
 }
 
 bool ParseCommandString(char cmd[])                                                                                                                                                                                                     
 {
     if(cmd == NULL)
         return false;
 #define SEP " "
     //"ls -a -l" -> "ls" "-a" "-l"
     gargv[gargc++] = strtok(cmd,SEP);
     // 整个数组,最后以NULL结尾
     while((bool)(gargv[gargc++] = strtok(NULL,SEP)));
     // 上一行代码为空时,也加了一次,回退一次,命令行参数的个数
     gargc--;
 
 //#define DEBUG
 #ifdef DEBUG
 bool BuiltInCommandExec(); 
     printf("gargc:%d\n",gargc);
     printf("-----------------------------------\n");
     for(int i = 0; i < gargc ;i++)
     {
         printf("gargv[%d]:%s\n",i,gargv[i]);
     }
     printf("-----------------------------------\n");
     for(int i = 0;gargv[i];i++)
     {
         printf("gargv[%d]:%s\n",i,gargv[i]);
     }
 
 #endif 
 
     return true;
 }

 void InitGlobal()
 {
     gargc = 0;
     memset(gargv,'0',sizeof(gargv));
 }
 
  void ForkAndExec()
 {
     pid_t id = fork();
     if(id < 0)
     {
         perror("fork");  //error ---> errstring;
         return;
     }
     else if(id == 0)
     {
         //子进程 --- 执行对应的命令,程序替换execvp                                                                                                                                                                                     
        execvp(gargv[0],gargv);
         exit(0);
     }
     else
     {
         //父进程 --- 进程等待 
         int status = 0;
         pid_t rid = waitpid(id,&status,0);
         if(rid > 0)
         {
             //等待成功
             lastcode = WEXITSTATUS(status);
         }
     }
 
 }
 
 bool BuiltInCommandExec()
 {
     //gargv[0]
     std::string cmd = gargv[0];
     bool ret = false;
     if(cmd == "cd")
     {
         //内键命令
         if(gargc == 2)
         {
             std::string target = gargv[1];
             if(target == "~")
             {
                 ret = true;
                 chdir(GetHomePath().c_str());
             }
             else
             {
                 ret = true;
                chdir(gargv[1]);
            }
                                                                                                                                                                                                                                         
         }
         else if(gargc == 1)
         {
             ret = true;
            chdir(GetHomePath().c_str());
         }
         else
         {
             //BUG
         }
     }
     else if(cmd == "echo")
     {
         if(gargc == 2)
         {
             std::string args = gargv[1];
             if(args[0] == '$')
             {
                 if(args[1] == '?')
                 {
                     printf("lastcode:%d\n",lastcode);
                     //echo本身也是一条命令,所以将lastcode置为0
                     lastcode = 0;
                     ret = true;  //内键命令
                 }
                 else{
                     const char *name = &args[1];
                     printf("%s\n",getenv(name));
                     lastcode = 0;
                     ret = true;
                 }
             }
             else
             {
                 printf("%s\n",args.c_str());
                 ret = true;
             }
         }
     }
     return ret;
 }
cpp 复制代码
//main.cc

int main()
 {
     char commandstr[SIZE];
     while(true)
     {
        //0.初始化操作
        InitGlobal();
        //1.输出命令行提示符
        PrintCommandPrompt();
  
        //2.获取用户输入的命令
       if(!GetCommandString(commandstr,SIZE))
           continue;
  
       // printf("%s\n",commandstr);
  
       //3. "ls -a -l"  --> 拆解为 "ls" "-a" "-l"
       //对命令字符串,进行解析 -> 解析成命令行参数表
       ParseCommandString(commandstr);
  
       //4.检查命令,内键命令,要让shell自己执行!!!
      if(BuiltInCommandExec())                                                                                                                                                                                 
      {
          continue;
      }
 
       //5.执行命令,让子进程来执行,这也是在shell当中创建子进程的原因
       //(不敢直接就程序替换,替换之后谁来进行初始化的操作之类呢?) 
       ForkAndExec();
  
      }
      return 0;
 }
cpp 复制代码
//Makefile

  myshell:main.cc myshell.cc
      g++ -o $@ $^ -std=c++11                                                                                                                                                                                  
  .PHONY:clean
  clean:
      rm -f myshell

6. 总结

运行结果:

相关推荐
眷蓝天1 小时前
Docker 镜像瘦身:从 GB 到 MB 的优化实践
运维·docker·容器
程序员黄老师2 小时前
Windows文件移动到Linux上的坑
linux·运维·服务器
shizhan_cloud2 小时前
自动化部署Kubernetes集群
运维·kubernetes
mounter6252 小时前
【内核前沿】Linux IPC 迎来大变局?POSIX 消息队列增强、io_uring IPC 与 Bus1 十年回归
linux·运维·服务器·kernel·ipc·io_uring
不怕犯错,就怕不做3 小时前
Linux-Sensor驱动移植与调试(转载)
linux·驱动开发·嵌入式硬件
wzl202612133 小时前
企业微信定时群发技术实现与实操指南(原生接口+工具落地)
java·运维·前端·企业微信
island13143 小时前
最详细VMware Workstation 17 上安装 Ubuntu 系统
linux·数据库·ubuntu
2401_895521343 小时前
Linux下安装Redis
linux·运维·redis
网络小白不怕黑3 小时前
2.1VMware部署Windows_server_2008_R2
运维·服务器