Shell进程替换,自定义Shell解释器——字符串库函数灵活操作!

@bit::Shadow
✧(≖ ◡ ≖✿

目录

进程程序替换

"程序"怎么理解?

shell内程序替换的接口

6+1

命名规律:

第一个参数path/file

which与where查询位置

自定义shell命令解释器

0.设置环境变量

1.命令行提示符

2.获取用户输入

strtok()字符串截取函数

例:

执行结果:

myshell模块功能实现:

3.指令执行(可优化封装)

strcmp()

strcpy()

dest要求:

strcat()

☆☆☆snprintf()

参数

返回值:

使用实例:

myshell指令执行功能


进程程序替换

定义:进程程序替换指使用一个新的程序 (可执行文件),替换当前进程的代码数据 栈 堆 内存段等 ,但保留 原进程的PID、某些环境及文件描述符等信息。替换完成后程序执行新的代码块,新代码块执行完成后,原代码块不再执行

"程序"怎么理解?

一个存储在硬盘上的,静态的、可执行的指令和数据的集合,它是一个/多个死的文件

shell内程序替换的接口

6+1

接口有6+1个前六个复用第七个,前六个作为库函数最后一个作为系统调用。

前六个:

后一个:

7者一旦返回(-1)代表执行失败。

观察发现它们的组成方式都是:"exec"+...... 参数几乎是一致的,实际上七者对应参数的功能都是一致的。

命名规律:

l(list) vs v(vector):指明命令行参数的传递方式

  • l :采用参数列表的形式。像execl("usr/bin/ls", "ls", "-l", NULL)这样每个参数作为单独的参数依次列出,最后以NULL结尾。
  • v:采用数组的方式。先将所有参数存储到一个指针数组内(包含NULL),然后把数组地址传递给函数。

p(path):指明可执行文件的查找方式

  • 带p只需提供文件名如ls,函数会在自动环境变量PATH中搜索。
  • 不带p:必须提供完整的文件路径(如:/usr/bin/ls)。

e(environment):指明环境变量的来源

  • 带e:可以传入一个envp[]数组来自定义新程序的环境变量。(很少使用)
  • 不带e:新程序会默认继承当前进程的环境变量。

第一个参数path/file

path:文件路径 ,使用这个路径查找可执行文件名,因此是完整的文件路径。

file:可以加路径也可以 不加路径,直接输入指令名称使得从环境变量中自动寻找指令但是要将后续的指令执行全部规范写出,不可因第一个指令而省略。

以execl()为例:

cpp 复制代码
int execl(const char*path, const char* arg0, ......, (char*)0);
which与where查询位置

|---------|--------------------------------|---------------------|
| where | 所有找到的匹配项(包括别名、二进制、源码、帮助文档) | 更全面,适合查看命令的"所有存在形式" |
| which | 只输出第一个匹配的二进制文件路径 | 更简洁,只告诉你会执行哪个 |

第一个参数路径path的寻找:

由"l"推出参数列表:

运行:

运行结果:

file参数可直接写指令:

数组char* argv[]:


自定义shell命令解释器

设计导图(图片过大 无法全部呈现->smlShell仓库)

0.设置环境变量

由于无法获取系统层面的环境变量只能通过const char** environ来拷贝+putenv();

cpp 复制代码
//设置环境变量
char* enVr[129];//内部存储char*类型"lll"
void EnvInit()
{
    memset(enVr, 0, sizeof(enVr)); // 好的习惯
    extern char** environ;//内部存储char**的数据类型environ[] "lll"
    for(int i = 0;environ[i];i++)//i至多为20
    {
        enVr[i]=(char*)malloc(strlen(environ[i])+1);
        //enVr[i]=(char*)malloc(sizeof(strlen(environ[i])+1));
        strcpy(enVr[i],environ[i]);
        putenv(enVr[i]);
    }
}

1.命令行提示符

char* getenv(const char* EnvmVar); // 参数为环境变量名称 返回对应内容

cpp 复制代码
//命令行
void CoutCommandLine()
{
    std::cout << "[" << getenv("USER") \
        << "@" << getenv("HOSTNAME") << " " << getenv("PWD") << "]";
    if(strcmp(getenv("USER"),"root")==0) 
        std::cout << "# ";//err当相等时返回0
    else std::cout << "$ ";
}

2.获取用户输入

strtok()字符串截取函数

cpp 复制代码
#include <string.h>
char *strtok(char *str, const char *delim);

char* strtok(char* str, const char* delim); // 用于分割字符串,delim为分割单位

具体功能:将原串delim处变作'\0',并返回分割得到的子串。

若想要多次分割(对同一字符串),第一个参数必须传入NULL。

例:
cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char path[] = "/home/user/documents/file.txt";
    
    // 分割路径
    char *token = strtok(path, "/");
    while (token != NULL) {
        printf("目录/文件: %s\n", token);
        token = strtok(NULL, "/");
    }
    
    return 0;
}
执行结果:

目录/文件: home

目录/文件: user

目录/文件: documents

目录/文件: file.txt

myshell模块功能实现:
cpp 复制代码
//指令解析
void CommandParsed()
{
    //std::cout << CommandLine ;
    //1.不接纳数组传参的直接淘汰 , 
    //2.file和path选file无需位置指定
    //3.无需传环境变量所以选择
    //int execvp(const char* file, char *const argv[])
    //第一个命令的截取 若一个命令由多部分组成该怎么办?怎么区分指令与选项

    int i = 0;
    CommandStr[i++]=strtok(CommandLine," ");//自动将分隔符替换为'\0'

    //截取剩余选项的主逻辑
    while((bool)(CommandStr[i++]=strtok(NULL," ")));
}

3.指令执行(可优化封装)

strcmp()

cpp 复制代码
#include <string.h>
int strcmp(const char *str1, const char *str2);

int strcmp(const char* str1, const char* str2);

// 字符串比较 函数,字符串 相等返回0。 // 注意:由于返回0所以if(!strcmp(str1, str2)) ;

strcpy()

cpp 复制代码
#include <string.h>
char *strcpy(char *dest, const char *src);

功能:将src复制到dest

dest要求:
要求 说明 违反后果
有效的内存地址 不能是 NULL 或野指针 段错误,程序崩溃
可写内存 不能是只读内存(如字符串常量) 段错误或未定义行为
足够的空间 至少 strlen(src) + 1 字节 缓冲区溢出,安全漏洞
已分配空间 不能是未初始化的指针 未定义行为,崩溃

strcat()

对dest要求可以找到'\0'。

cpp 复制代码
#include <string.h>
char *strcat(char *dest, const char *src);

☆☆☆snprintf()

作为字符串拷贝转移最安全的函数

cpp 复制代码
#include<stdio.h>
int snprintf(char* str, size_t size, const char* format, ...);
参数
  • str:目标缓冲区

  • size:缓冲区大小(字节数)

  • format:格式化字符串

  • ...:可变参数

返回值:
  • 成功时:返回应该写入的字符数(不包括'\0')。
  • 失败时:返回负数。

关键特性

  • 自动添加 '\0' 结尾

  • 不会溢出 (超过 size-1 的内容被截断)

  • 可替代 strcpystrcat

使用实例:
cpp 复制代码
#include <stdio.h>

int main() 
{
    char buffer[20];
    
    // 基本使用
    snprintf(buffer, sizeof(buffer), "Hello");
    printf("%s\n", buffer);  // Hello
}

myshell指令执行功能

cpp 复制代码
//指令执行
void CommandRun()
{    
    //"ls" "ls" "-a" "-l" NULL
    if(strcmp(CommandStr[0],"cd")==0)
    {   
        if(!strcmp(CommandStr[1],getenv("HOME"))) 
            strcpy(getenv("PWD"),getenv("HOME"));
        else 
            strcpy(getenv("PWD"),CommandStr[1]);
    }
    else if(strcmp(CommandStr[0],"echo")==0)
    {
        //echo "字符串" 环境变量/本地变量  $?
        std::string s = CommandStr[1];
        //std::cout<<s;
        if(s=="$?")
        {
            std::cout << exitCode << std::endl;
        }
        else if(s[0]=='$') 
        {
            //CommandStr = "$PATH"
            std::string s1 = s.substr(1);
            std::cout << getenv(s1.c_str()) << std::endl;
        }
        else{
            std::cout << s << std::endl;
        }
    }
    else if(strcmp(CommandStr[0],"env")==0)
    {
        for(int i =0;enVr[i];i++)
            std::cout << enVr[i] << std::endl;
        std::cout << std::endl << std::endl;
    }
    else
         execvp(CommandStr[0],CommandStr);   
}

感谢观看

多多关注

相关推荐
Donk_678 小时前
什么是虚拟化
linux·运维
IT大白鼠8 小时前
2019年Cloudflare全球宕机事件技术分析:正则表达式回溯失控与互联网基础设施脆弱性研究
运维·正则表达式·去中心化
funnycoffee1238 小时前
华为S5736交换机3层ECMP负载方式
linux·服务器·数据库
_F_y8 小时前
树形 DP 从入门到进阶:普通树形DP、树形背包、换根DP
c++·动态规划
上海云盾-小余8 小时前
内网终端安全管控:筑牢企业内部网络入侵防火墙
服务器·网络·安全
数智工坊8 小时前
PyCharm 运行 Python 脚本总自动进 Test 模式?附 RT-DETRv2 依赖缺失终极排坑
开发语言·ide·人工智能·python·pycharm
再写一行代码就下班8 小时前
根据给定word模板,动态填充指定内容,并输出为新的word文档。(${aa}占位符方式且支持循环动态表格)
java·开发语言
星幻元宇VR8 小时前
VR禁毒骑行系统|以沉浸式体验提升禁毒宣传教育效果
人工智能·科技·学习·安全·vr·虚拟现实
七夜zippoe8 小时前
DolphinDB流数据表:创建与订阅
开发语言·订阅··dolphindb·数据表