自制shell命令行解释器,深入理解Linux系统命令行实现原理

个人主页:敲上瘾-CSDN博客

个人专栏:Linux学习、游戏、数据结构、c语言基础、c++学习、算法

目录

​编辑

1.打印命令提示符

​编辑

2.获取用户输入指令

3.重定向分析

4.命令行参数表与环境变量表

5.命令解析

6.命令执行

6.1.创建子进程

6.2.文件重定向

6.3.处理内建命令

7.源码


        在实现shell的时候我们先创建自己myshell目录,在目录中创建myshell.cc文件,因为shell本来是用c语言写的,但为了方便我们这里使用c和c++混编。

首先我们做一个整体框架:

1.打印命令提示符

首先我们需要给用户显示提示信息,就像我们在使用shell时所看到的提示信息一样,如下:

对它进行一一分析:

所以我们可以这样定义一个宏: 

 #define ROOT_PROMPT "[%s@%s %s]# "#define OTHER_PROMPT "[%s@%s %s]$ "

        对于用户名,主机号我们可以通过getenv从环境变量中得到,但是获取当前路径我们不能使用getenv,因为我们myshell的环境变量使用的是父进程的环境变量,我们在当前使用cd命令切换路径环境变量中的pwd并不会有改变。

        所以获取当前路径,我们可以使用getcwd,直接查询操作系统的文件系统,获取当前进程的工作目录的绝对路径。而不用依赖环境变量。当然我们最好单独设计一个GetPwd把它封装起来,这样也方便把当前路径加入到环境变量中。如下:


char* GetPwd()
{char* const pwd=getcwd(cwd,sizeof(cwd));if(pwd!=NULL){snprintf(envpwd,sizeof(envpwd),"PWD=%s",pwd);putenv(envpwd);}return pwd;                                                                                                                                            
}void Print()
{string s=GetPwd();string tmp;//去掉路径,只保留目录名for(int i=s.size()-1;i>=0;i--){  if(s[i]=='/') break;tmp.push_back(s[i]);}reverse(tmp.begin(),tmp.end());                                                                                                                        if(strcmp(getenv("USER"),"root")==0)printf(ROOT_PROMPT,getenv("USER"),getenv("HOSTNAME"),tmp.c_str());elseprintf(OTHER_PROMPT,getenv("USER"),getenv("HOSTNAME"),tmp.c_str());
}

2.获取用户输入指令

        获取用户输入,因为用户输入的命令行参数是一个字符串,中间含有空格。所以我们不用scanf,cin进行输入。这里我们使用fgets。

bool GetCommand(char out[],int size)
{char* c=fgets(out,size,stdin);if(c==NULL) return false;out[strlen(out)-1]=0;if(strlen(out)==0) return false;else return true;
}

3.重定向分析

        在用户输入的指令中可能汉含有重定向操作,所以我们要提前特殊处理一下字符串,并把它做一个分割。

既然是重定向,也就是我们打开需要重向到的那个文件,所以我们需要获取打开方式文件名

重定向有三种:

  • <:输入重定向(以读的方式打开文件)
  • >:输出重定向(以写的方式打开文件)
  • >>:追加重定向(以追加的方式打开文件)

所以我们可以使用宏来标记这些情况。

//重定向
#define NONE_REDIR 0//无重定向操作
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
int redir=NONE_REDIR;//记录重定向情况
string filename;//记录需要重定向到的文件
bool AnalyseRedir(char arr[])    
{    redir=NONE_REDIR;//这里还保留着上次执行的状态,需要初始化一下filename.clear();//同理,保留着上次执行的文件名,需要清理int end=strlen(arr)-1;    while(end>=0)    {    if(arr[end]=='<')    {    redir=INPUT_REDIR;    arr[end++]=0;    filename = GetFileName(arr,end);//这里的文件名就是end以后从非空格的部分开始到结束,这里就不再展示,下面会给出源码    break;    }    else if(arr[end]=='>')    {    if(end-1>=0&&arr[end-1]=='>')    {    redir=APPEND_REDIR;    arr[end-1]=0;//使后面再做命令行参数分析时,方便与后面内容分开  }    else redir=OUTPUT_REDIR;    filename = GetFileName(arr,end+1);    arr[end] = 0;    break;    }    else end--;     }    return true;    
}  

4.命令行参数表与环境变量表

在shell中有两张表命令行参数表环境变量表,实质都是字符串数组。

  • 命令行参数表:用来储存用户输入的命令行参数。
  • 环境变量表:用来储存当前进程的属性和状态。

所以我们可以这样做一个全局变量:

#define MAXARGV 128
#define MAXENV 200
//命令行参数表
char* g_argv[MAXARGV];
int g_argc=0;
//环境变量表
char* g_env[MAXENV];
int g_envc=0;

        环境变量表需要我们在程序启动时就将它导入, 当然程序启动后环境变量默认是父进程的,所以我们可以重新开辟空间把原环境变量的数据拷贝过来,然后再把environ更新为新的地址。具体实现请参考下文源码。

5.命令解析

        刚才我们获取到了用户的输入得到一个字符串,需要把它一个一个按空格分开,来得到一张命令行参数表。方便后面做进程替换。

//命令行参数表
char* g_argv[MAXARGV];
int g_argc=0;
void AnalyseCommand(char* out)    
{    
#define SYMBOL " "    g_argc=0;    g_argv[g_argc++]=strtok(out,SYMBOL);    while((bool)(g_argv[g_argc++]=strtok(nullptr,SYMBOL)));    g_argv[g_argc]=0;    
}

6.命令执行

6.1.创建子进程

        shell执行命令的实质就是进程替换,我们在做进程替换的时候不想结束父进程,那么需要我们创建一个新的子进程,让子进程来做替换。

6.2.文件重定向

  • pcd文件数组:储存了这个pcb打开的所有文件信息地址。
  • 文件描述符(记fd):pcb的文件数组中的一个下标,0下标的文件为标准输入流,1下标的文件为标准输出流,2下标的文件为标准错误流,这三个都是系统默认打开的文件。
  • 重定向:系统在对文件进行操作时只认fd,所以重定向的实质就是一个fd位置的信息被其他fd的信息覆盖。

        >,>>,指的都是从原来的标准输出(fd=1)重定向到某个文件,< 从原来的标准输入(fd=0)重定向到某个文件。所以这里我们只需要打开新的文件并获取到它的fd,然后使用dup2把新文件的地址信息覆盖到fd=1的文件上就行。然后关闭新文件的fd。

//重定向文件打开+dup2    
if(redir!=NONE_REDIR)    
{    int fd=-1;    if(redir==INPUT_REDIR)    {    fd=open(filename.c_str(),O_RDONLY);    if(!(fd>=0)) exit(1);    dup2(fd,0);    
}    else if(redir==OUTPUT_REDIR)    {    fd=open(filename.c_str(),O_CREAT | O_WRONLY | O_TRUNC,0666);    if(!(fd>=0)) exit(1);    dup2(fd,1);    }    else    {    fd=open(filename.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);    if(!(fd>=0)) exit(1);    dup2(fd,1);    }    close(fd);
} 

6.3.处理内建命令

        有一些命令比如cd,是一个内建命令,子进程是无法完成的,需要系统来执行。我们可以使用chdir来完成,chdir函数声明如下:

int chdir(const char *path);

它的作用是进入某个目录,需要传一个目录的路径。 

7.源码

注意:在做编译链接时需要用指令g++,并且加入-std=c++11选项

#include<iostream>    
#include<cstdio>    
#include<cstring>    
#include<string>    
#include<sys/types.h>    
#include<unistd.h>    
#include<vector>    
#include<cstdlib>    
#include<sys/wait.h>    
#include<algorithm>    
#include<sys/stat.h>    
#include<fcntl.h>    
using namespace std;    
#define COMMAND_SIZE 1024    
#define MAXARGV 128    
#define MAXENV 200    
#define ROOT_PROMPT "[%s@%s %s]# "    
#define OTHER_PROMPT "[%s@%s %s]$ "    
//重定向    
#define NONE_REDIR 0    
#define INPUT_REDIR 1    
#define OUTPUT_REDIR 2    
#define APPEND_REDIR 3    
int redir=NONE_REDIR;    
string filename;    
//命令行参数表    
char* g_argv[MAXARGV];    
int g_argc=0;    
//环境变量表    
char* g_env[MAXENV];    
int g_envc=0;    
//路径记录    
char cwd[1024];    
char envpwd[1024];    
char* GetPwd()    
{    char* const pwd=getcwd(cwd,sizeof(cwd));    if(pwd!=NULL)    {    snprintf(envpwd,sizeof(envpwd),"PWD=%s",pwd);    putenv(envpwd);    }    return pwd;    
}    
bool GetCommand(char out[],int size)    
{    char* c=fgets(out,size,stdin);    if(c==NULL) return false;    out[strlen(out)-1]=0;    if(strlen(out)==0) return false;    else return true;    
}    
string GetFileName(char arr[],int p)    
{    if(arr[p]==' ') p++;    return string(arr+p);    
}    
void PushEnv()    
{    for(int i=0;environ[i];i++)    {    g_env[i]=(char*)malloc(strlen(environ[i])+1);    strcpy(g_env[i],environ[i]);    g_envc++;    }    g_env[g_envc]=NULL;    for(int i=0;g_env[i];i++)    putenv(g_env[i]);  environ=g_env;    
}    
void Print()    
{    string s=GetPwd();    string tmp;    for(int i=s.size()-1;i>=0;i--)    {    if(s[i]=='/') break;    tmp.push_back(s[i]);    }    reverse(tmp.begin(),tmp.end());    if(strcmp(getenv("USER"),"root")==0)    printf(ROOT_PROMPT,getenv("USER"),getenv("HOSTNAME"),tmp.c_str());    else    printf(OTHER_PROMPT,getenv("USER"),getenv("HOSTNAME"),tmp.c_str());    
}    
bool AnalyseRedir(char arr[])    
{    redir=NONE_REDIR;    filename.clear();    int end=strlen(arr)-1;    while(end>=0)    {    if(arr[end]=='<')    {    redir=INPUT_REDIR;    arr[end++]=0;    filename = GetFileName(arr,end);    break;    }    else if(arr[end]=='>')    {    if(end-1>=0&&arr[end-1]=='>')    {    redir=APPEND_REDIR;    arr[end-1]=0;    }    else    {    redir=OUTPUT_REDIR;    }    filename = GetFileName(arr,end+1);    arr[end] = 0;    break;    }    else end--;     }    return true;    
}    
void AnalyseCommand(char* out)    
{    
#define SYMBOL " "    g_argc=0;    g_argv[g_argc++]=strtok(out,SYMBOL);    while((bool)(g_argv[g_argc++]=strtok(nullptr,SYMBOL)));    g_argv[g_argc]=0;    
}    
bool Cd()    
{    char* where;    if(g_argc==1)    where=getenv("HOME");    else    where=g_argv[1];    chdir(where);    return true;    
}    
bool Echo()    
{    //... ...return true;    
}    
void RunCmd()    
{    pid_t id=fork();    if(id==0)    {    //重定向文件打开+dup2    if(redir!=NONE_REDIR)    {    int fd=-1;    if(redir==INPUT_REDIR)    {    fd=open(filename.c_str(),O_RDONLY);    if(!(fd>=0)) exit(1);    dup2(fd,0);    }    else if(redir==OUTPUT_REDIR)    {    fd=open(filename.c_str(),O_CREAT | O_WRONLY | O_TRUNC,0666);    if(!(fd>=0)) exit(1);    dup2(fd,1);    }    else    {    fd=open(filename.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);    if(!(fd>=0)) exit(1);    dup2(fd,1);    }    close(fd);} string tmp="cd";    if(g_argv[0]==tmp)    {    if(!Cd())  printf("-myshell: command not found\n");    }    else    {    execvp(g_argv[0],g_argv);    printf("-myshell: %s: command not found\n",g_argv[0]);    exit(1);    }    }    pid_t p=waitpid(id,NULL,0);    (void)p;    
}    
int main()    
{       //载入环境变量    PushEnv(); while(1)    {    //命令行提示打印    Print();    //获取用户输入命令    char out[COMMAND_SIZE];    GetCommand(out,sizeof(out));    //重定向解析    AnalyseRedir(out);    //命令解析    AnalyseCommand(out);    //命令执行    RunCmd();    }    return 0;
} 

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

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

相关文章

ADB常用各模块操作命令

目录 1. 基本设备信息获取 2. 设备连接与管理 3. 文件管理 4. 进程与应用管理 5. 日志与调试 6. 调试和性能 7. 设备操作 8.adb命令的应用场景 1. 基本设备信息获取 获取设备的系统版本&#xff0c;获取设备安卓版本号&#xff1a; adb shell getprop ro.build.version.…

Mac M1 安装数据库

1. Docker下载 由于Sqlserver和达梦等数据库&#xff0c;不支持M系列的芯片&#xff0c;所以我们通过docker安装 下载并安装docker: https://www.docker.com/get-started/ 安装完成后&#xff0c;打开docker 2. SQL Server 安装 2.1 安装 打开终端&#xff0c;执行命令 doc…

渗透测试实验环境搭建

下载虚拟机镜像 5个虚拟机镜像&#xff0c;其中Linux攻击机我选择用最新的kali Linux镜像&#xff0c;其余的均使用本书配套的镜像。 网络环境配置 VMware虚拟网络编辑器配置&#xff1a; 将VMnet1和VMnet8分别设置IP为192.168.10.0/24和10.10.10.0/24。 虚拟机镜像配置 攻击机…

Linux shell脚本(一)

监控内存和磁盘容量&#xff0c;小于给定值时报警 [rootlinux-lyz test1]# ./monitor.sh & [1] 23110 # 提取根分区剩余空间 disk_size$(df / | awk /\//{print $4})# 提取内存剩余空间 mem_size$(free | awk /Mem/{print $4}) while : do # 注意内存和磁盘提取空间大小都…

C# 中 Interface(接口)和 virtual(虚方法)

文章目录 前言一、Interface&#xff08;接口&#xff09;1. 什么是接口2. 接口的定义3. 实现接口4. 接口的作用 二、virtual&#xff08;虚方法&#xff09;1. 什么是虚方法2. 虚方法的定义3. 重写虚方法4. 虚方法的作用 三、Interface 和 virtual 的结合使用1. 接口中的虚方法…

JWT 在 SaaS 系统中的作用与分布式 SaaS 系统设计的最佳实践

在现代 SaaS&#xff08;软件即服务&#xff09; 系统中&#xff0c;随着服务规模的扩大和用户需求的多样化&#xff0c;如何高效、安全地进行用户身份验证、权限控制以及租户隔离&#xff0c;成为了系统架构中的核心问题之一。**JWT&#xff08;JSON Web Token&#xff09;**作…

《智能体雏形开发(高阶实操)》二、智能体雏形开发

基于阿里云百炼平台开发智能体应用:生成日报与周报 在智能体开发中,生成结构化的日报与周报是一个典型的任务。本篇文章将基于阿里云百炼平台,结合 Python 开发环境,介绍如何开发一个从日志文件提取信息并生成摘要的智能体。我们将从需求分析、任务设计到核心功能实现逐步…

阿里云ECS服务器域名解析

阿里云ECS服务器域名解析&#xff0c;以前添加两条A记录类型&#xff0c;主机记录分别为www和&#xff0c;这2条记录都解析到服务器IP地址。 1.进入阿里云域名控制台&#xff0c;找到域名 ->“解析设置”->“添加记录” 2.添加一条记录类型为A,主机记录为www&#xff0c…

Scala的正则表达式(1)

package hfd //正则表达式的应用场景 //1.查找 findAllin //2.验证 matches //3.替换//验证用户名十分合法 //规则&#xff1a; //1.长度在6-12之间 //2.不能数字开头 //3.只能包含数字&#xff0c;大小写字母&#xff0c;下划线 object Test36 {def main(args: Array[String])…

网络知识:IP数据报知识详解

目录 一、IP数据报概念 二、IPV4数据报报头组成 三、IPV6数据报报头组成 今天给大家分享IP数据库相关的知识,希望对大家进一步了解IP协议提供一些帮助! 一、IP数据报概念 TCP/IP协议的网际层接收到传输层传递过来的数据单元,封装成向下(OSI模型的数据链路层、TCP/IP协…

Android 因为混淆文件配置,打release包提示running R8问题处理

一、报错信息 Missing classes detected while running R8. Please add the missing classes or apply additional keep rules that are generated in E:\workplace\xxxxxx\app\build\outputs\mapping\release\missing_rules.txt. Missing class org.mediakit.R$layout (refer…

【中断】向量中断、中断服务程序、中断向量、中断向量表、中断向量地址,之间的关系

向量中断&#xff1a; 一种识别中断源的方式和技术。 中断服务程序&#xff1a; 不同的中断信号&#xff0c;需要用不同的 中断处理程序&#xff08;中断服务程序&#xff09; 来处理。中断服务程序一般是OS模块。 中断向量&#xff1a; 由向量地址形成部件&#xff0c;是…

蓝桥杯刷题日记02-小球反弹

问题描述 有一长方形&#xff0c;长为 343720343720 单位长度&#xff0c;宽为 233333233333 单位长度。在其内部左上角顶点有一小球 (无视其体积)&#xff0c;其初速度如图所示且保持运动速率不变&#xff0c;分解到长宽两个方向上的速率之比为 dx:dy15:17dx:dy15:17。小球碰…

SWIRL:有望成为2025年顶级AI搜索引擎

现在几乎每家公司都会有内部文档系统&#xff0c;如阿里的语雀、钉钉&#xff0c;字节的飞书&#xff0c;Confluence&#xff0c;印象笔记等等都可以提供给B端在局域网部署。因此&#xff0c;如果能把搜索功能做得高效&#xff0c;就能提高自家产品的竞争力。 想象一下&#xf…

【源码】Sharding-JDBC源码分析之SQL中读写分离动态策略、数据库发现规则及DatabaseDiscoverySQLRouter路由的原理

Sharding-JDBC系列 1、Sharding-JDBC分库分表的基本使用 2、Sharding-JDBC分库分表之SpringBoot分片策略 3、Sharding-JDBC分库分表之SpringBoot主从配置 4、SpringBoot集成Sharding-JDBC-5.3.0分库分表 5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表 6、【…

2024 数学建模国一经验分享

2024 数学建模国一经验分享 背景&#xff1a;武汉某211&#xff0c;专业&#xff1a;计算机科学 心血来潮&#xff0c;就从学习和组队两个方面指点下后来者&#xff0c;帮新人避坑吧 2024年我在数学建模比赛中获得了国一&#xff08;教练说论文的分数是湖北省B组第一&#xff0…

可视化数据分析系统:提升企业决策效率的重要工具

作为数字化时代的企业&#xff0c;数据的重要性对于当今企业来说不言而喻。有效的针对企业内部数据进行深度分析就成为了目前企业面临的关键所在。可视化数据分析系统因此就在这样的背景之下出现的&#xff0c;通过直观清晰的数据展示&#xff0c;可以帮助企业管理层快速提高决…

XML与HTML的区别汇总

XML的基本格式规则 主要规则&#xff1a; XML文档必须格式良好(well-formed)所有标签必须关闭标签名称区分大小写HTML内容需要转义属性值必须使用引号不能有交叉嵌套 XML声明&#xff08;可选但推荐&#xff09;&#xff1a; <?xml version"1.0" encoding&quo…

C语言 字符数组/多维数组/函数/作用域

1. 遍历数组 遍历数组:通过循环的方式来把数组中的每个元素数据进行查询 使用for循环遍历数组更多一些 数组长度计算: 数组总字节数/元素的数据类型的字节数 数组总字节数/第一个元素的字节数 数组遍历相关的案例:求和,求平均值,求最大值,求最小值,冒泡排序 2. 字符数组 …

Centos7环境下nifi单机部署

Centos7环境下nifi单机部署 前言一、安装Nifi1.1 下载并解压1.2 修改配置文件 二、启动Nifi程序三、Nifi的简单使用3.1 文件移动3.2 本地文件传到HDFS 参考博客 前言 本以为在服务器上部署nifi很简单&#xff0c;跟着教程走就好&#xff0c;但是并没有成功&#xff0c;可能是因…