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

news2025/5/11 5:14:50

目录

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;
}

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

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

相关文章

挪车小程序挪车二维码php+uniapp

一款基于FastAdminThinkPHP开发的匿名通知车主挪车微信小程序&#xff0c;采用匿名通话的方式&#xff0c;用户只能在有效期内拨打车主电话&#xff0c;过期失效&#xff0c;从而保护车主和用户隐私。提供微信小程序端和服务端源码&#xff0c;支持私有化部署。 更新日志 V1.0…

DockerDesktop更改默认的磁盘镜像地存储位置

DockerDesktop更改默认的磁盘镜像地存储位置 文章目录 DockerDesktop更改默认的磁盘镜像地存储位置1. 默认存储位置2. 新建一个目录3. 将磁盘镜像存储位置改为新建的目录下 1. 默认存储位置 2. 新建一个目录 如&#xff1a;D:\DiskImagelocationData 3. 将磁盘镜像存储位置改为…

GO语言的安装以及第一个Go语言程序

1. Go语言的安装与设置 官网:golang.org 国内下载:https://studygolang.com/dl 国内镜像:https://goproxy.cn/ 2. GOland的安装 Go 1.13 及以上&#xff08;推荐&#xff09; 打开你的终端并执行 $ go env -w GO111MODULEon $ go env -w GOPROXYhttps://goproxy.cn,direc…

SpringBoot速成(12)文章分类P15-P19

1.新增文章分类 1.Postman登录不上&#xff0c;可以从头registe->login一个新的成员:注意&#xff0c;跳转多个url时&#xff0c;post/get/patch记得修改成controller类中对应方法上写的 2.postman运行成功&#xff1a; 但表中不更新&#xff1a;细节有问题&#xff1a; c是…

(leetcode42 前缀后缀最值)接雨水

记忆化&#xff1a;打比方说前缀和 dp数组每个值代表了某一段计算过程 直接取值无需再计算就是记忆化 问题的核心思路 为了计算每个位置能接住多少水&#xff0c;我们需要知道在每个位置上方的水的容量。假设位置 i 是某个柱子的底部&#xff0c;要计算它能接多少水&#xff…

SpringBoot+uniApp日历备忘录小程序系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码1.日历渲染代码&#xff1a;2.保存备忘录代码&#xff1a;3.删除备忘录代码&#xff1a; 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBootuniApp框架开…

分类预测 | MFO-LSSVM飞蛾扑火算法优化最小二乘支持向量机多特征分类预测Matlab实现

分类预测 | MFO-LSSVM飞蛾扑火算法优化最小二乘支持向量机多特征分类预测Matlab实现 目录 分类预测 | MFO-LSSVM飞蛾扑火算法优化最小二乘支持向量机多特征分类预测Matlab实现分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.Matlab实现MFO-LSSVM飞蛾扑火算法优化最小二…

Redis未授权访问漏洞原理

redis未授权访问漏洞 目录 redis未授权访问漏洞一、Redis介绍二、redis环境安装三、漏洞原理四、漏洞复现4.1 webshell提权4.2redis写入计划任务反弹shell4.3 ssh key免密登录4.4 Redis基于主从复制的RCE方式 五、Redis加固建议 一、Redis介绍 Redis&#xff0c;全称为Remote …

喜报!博睿数据案例获经观传媒“2024年度数字转型创新案例”!

本文已在“经观”APP中发表&#xff0c;点击下方文章链接查看原文&#xff1a; 2024科技创变纪&#xff1a;创新破局 变量启新 近日&#xff0c;经济观察报“2024年度卓越创新实践案例”榜单评选结果正式公布。博睿数据选送的案例“从零到一&#xff1a;可观测体系建设的探索…

【从0做项目】Java搜索引擎(4)——性能优化~烧脑~~~

本篇文章将对项目搜索引擎&#xff08;1&#xff09;~&#xff08;3&#xff09;进行性能优化&#xff0c;包括测试&#xff0c;优化思路&#xff0c;优化前后对比 目录 一&#xff1a;文件读取 二&#xff1a;实现多线程制作索引 1&#xff1a;代码分析 2&#xff1a;代码…

什么是网络安全审计?网络安全审计的作用...

网络安全审计通过对网络数据的采集、分析、识别&#xff0c;实时动态监测通信内容、网络行为和网络流量&#xff0c;发现和捕获各种敏感信息、违规行为&#xff0c;实时报警响应&#xff0c;全面记录网络系统中的各种会话和事件&#xff0c;实现对网络信息的智能关联分析、评估…

【核心算法篇十三】《DeepSeek自监督学习:图像补全预训练方案》

引言:为什么自监督学习成为AI新宠? 在传统监督学习需要海量标注数据的困境下,自监督学习(Self-Supervised Learning)凭借无需人工标注的特性异军突起。想象一下,如果AI能像人类一样通过观察世界自我学习——这正是DeepSeek图像补全方案的技术哲学。根据,自监督学习通过…

【Linux专栏】find命令+同步 实验

Linux & Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.实验背景 需要把一个目录中所有文件,按照目录把某个时间点之前的同步到一个盘中,之后的同步备份到另一个盘中,实现不同时间段的备份。 本次实现目标:把common文件夹中 2025年之后的含文件夹…

vite配置scss全局变量

vite配置scss全局变量 创建单独文件variable.scss在其中定义变量 vite.config.ts中配置 import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path// https://vite.dev/config/ export default defineConfig({plugins: [vue()],resolve:…

Qt开发①Qt的概念+发展+优点+应用+使用

目录 1. Qt的概念和发展 1.1 Qt的概念 1.2 Qt 的发展史&#xff1a; 1.3 Qt 的版本 2. Qt 的优点和应用 2.1 Qt 的优点&#xff1a; 2.2 Qt 的应用场景 2.3 Qt 的应用案例 3. 搭建 Qt 开发环境 3.1 Qt 的开发工具 3.2 Qt SDK 的下载和安装 3.3 Qt 环境变量配置和使…

FastGPT快速将消息发送至飞书

欢迎关注【AI技术开发者】 在很多企业内部场景下&#xff0c;都需要发送数据到内部交流软件&#xff0c;如飞书、钉钉、企业微信 本文就以飞书为例&#xff0c;企业内部其他同事上报故障后&#xff0c;自动发送消息到飞书&#xff0c; 并相关人员 前文中&#xff0c;使用coz…

qsort介绍与实现

qsort qsort 是 C 标准库中的一个通用排序函数&#xff0c;位于 <stdlib.h> 头文件中。它可以对任意类型的数组进行排序&#xff0c;使用的是快速排序&#xff08;Quick Sort&#xff09;算法的变种。 参数说明 base&#xff1a;指向要排序的数组的第一个元素的指针。由…

WPF创建自定义类和控件及打包成dll引用

WPF创建自定义类和控件及打包成dll引用 一、前言二、创建自定义类和控件并生成dll文件2.1创建类库项目2.2创建自定义类和控件2.3生成dll文件 三、在其他项目中引用3.1添加dll文件引用3.2cs文件中引用命名空间3.3XAML文件中引用命名空间 一、前言 出于一些代码复用的需求&#…

DVWA-DOM型XSS全等级绕过方法

DOM型XSS全等级绕过 前言一、LOW级别二、Medium级别 图片插入语句法 三、High级别 字符 # 绕过服务端过滤 四、Impossible级别 前言 DOM&#xff0c;全称Document Object Model&#xff0c;是一个平台和语言都中立的接口&#xff0c;可以使程序和脚本能够动态访问和更新文档…

《[含文档+PPT+源码等]精品基于Python实现的Django中药材在线学习系统的设计与实现

基于Python实现的Django中药材在线学习系统的设计与实现背景&#xff0c;可以从以下几个方面进行阐述&#xff1a; 一、行业背景 随着中医药在全球范围内的不断推广和普及&#xff0c;中药材的知识普及和在线学习需求日益增长。传统的中药材学习方式往往受限于地域、时间和资…