自制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());
    else
        printf(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;
} 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LangChain:大模型AI应用开发的强大引擎

文章目录 LangChain的核心功能LangChain的典型使用场景LangChain的未来展望《LangChain大模型AI应用开发实践》编辑推荐内容简介作者简介目录 在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的应用开发正逐渐成为技术前沿的热门话题。随着技术的不断进步…

【MFC】vs2019中使用sqlite3完成学生管理系统

目录 效果图list Contral 控件的简单使用使用sqlite3 效果图 使用sqlite3完成简单的数据库操作。 list Contral 控件的简单使用 本章只介绍基本应用 添加表头&#xff1a;语法&#xff1a; int InsertColumn(int nCol, LPCTSTR lpszColumnHeading, int nFormat LVCFMT_LEFT…

杨振宁大学物理视频中黄色的字,c#写程序去掉

先看一下效果&#xff1a;&#xff08;还有改进的余地&#xff09; 我的方法是笨方法&#xff0c;也比较刻板。 1&#xff0c;首先想到&#xff0c;把屏幕打印下来。c#提供了这样一个函数&#xff1a; Bitmap bmp new Bitmap(640, 480, PixelFormat.Format32bppArgb); // 创…

Android 逆向/反编译/Hook修改应用行为 基础实现

前言&#xff1a;本文通过一个简单的情景案例实现安卓逆向的基本操作 一、情景描述 本文通过一个简单的情景案例来实现安卓逆向的基本操作。在这个案例中所使用的项目程序是我自己的Demo程序&#xff0c;不会造成任何的财产侵害&#xff0c;本文仅作为日常记录及案例分享。实…

OSCP - Proving Grounds - Zino

主要知识点 SMB知识python脚本提权 具体步骤 执行nmap Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-10 01:24 UTC Nmap scan report for 192.168.52.64 Host is up (0.00077s latency). Not shown: 65529 filtered tcp ports (no-response) PORT STATE SER…

VLA模型

目录 引言1. 机器人大模型面临的挑战2. 目前的数据集2.1 RT-12.2 Open X-Embedding2.3 DROID 3. 目前的VLA模型3.1 Goat3.2 RT-13.2.1 总体架构3.2.2 效果 3.3 RT-23.3.1 总体架构3.3.2 效果 3.4 RT-X3.4.1 模型效果1). RT-1-X2). RT-2-X 3.5 RT-H3.5.1 总体架构3.5.2 效果 3.6…

aws(学习笔记第十六课) 使用负载均衡器(ELB)解耦webserver以及输出ELB的日志到S3

aws(学习笔记第十六课) 使用负载均衡器(ELB)以及输出ELB的日志到S3 学习内容&#xff1a; 使用负载均衡器(ELB)解耦web server输出ELB的日志到S3 1. 使用负载均衡器(ELB) 全体架构 使用ELB(Elastic Load Balancer)能够解耦外部internet访问和web server之间的耦合&#xff0c…

如何使用Java编写Jmeter函数

Jmeter 自带有各种功能丰富的函数&#xff0c;可以帮助我们进行测试&#xff0c;但有时候提供的这些函数并不能满足我们的要求&#xff0c;这时候就需要我们自己来编写一个自定义的函数了。例如我们在测试时&#xff0c;有时候需要填入当前的时间&#xff0c;虽然我们可以使用p…

实战指南:如何通过WBS提高项目估算准确性?

通过WBS将复杂任务细分为更易管理的任务&#xff0c;这有助于明确每项工作范围、所需资源及时间&#xff0c;从而减少估算误差&#xff0c;制定更现实的预算和时间表&#xff0c;提升团队协作效率。如果没有通过WBS将任务细化&#xff0c;项目范围可能变得模糊不清&#xff0c;…

ECharts实战教程:如何生成动态水波纹效果

导语&#xff1a;在数据可视化领域&#xff0c;ECharts是一款非常强大的图表库。今天&#xff0c;我们将带领大家学习如何使用ECharts生成动态水波纹效果&#xff0c;让我们的图表更加生动有趣。 一、准备工作 首先&#xff0c;我们需要准备一些基础数据&#xff0c;如下所示&…

详解:HTTP/HTTPS协议

HTTP协议 一.HTTP是什么 HTTP&#xff0c;全称超文本传输协议&#xff0c;是一种用于分布式、协作式、超媒体信息系统的应用层协议。HTTP往往是基于传输层TCP协议实现的&#xff0c;采用的一问一答的模式&#xff0c;即发一个请求&#xff0c;返回一个响应。 Q&#xff1a;什…

(0基础保姆教程)-JavaEE开课啦!--13课程(Interception拦截器)-完结

一、Interception(拦截器)是什么&#xff1f; 拦截器&#xff08;Interceptor&#xff09;是一种用于在请求到达目标方法之前或之后执行特定逻辑的机制。它是基于Java反射机制&#xff0c;属于面向切面编程&#xff08;AOP&#xff09;的一种应用。拦截器可以用于多种应用场景&…

vue 封装全局过滤器

1.找到utils下创建fifilter.js 一些常用的过滤方法 export const filters {//url解码urlCode: value > {if (!value) return let v decodeURIComponent(value)let bigIndex v.lastIndexOf(/)let endIndex v.lastIndexOf(.)let url v.substring(bigIndex 1, endIndex)…

Flask返回中文Unicode编码(乱码)解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

VMware:安装centos网络信息不可用

我们今天要处理的就是在vmware中安装centos出现网络不可用&#xff0c;导致无法安装系统的问题直接上图&#xff0c;我们在主机直接 cmdipconfig 发现IPV4地址都不一样&#xff0c;导致我们无法ping通虚拟机 那我们如何解决呢~~~~ 打开自己VM【编辑】【虚拟网络编辑器】【更…

MperReduce学习笔记下

自定义InputFormat合并小文件 案例需求 无论hdfs还是mapreduce&#xff0c;对于小文件都有损效率&#xff0c;实践中&#xff0c;又难免面临处理大量小文件的场景&#xff0c;此时&#xff0c;就需要有相应解决方案。 案例分析 小文件的优化无非以下几种方式&#xff1a; …

【MySQL 探索者日志 】第二弹 —— 数据库基础

MySQL系列学习笔记&#xff1a; MySQL探索者日志__Zwy的博客-CSDN博客 各位于晏&#xff0c;亦菲们&#xff0c;请点赞关注&#xff01; 我的个人主页&#xff1a; _Zwy-CSDN博客 目录 1、MySQL服务器&#xff0c;数据库&#xff0c;表关系 2、MySQL登录连接服务器 3、MyS…

flink终止提交给yarn的任务

接上文&#xff1a;一文说清flink从编码到部署上线 1.查看正在执行的flink 访问地址&#xff08;参考&#xff09;&#xff1a;http://10.86.97.191:8099/cluster/apps 2.终止任务 yarn application -kill appID 本文为&#xff1a; yarn application -kill application_17…

CentOS虚拟机开机出现问题

CentOS虚拟机断电或强制关机&#xff0c;再开机出现问题 错误原因&#xff1a; failed to mount /sysroot.&#xff08;无法挂载/ sysroot。&#xff09; Dependency failed for Initrd root File System.&#xff08;Initrd根文件系统的依赖关系失败。&#xff09; Dependency…

可靠的人形探测,未完待续(I)

HI&#xff0c;there&#xff01;从紧张的项目中出来冒个泡&#xff01; 刚好想要验证一下mmWave在有人检测方面的应用&#xff0c;就看到了这个活动 - 瞌睡了有枕头属于是&#xff0c;活动策划好评&#xff01; 朋友曾关注汽车相关的技术领域&#xff0c;跟我吐槽过&#xff0…