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

目录

1.分析

2.打印输入提示符

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

4.创建子进程并切换

5.bash内部指令

6.完整代码


1.分析

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

2.打印输入提示符

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

cpp 复制代码
#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++的方式哪个方便用哪个)。处理的话我们就把它处理成命令行参数列表以便后续切换进程。

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

cpp 复制代码
//读取处理指令
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.创建子进程并切换

cpp 复制代码
//执行指令
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内部程序。

cpp 复制代码
//执行指令
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.完整代码

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

cpp 复制代码
#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;
}
相关推荐
绵绵细雨中的乡音1 小时前
网络基础知识
linux·网络
Peter·Pan爱编程2 小时前
Docker在Linux中安装与使用教程
linux·docker·eureka
kunge20132 小时前
Ubuntu22.04 安装virtualbox7.1
linux·virtualbox
清溪5492 小时前
DVWA中级
linux
MUY09903 小时前
应用控制技术、内容审计技术、AAA服务器技术
运维·服务器
楠奕3 小时前
elasticsearch8.12.0安装分词
运维·jenkins
Sadsvit3 小时前
源码编译安装LAMP架构并部署WordPress(CentOS 7)
linux·运维·服务器·架构·centos
xiaok3 小时前
为什么 lsof 显示多个 nginx 都在 “使用 443”?
linux
java资料站3 小时前
Jenkins
运维·jenkins
苦学编程的谢4 小时前
Linux
linux·运维·服务器