当前位置: 首页 > news >正文

Linux应用之构建命令行解释器(bash进程)

目录

1.分析

2.打印输入提示符

3.读取并且处理输入字符串

4.创建子进程并切换

5.bash内部指令

6.完整代码


1.分析

        当我们登录服务器的时候,命令行解释器就会自动加载出来。接下来我们就。在命令行中输入指令来达到我们想要的目的。        我们在命令行上输入的是一连串的字符串,那么bash首先要做的就是分析字符串。然后判断是否合法字符串。如果是合法字符串,那么就创建一个子进程,让子进程去切换执行命令,bash分析子进程结果。如果是非法字符串,那么就再次循环,整体嵌套在一个while循环里面。如此循环下去。

2.打印输入提示符

        

        通过观察超级用户和普通用户可以发现提示行主要由三部分构成,用户名,主机名,地址以及特殊符号,用户名和主机名可以从环境变量中获取,主机名就设置为@区别于shell。

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string>
using namespace std;

const int MaxSize=1024;

char * UsrName;
char * HostName;
char  CommandPwd[MaxSize];

//打印命令行提示符
void PrintCLPrompt()
{
    printf("[%s@%s %s]@",UsrName,HostName,CommandPwd);
}

//准备工作
void setup()
{
    UsrName=getenv("USER");
    HostName=getenv("HOSTNAME");
    
    //这里采用C++的string来接收是为了后序处理方便
    string str=getenv("PWD");
    string tmp=str.substr(str.rfind("/")+1);//加一去除/字符
    snprintf(CommandPwd,MaxSize,"%s",tmp.c_str());//格式化输出到数组函数
}

int main()
{
    
    setup();

    while(1)
    {
        PrintCLPrompt();


        //让程序停在这里方便观察 
        scanf("%d",NULL);

    }


    return 0;
}

        此时输出的命令行提示符就在我们的预期之中了

3.读取并且处理输入字符串

        读取比较简单,我们只需scanf或者cin即可。( C++兼容C所以在这里读取可以使用C的方式或者是C++的方式,同理输出也可以采用C的方式或者C++的方式哪个方便用哪个)。处理的话我们就把它处理成命令行参数列表以便后续切换进程。

        前面代码相同,加上后序调试代码与读取指令即可。

//读取处理指令
char Command[MaxSize];
char * argv[MaxSize];
void ReadDealCommand()
{
    //读取一行字符串
    fgets(Command,MaxSize,stdin);

    int len=strlen(Command)-1;//fegts会把\n也读取到,但这个是不用的字符.
    Command[len]='\0';

    //处理字符串
    int size=0;
    int prev=-1,cur=0;

    //预处理,将空格换成\0
    for(int i=0; i<len; i++)
    {
        if(Command[i]==' ')
            Command[i]='\0';
    }

    while(cur<len)
    {
        if(prev == -1 && Command[cur]!='\0'
                || Command[prev]=='\0' && Command[cur]!='\0')
        {
            argv[size++]=Command+cur;
        }

        prev++;
        cur++;
    }
    
    //argv最后加上NULL
    argv[size]=NULL;

}


int main()
{
    
    setup();

    while(1)
    {   
        //打印提示行
        PrintCLPrompt();
        //读取处理指令
        ReadDealCommand();
        

        //检测是否处理成功
        for(int i=0; argv[i]; i++)
        {
            printf("argv[%d]:%s\n",i,argv[i]);
        }


    }

    return 0;
}

        上述读取一行字符串采用了C语言的fgets函数,当然也可以用C++的getline。使用C++的fgets函数,要注意它会把反斜\n也读取到,在计算字符串的长度时要减一。

        将原字符串空格改为\0后,就可以让数组指针直接指向原字符串即可。

        其中要注意可能有结尾或者开头带空格的情况。(以下用@代替空格)

ls@-a@-l

@ls@-a@-l

ls@-a@-l@

4.创建子进程并切换

        

//执行指令
void PreformCommand()
{
    pid_t id=fork();

    if(id ==0 )
    {
        execvp(argv[0],argv);
        exit(111);
    }

    //父进程
    int status=0;
    pid_t rid=wait(&status);
     if (WIFEXITED(status))
     {
         // 子进程正常退出,提取退出状态码
         int exit_status = WEXITSTATUS(status);
        // printf("Child process exited normally with status %d\n", exit_status);
     }
     else if (WIFSIGNALED(status)) {
            // 子进程因信号终止,提取信号编号
            int signal_num = WTERMSIG(status);
            printf("Child process terminated by signal %d\n", signal_num);
        }    
}


int main()
{
    
    setup();

    while(1)
    {   
        //打印提示行
        PrintCLPrompt();
        //读取处理指令
        ReadDealCommand();
        
        //执行指令
        PreformCommand();
    }


    return 0;
}

       创建子进程,并让子进程切换到所要执行的程序,父进程接收子进程的退出码即可.由此我们便基本完成了bash程序。

5.bash内部指令

        有一些指令是可以直接从环境变量中得到或使用的,就不必再创建子进程了直接在bash里完成指令即可。比如cd,pwd等这些都是bash内部程序。

//执行指令
void PreformCommand()
{
    //bash内部程序
    if(strcmp("pwd",argv[0])==0)
    { 
        printf("%s\n",getenv("PWD"));
        return ;
    }
    else if(strcmp("cd",argv[0])==0)
    {
        chdir(argv[1]);

        char cwd[1024];
        getcwd(cwd,1024);
        //更新地址
        string str=cwd;
        string tmp=str.substr(str.rfind("/")+1);//加一去除/字符
        snprintf(CommandPwd,MaxSize,"%s",tmp.c_str());//格式化输出到数组函数
        
        //修改环境变量
        string s="PWD=";
        s+=cwd;
        snprintf(cwd,MaxSize,"%s",s.c_str());//格式化输出到数组函数 
        putenv(cwd);//putenv参数要是char*,但c_str()返回的是const char *
        

        return ;
    }
    

    pid_t id=fork();

    if(id ==0 )
    {
        execvp(argv[0],argv);
       
        //切换不成功返回退出码
        exit(111);
    }

    //父进程
    int status=0;
    pid_t rid=wait(&status);
     if (WIFEXITED(status))
     {
         // 子进程正常退出,提取退出状态码
         int exit_status = WEXITSTATUS(status);
        // printf("Child process exited normally with status %d\n", exit_status);
     }
     else if (WIFSIGNALED(status)) {
            // 子进程因信号终止,提取信号编号
            int signal_num = WTERMSIG(status);
            printf("Child process terminated by signal %d\n", signal_num);
    }    
}


int main()
{
    
    setup();

    while(1)
    {   
        //打印提示行
        PrintCLPrompt();

        //读取处理指令
        ReadDealCommand();
        
        //执行指令
        PreformCommand();
    }


    return 0;
}

        

6.完整代码

        最后全部的代码就在这了,上述粘贴可能有错误还望海涵

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string>
#include<string.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
using namespace std;

const int MaxSize=1024;

char * UsrName;
char * HostName;
char  CommandPwd[MaxSize];

//打印命令行提示符
void PrintCLPrompt()
{
    printf("[%s@%s %s]@",UsrName,HostName,CommandPwd);
}

//准备工作
void setup()
{
    UsrName=getenv("USER");
    HostName=getenv("HOSTNAME");
    
    string str=getenv("PWD");
    string tmp=str.substr(str.rfind("/")+1);//加一去除/字符
    snprintf(CommandPwd,MaxSize,"%s",tmp.c_str());//格式化输出到数组函数
}

//读取处理指令
char Command[MaxSize];
char * argv[MaxSize];
void ReadDealCommand()
{
    //读取一行字符串
    fgets(Command,MaxSize,stdin);

    int len=strlen(Command)-1;//fegts会把\n也读取到,但这个是不用的字符
    Command[len]='\0';
    //处理字符串
    int size=0;
    int prev=-1,cur=0;

    //预处理,将空格换成\0
    for(int i=0; i<len; i++)
    {
        if(Command[i]==' ')
            Command[i]='\0';
    }

    while(cur<len)
    {
        if(prev == -1 && Command[cur]!='\0'
                || Command[prev]=='\0' && Command[cur]!='\0')
        {
            argv[size++]=Command+cur;
           //.autorelabel printf("%d ",cur);
        }

        prev++;
        cur++;
    }
    
    //argv最后加上NULL
    argv[size]=NULL;

}
//执行指令
void PreformCommand()
{
    //bash内部程序
    if(strcmp("pwd",argv[0])==0)
    { 
        printf("%s\n",getenv("PWD"));
        return ;
    }
    else if(strcmp("cd",argv[0])==0)
    {
        chdir(argv[1]);

        char cwd[1024];
        getcwd(cwd,1024);
        //更新地址
        string str=cwd;
        string tmp=str.substr(str.rfind("/")+1);//加一去除/字符
        snprintf(CommandPwd,MaxSize,"%s",tmp.c_str());//格式化输出到数组函数
        
        //修改环境变量
        string s="PWD=";
        s+=cwd;
        snprintf(cwd,MaxSize,"%s",s.c_str());//格式化输出到数组函数 
        putenv(cwd);//putenv参数要是char*,但c_str()返回的是const char *
        

        return ;
    }
    

    pid_t id=fork();

    if(id ==0 )
    {
        execvp(argv[0],argv);
       
        //切换不成功返回退出码
        exit(111);
    }

    //父进程
    int status=0;
    pid_t rid=wait(&status);
     if (WIFEXITED(status))
     {
         // 子进程正常退出,提取退出状态码
         int exit_status = WEXITSTATUS(status);
        // printf("Child process exited normally with status %d\n", exit_status);
     }
     else if (WIFSIGNALED(status)) {
            // 子进程因信号终止,提取信号编号
            int signal_num = WTERMSIG(status);
            printf("Child process terminated by signal %d\n", signal_num);
    }    
}


int main()
{
    
    setup();

    while(1)
    {   
        //打印提示行
        PrintCLPrompt();

        //读取处理指令
        ReadDealCommand();
        
        //执行指令
        PreformCommand();
    }


    return 0;
}

相关文章:

  • vue3之echarts柱状图-圆锥加自动轮播
  • 使用Termux将安卓手机变成随身AI服务器(page assist连接)
  • Pyrhon函数-装饰器第一部分250219
  • C程序设计(第5版)——谭浩强(2)
  • 构建简单RAG代码实现
  • java常见面试场景题
  • nodejs各版本下载地址 —— 筑梦之路
  • 【Java】泛型与集合篇 —— 泛型
  • virt-io 如何运行在 kvm windows 虚拟机上
  • rust学习三、基本类型
  • Spring框架基本使用(Maven详解)
  • 【二分搜索 C/C++】洛谷 P1873 EKO / 砍树
  • SAM C++ TensorRT(实时图像分割)
  • 【有啥问啥】DeepSeek 技术原理详解
  • vue取消全选功能按钮注意事项
  • java机器学习计算指标动态阈值
  • Jackson使用
  • 点击unity资源文件自动展开左侧的文件路径
  • StableDiffusion学习笔记——4、模型下载和学习
  • 算法系列之搜索算法-广度优先搜索BFS
  • 当农民跨进流动的世界|劳动者的书信①
  • 五大国货美妆去年业绩分化:珀莱雅百亿营收领跑,上海家化转亏
  • 解放日报:上海深化改革开放,系统集成创新局
  • 五一小长假,带着小狗去上海音乐厅
  • 人社部:将制定提前领取个人养老金相关办法
  • 习近平就伊朗发生严重爆炸事件向伊朗总统佩泽希齐扬致慰问电