【Linux】简易版shell

news2024/11/25 10:06:03

在这里插入图片描述

文章目录

  • shell的基本框架
  • PrintCommandLine
  • GetCommandLine
  • ParseCommandLine
  • ExecuteCommand
  • InitEnv
  • CheckAndExecBuildCommand
  • 代码总览
  • 运行效果
  • 总结

shell的基本框架

要写一个命令行我们首先要写出基本框架。

  1. 打印命令行
  2. 获取用户输入的命令
  3. 分析命令
  4. 执行命令

基本框架的代码:

int main()
{
  //because shell must keep running
  while(true)
  {
    PrintCommandLine();  //1.use this func to print command line
    //only get <command_buffer ->output
    GetCommandLine();    //2.get user's command 

    //"is -a -b -c -d"--->"ls" "-a" "-b" "-d"
    ParseCommandLine();  //3.analyze command

    ExecuteCommand();    //4.implement command
  }
  return 0;
}

因为命令要时刻保持运行,所以我们还要加上一层循环保证时时刻刻在刷新命令行。

PrintCommandLine

接下来就需要实现打印命令行的函数了,首先看一下我们打印命令行时需要什么?
在这里插入图片描述
需要用户名,主机名,文件路径最后还需要一个$或者#

void PrintCommandLine()  //1.use this func to print command line
{
  //create command line 
  //this printf have no \n,so this result is won't be display immediately
  printf("%s",MakeCommandLine().c_str());
}

打印的这个函数,MakeCommandLine()负责返回一个string类型的表。
由于我们要获取主机名,所以需要用到获取环境变量的函数,getenv()

//get user name 
string GetUserName()
{
  string name =getenv("USER");
  return name.empty()?"None":name;
}

//get hostname 
string GetHostName()
{
  string HostName=getenv("HOSTNAME");
  return HostName.empty()?"None":HostName;
}
const int basesize=1024;
//overall situation's The working path of the current shell
char Pwd[basesize];
//overall situation's Pwd envitonment variable
char Pwdenv[basesize];
//get pwd 
string GetPwd()
{
  //string Pwd=getenv("PWD");
  if(nullptr == getcwd(Pwd,sizeof(Pwd))) return "None";
  snprintf(Pwdenv,sizeof(Pwdenv),"PWD=%s",Pwd);
  putenv(Pwdenv);
  return Pwd;
}

由于这里不能直接使用getenv(“PWD”),因为这里获取的是shell的pwd,shell的pwd一直都处在当前路径下,也就是我们的自己的shell运行的那个目录下,所以这里,这里前两个函数都很容易理解,只需要解释一下最后一个,我们来看看snprintf这个函数
在这里插入图片描述
这个函数是将后面的一个字符串以某种格式打印到前面的s当中,需要写出前面s的大小。
在这里插入图片描述
回到获取路径这个函数当中,第一个if是用来判断获取当前工作路径是否成功。如果获取成功,当前工作路径将存储在Pwd当中,snprintf,这个函数我们将Pwd这个字符串以PWD="Pwd"这样的格式打印这个,所以这里Pwdenv已经存储了环境变量的那个格式,获取了环境变量瞬时应该将环境变量表中的环境变量更新一下,所以putenv,最后将当前工作路径返回即可。
下面函数只需要调用上面获取好的接口即可,然后按照格式化打印,将对应的用户名,型号名,还有工作路径,都存储在Command_Line这个缓冲区中,然后返回到打印的函数

string MakeCommandLine()
{
  //[newuser@hcss-ecs-e091 myshell]$
  char Command_Line[basesize];
  //output
  snprintf(Command_Line,basesize,"[%s@%s %s]# ",GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());
  return Command_Line;
}

PrintCommandLine
这里打印的时候需要刷新的一下屏幕,保证一直都是在一行

void PrintCommandLine()  //1.use this func to print command line
{
  //create command line 
  //this printf have no \n,so this result is won't be display immediately
  printf("%s",MakeCommandLine().c_str());
  //refresh screen
  fflush(stdout);
}

GetCommandLine

获取命令行,首先需要一个数组,将输入的命令先存放在这个函数中,然后由下一步来执行将字符串拆分为单个命令和选项即可。

//after only call this commandline,put string to this buffer
bool GetCommandLine(char Command_Buffer[],int size)    //2.get user's command 
{
  //cannot be use scanf and cin 
  //we think:We need to treat the command line entered by the user as a complete string 
  //"ls -a -l -n" this is a complete string 
  //              array        size   stadard input
  char *result = fgets(Command_Buffer,size,stdin);
  if(!result) return false;
  //we should delete last str,because it is enter key
  Command_Buffer[strlen(Command_Buffer)-1] = 0;//currently, it is OK
  if(strlen(Command_Buffer) == 0) return false;
  return true;
}

这里不能用scanf,因为scanf和cin不能使用空格,所以我们选择用fgets,用fgets获取字符串,然后将这个字符串存在Command_Buffer中,获取完之后判断一下是否获取成功,就是检查一下获取之后的变量是否是nullptr。

注意:最后一个位置的字符需要改为0,因为我们输入的时候,会回车也是一个字符,所以应该将这个回车给去掉
当获取的命令中只有回车的时候,将回车去掉strlen就变为0了,所以只有回车时不需要解析命令,所以直接返回false。

ParseCommandLine

获取成功后我们就需要将整个字符串以空格为分隔符将其分为各个字符串来进行解析了。
这里转化为的表在shell中就是传给main参数的argc和argv,所以这里我们也需要一个变量argc和一个argv一个来计数一个来存储解析出来的表。

const int argvnum = 64;
//command line parameter list 
char *gargv[argvnum];
//for counting
int gargc;

由于我们使用的是全局变量,所以每次进入这个函数的时候都需要重置这两个全局变量

void ParseCommandLine(char Command_Buffer[],int len)  //3.analyze command
{
  (void)len;
  memset(gargv,0,sizeof(gargv));
  gargc=0;
  //"ls -a -l -n"---->ls
  //finally cut to Cogargv 
  const char *sep = " ";
  //post++
  gargv[gargc++] = strtok(Command_Buffer,sep);
  //Form a table and stop looping when the return value is nullptr 
  while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
  gargc--;
}

在这里插入图片描述
这个函数是将str以delimiters为分隔符来分割字符串,分出来的字符串会返回首地址,第一个参数只有第一次才传入对应的字符串的首地址,往后调用这个函数对同一个字符串做分割,只需要传入nullptr,所以第一个较为特殊,我们只需要对第一个做特殊处理,将其第一个分割,然后存储在gargv中,然后对应的计数++,往后都是nullptr作为第一个参数,往后分割之后返回的是nullptr就证明分割完了,所以这里我们要使用一个循环,但是由于nullptr那次也进行了++,所以实际上计数多记了一次,下面要进行–。
在这里插入图片描述

ExecuteCommand

将对应的字符串根据空格分隔符翻译为表之后,接下来就需要执行命令了,为了确保shell的稳定,所以我们用子进程来执行命令,这里创建子进程,然后判断子进程是否创建成功,创建成功后,子进程执行任务,这里最开始其实可以用execvp来进行进程替换,gargv是指令,gargv是整个选项。
在这里插入图片描述
由于进行进程替换之后就不可能执行exit了,所以exit是用来判断替换失败还是替换成功的,如果替换成功就不会exit了,如果失败就会退出,并且退出码是1,父进程等待子进程结束,回收子进程即可,如果rid为正说明回收成功,如果rid小于零等待失败直接返回false

bool ExecuteCommand()    //4.implement command
{
  //implement command 
  //let the child process execute 
  //because parent process execute the process ,if this process is failed ,myshell is hangs
  pid_t id = fork();//create child
  if(id < 0) return false;
  if(id == 0)
  {
    //child process
    //implement command 
    execvpe(gargv[0],gargv,genv);
    
    //Exit
    exit(1);//fail return 1
  }

  int status = 0;
  pid_t rid = waitpid(id,&status,0);//blocking wait 
  if(rid > 0)
  {
    //wait success
    return true;
  }
  else return false;
}

这里其实已经差不多了,但是我们还需要初始化我们的环境变量表。

InitEnv

需要顶一个全局的环境变量表

//my env array 
const int envnum = 64;
char *genv[envnum];

拥有我们自己的环境变量只需要将父进程的环境变量拷贝下来即可,进行深拷贝。

//as a shell,to get a evironment variable should from system to get 
//today, we direct get environment variable from parent process
void InitEnv()
{
  //get environment variables from parent process 
  extern char **environ;
  int index = 0;
  while(environ[index] != nullptr)
  {
    //open up the same space as environment variable 
    genv[index] =(char*)malloc(strlen(environ[index])+1);
    //copy element in genv to environ 
    strncpy(genv[index],environ[index],strlen(environ[index]+1));
    index++;
  }
  genv[index]=nullptr;
}

我们还需要对一些命令进行特殊处理,比如一些内建命令。

CheckAndExecBuildCommand

因为我们是用子进程执行的命令,所以如果我们cd的话,是子进程cd,影响不了父进程,子进程执行完cd直接退出了,所以我们需要一个函数来判断这个命令是否是内建命令
内建命令是指直接在 shell 内部实现的命令,而不是外部可执行文件。内建命令是由 shell 本身提供和执行的,因此它们不需要创建新的进程来执行。相比于外部命令,内建命令的执行速度更快,因为它们不需要通过系统调用加载可执行文件。内建命令通常用于控制 shell 的行为或执行与 shell 相关的任务。

//shell execute command by itself,the essence is shell call itself's func
bool CheckAndExecBuildCommand()//check build-in command and execute command 
{
  if(strcmp(gargv[0],"cd") == 0)
  {
    //build-in command
    if(gargc == 2)
    {
      //change path 
      chdir(gargv[1]);
    }
    return true;
  }
  //export is also a build-in command 
  else if(strcmp(gargv[0],"export") == 0)
  {
    if(gargc == 2)
    {
      AddEnv(gargv[1]);
    }
    return true;
  }
  //env is also a build-in command 
  else if(strcmp(gargv[0],"env") == 0)
  {
    for(int i = 0;genv[i];i++)
    {
      printf("%s\n",genv[i]);
    }
    return true;
  }
  return false;
}

代码总览

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<cstdlib>
using namespace std;

const int basesize=1024;
const int argvnum = 64;
//command line parameter list 
char *gargv[argvnum];

//for counting
int gargc;

//overall situation's The working path of the current shell
char Pwd[basesize];
//overall situation's Pwd envitonment variable
char Pwdenv[basesize];

//my env array 
const int envnum = 64;
char *genv[envnum];


//get user name 
string GetUserName()
{
  string name =getenv("USER");
  return name.empty()?"None":name;
}

//get hostname 
string GetHostName()
{
  string HostName=getenv("HOSTNAME");
  return HostName.empty()?"None":HostName;
}

//get pwd 
string GetPwd()
{
  //string Pwd=getenv("PWD");
  if(nullptr == getcwd(Pwd,sizeof(Pwd))) return "None";
  snprintf(Pwdenv,sizeof(Pwdenv),"PWD=%s",Pwd);
  putenv(Pwdenv);
  return Pwd;
}

//Create output format 
string MakeCommandLine()
{
  //[newuser@hcss-ecs-e091 myshell]$
  char Command_Line[basesize];
  //output
  snprintf(Command_Line,basesize,"[%s@%s %s]# ",GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());
  return Command_Line;
}

void PrintCommandLine()  //1.use this func to print command line
{
  //create command line 
  //this printf have no \n,so this result is won't be display immediately
  printf("%s",MakeCommandLine().c_str());
  //refresh screen
  fflush(stdout);
}

//after only call this commandline,put string to this buffer
bool GetCommandLine(char Command_Buffer[],int size)    //2.get user's command 
{
  //cannot be use scanf and cin 
  //we think:We need to treat the command line entered by the user as a complete string 
  //"ls -a -l -n" this is a complete string 
  //              array        size   stadard input
  char *result = fgets(Command_Buffer,size,stdin);
  if(!result) return false;
  //we should delete last str,because it is enter key
  Command_Buffer[strlen(Command_Buffer)-1] = 0;//currently, it is OK
  if(strlen(Command_Buffer) == 0) return false;
  return true;
}

void ParseCommandLine(char Command_Buffer[],int len)  //3.analyze command
{
  (void)len;
  memset(gargv,0,sizeof(gargv));
  gargc=0;
  //"ls -a -l -n"---->ls
  //finally cut to Cogargv 
  const char *sep = " ";
  //post++
  gargv[gargc++] = strtok(Command_Buffer,sep);
  //Form a table and stop looping when the return value is nullptr 
  while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
  gargc--;
}


//in my command line 
//have some command must be child process to implement
//but have some command not be child process to implement----built-in command 
bool ExecuteCommand()    //4.implement command
{
  //implement command 
  //let the child process execute 
  //because parent process execute the process ,if this process is failed ,myshell is hangs
  pid_t id = fork();//create child
  if(id < 0) return false;
  if(id == 0)
  {
    //child process
    //implement command 
    execvpe(gargv[0],gargv,genv);
    
    //Exit
    exit(1);//fail return 1
  }

  int status = 0;
  pid_t rid = waitpid(id,&status,0);//blocking wait 
  if(rid > 0)
  {
    //wait success
    return true;
  }
  else return false;
}

//add a environment variable 
void AddEnv(const char *item)
{
  int index = 0;
  while(genv[index]) index++;

  //find last location 
  genv[index] = (char*)malloc(strlen(item)+1);
  strncpy(genv[index],item,strlen(item)+1);
  genv[++index] = nullptr;

}

//shell execute command by itself,the essence is shell call itself's func
bool CheckAndExecBuildCommand()//check build-in command and execute command 
{
  if(strcmp(gargv[0],"cd") == 0)
  {
    //build-in command
    if(gargc == 2)
    {
      //change path 
      chdir(gargv[1]);
    }
    return true;
  }
  //export is also a build-in command 
  else if(strcmp(gargv[0],"export") == 0)
  {
    if(gargc == 2)
    {
      AddEnv(gargv[1]);
    }
    return true;
  }
  //env is also a build-in command 
  else if(strcmp(gargv[0],"env") == 0)
  {
    for(int i = 0;genv[i];i++)
    {
      printf("%s\n",genv[i]);
    }
    return true;
  }
  return false;
}


//as a shell,to get a evironment variable should from system to get 
//today, we direct get environment variable from parent process
void InitEnv()
{
  //get environment variables from parent process 
  extern char **environ;
  int index = 0;
  while(environ[index] != nullptr)
  {
    //open up the same space as environment variable 
    genv[index] =(char*)malloc(strlen(environ[index])+1);
    //copy element in genv to environ 
    strncpy(genv[index],environ[index],strlen(environ[index]+1));
    index++;
  }
  genv[index]=nullptr;
}


int main()
{
  //my shell's environment variable 
  InitEnv();
  //new buffer 
  char Command_Buffer[basesize];
  //because shell must keep running
  while(true)
  {
    PrintCommandLine();  //1.use this func to print command line
    //only get <command_buffer ->output
    if(!GetCommandLine(Command_Buffer,basesize))    //2.get user's command 
    {
      //get fail 
      continue; 
    }
    //"is -a -b -c -d"--->"ls" "-a" "-b" "-d"
    ParseCommandLine(Command_Buffer,strlen(Command_Buffer));  //3.analyze command
    if(CheckAndExecBuildCommand())
    {
      continue;
    }
    ExecuteCommand();    //4.implement command
  }
  return 0;
}

运行效果

在这里插入图片描述

总结

通过编写一个简易版的Linux命令行shell,我们掌握了在命令行环境中解析并运行指令的基础知识。这一项目帮助我们理解了如何通过系统调用执行外部程序、处理输入和输出,以及如何让shell与用户交互。尽管功能较为基础,但它包含了命令读取、解析和执行等关键流程,为后续学习更复杂的shell实现和系统编程提供了扎实的基础。如果有兴趣进一步扩展,可以尝试加入更多特性,如命令历史记录、自动补全、管道和重定向支持等,使这个shell更加功能丰富。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2234012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于MySQL的企业专利数据高效查询与统计实现

背景 在进行产业链/产业评估工作时&#xff0c;我们需要对企业的专利进行评估&#xff0c;其中一个重要指标是统计企业每一年的专利数量。本文基于MySQL数据库&#xff0c;通过公司名称查询该公司每年的专利数&#xff0c;实现了高效的专利数据统计。 流程 项目流程概述如下&…

盘点 2024 十大免费/开源 WAF

WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。区别于传统防火墙&#xff0c;WAF 工作在应用层&#xff0c;对基于 HTTP/HTTPS 协议的 Web 系统有着更好的防护效果&#xff0c;使其免于受到黑客的攻击。 近几年经济增速开始放缓&#xff0c;科…

鸿蒙进阶-AlphabetIndexer组件

大家好&#xff0c;这里是鸿蒙开天组&#xff0c;今天我们来学习AlphabetIndexer组件&#xff0c;喜欢就点点关注吧&#xff01; 通过 AlphabetIndexer 组件可以与容器组件结合&#xff0c;实现导航联动&#xff0c;以及快速定位的效果 核心用法 AlphabetIndexer不是容器组件…

【Unity】【游戏开发】Sprite背景闪烁怎么解决

【现象】 VR游戏中&#xff0c;给作为屏幕的3D板子加上Canvas后再加背景image&#xff0c;运行时总是发现image闪烁不定。 【分析】 两个带颜色的object在空间上完全重合时也遇到过这样的问题&#xff0c;所以推测是Canvas的image背景图与木板的面重合导致。 【解决方法】 …

sublime Text中设置编码为GBK

要在sublime Text中设置编码为GBK&#xff0c;请按照以下步骤操作 1.打开Sublime Text编辑器, 2.点击菜单栏中的“Preferences”(首选项)选项&#xff0c;找打Package Control选项。 3.点击Package Control&#xff0c;随后搜索Install Package并点击&#xff0c;如下图 4.再…

队列与栈的代码对比(Java)

目录 链表实现队列 数组实现队列 链表实现栈 数组实现栈 图片: 链表实现队列 package Queue;import java.util.Iterator;public class LinkedListQueue <E> implements Queue<E>, Iterable<E>{//单向环形哨兵链表//节点类private static class Node<…

一些常规IP核功能

一,util_vector_logic util_vector_logic 主要支持以下类型的逻辑操作: 逻辑与(AND): 当所有输入都为1时,输出为1,否则为0。逻辑或(OR): 当任意输入为1时,输出为1,否则为0。逻辑非(NOT): 当输入为1时,输出为0;输入为0时,输出为1。异或(XOR): 当输入中有奇…

Docker篇(Docker安装)

目录 一、Centos7.x 1. yum 包更新到最新 2. 安装需要的软件包 3. 设置 yum 源为阿里云 4. 安装docker 5. 安装后查看docker版本 6. 设置ustc镜像源 二、CentOS安装Docker 前言 1. 卸载&#xff08;可选&#xff09; 2. 安装docker 3. 启动docker 4. 配置镜像加速 …

【c++ gtest】使用谷歌提供的gtest和抖音豆包提供的AI大模型来对代码中的函数进行测试

【c gtest】使用谷歌提供的gtest和抖音豆包提供的AI大模型来对代码中的函数进行测试 下载谷歌提供的c测试库在VsCode中安装抖音AI大模型找到c项目文件夹&#xff0c;使用VsCode和VS进行双开生成gtest代码进行c单例测试 下载谷歌提供的c测试库 在谷歌浏览器搜索github gtest, 第…

google adsense广告费中国收款结算被银行拒解决办法

多年前搞了几个网站&#xff0c;挂了谷歌google adsense广告&#xff0c;不知道不觉到了100美金最低结算&#xff0c;谷歌给我打款&#xff0c;之前是绑定交银银行的。被银行镜内登陆谷歌不合法不合规给拒绝入账&#xff0c;把美金退回了&#xff0c;怎么办&#xff1f; googl…

蓝桥杯 区间移位--二分、枚举

题目 代码 #include <stdio.h> #include <string.h> #include <vector> #include <algorithm> #include <iostream> using namespace std; struct node{ int a,b; }; vector<node> q; bool cmp(node x,node y){ return x.b <…

书生第四期实训营基础岛——L1G1000书生大模型全链路开源体系

书生浦语大模型开源开放体系 书生浦语开源一周年历史 2023.7.6&#xff1a;InternLM-7B开源率先免费商用发布全链条开源工具体系2023.9.20&#xff1a;InternLM-20B开源&#xff0c;开源工具链全线升级2024.1.17&#xff1a;InternLM2开源&#xff0c;性能超最新同量级开源模…

单元测试(Junit)

系统—模块—子模块&#xff0c;子模块中不可分割的程序单元的测试&#xff0c;单元的粒度根据实际情况可能是 类或方法等。 面向对象编程中&#xff0c;最小单元就是方法。 单元测试目的是在集成测试和功能测试之前对系统可测试单元进行逐一检查和验证。 单元测试基本原则 …

MySQL表的增删改查(CRUD3约束)

这次我们开始先不复习嗷&#xff0c;等到把数据表的删除说完咱们统一&#xff0c;总结书写 1.数据表的删除&#xff1a; 语法&#xff1a; 1. 使用 DROP TABLE 语句删除单个表 基本语法&#xff1a;DROP TABLE [IF EXISTS] table_name; table_name是要删除的表的名称。IF EXIS…

go中Println和Printf的区别

Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 go中Println和Printf的区别 package mainimport ( "fmt" )//TIP To run your code, right-click the c…

【系统面试篇】进程和线程类(1)(笔记)——区别、通讯方式、同步、互斥、锁分类

目录 一、问题综述 1. 进程和线程的区别&#xff1f; 2. 进程的状态有哪些&#xff1f; 3. 进程之间的通信方式? &#xff08;1&#xff09;管道 &#xff08;2&#xff09;消息队列 &#xff08;3&#xff09;共享内存 &#xff08;4&#xff09;信号量 &#xff08…

编译安装并刷写高通智能机器人SDK

The Qualcomm Intelligent Robotics Product SDK (QIRP SDK) 高通智能机器SDK基于ROS2进行开发&#xff0c;此SDK适用于高通linux发行版本&#xff0c;QIRPSDK中提供以下内容&#xff1a; ROS 包中用于支持机器人应用程序开发的参考代码 用于评估机器人平台的端到端场景示例集…

网页版五子棋—— WebSocket 协议

目录 前言 一、背景介绍 二、原理解析 1.连接过程&#xff08;握手&#xff09; 2.报文格式 三、代码示例 1.服务端代码 &#xff08;1&#xff09;TestAPI 类 &#xff08;2&#xff09;WebSocketConfig 类 2.客户端代码 3.代码演示 结尾 前言 从本篇文章开始&am…

鸿蒙应用开发:下载功能

鸿蒙系统不断发展&#xff0c;有与安卓、iOS 形成三足鼎立之势&#xff0c;且其在智能手机、智能穿戴、车载、家居等行业领域的应用越来越广泛。作为开发者&#xff0c;如何抓住鸿蒙生态崛起的机遇&#xff0c;解决开发挑战&#xff0c;创造更好的应用体验&#xff1f;欢迎您和…

小白直接冲!BiTCN-BiLSTM-Attention双向时间卷积双向长短期记忆神经网络融合注意力机制多变量回归预测

小白直接冲&#xff01;BiTCN-BiLSTM-Attention双向时间卷积双向长短期记忆神经网络融合注意力机制多变量回归预测 目录 小白直接冲&#xff01;BiTCN-BiLSTM-Attention双向时间卷积双向长短期记忆神经网络融合注意力机制多变量回归预测效果一览基本介绍程序设计参考资料 效果一…