Linux基础-shell的简单实现

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

Linux基础-shell的简单实现

收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1, 全局变量和常量定义

2. 环境信息获取

2.1 GetUserName

2.2 GetHostName

2.3 GetPwd

2.4 LastDir

3. 命令行提示符

3.1 MakeCommandLine

3.2  PrintCommandLine

4, 命令输入处理

4.1 GetCommandLine

4.2 ParseCommandLine

5. 命令调试

5.1 debug

6. 命令执行

6.1 ExecuteCommand

子进程执行命令:

等待子进程结束:

处理子进程的返回状态: 

7. 环境变量管理

7.1 AddEnv

 7.2 InitEnv

8. 内建命令处理

8.1 CheckAndExeBuiltCommand

8.1.1 cd命令

8.1.2 export命令

8.1.3 evn命令

8.1.4 echo命令 

8.2 为什么使用内建命令 

9. 主函数

10. 效果展示:


1, 全局变量和常量定义

功能 : 定义常量和全局变量,包括命令行参数、环境变量、当前工作目录、和最近的命令执行状态代码。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;

//全局的命令行参数
char *gragv[argvnum];
int gargc = 0;

//全局变量
int lastcode = 0;

//我的系统的环境变量
char *genv[envnum];

//全局的当前shell工作路径
char pwd[basesize];
char pwdenv[basesize];

basesize: 定义了缓冲区的基础大小,用于存储用户输入的命令和路径。

argvnum: 定义最大命令行参数的数量。

envnum: 定义最大环境变量的数量。

gargv: 存储解析后的命令行参数的数组。

gargc: 表示当前解析出的参数数量。

lastcode: 保存最近执行命令的退出状态。

genv: 存储环境变量的数组。

pwd 和 pwdenv: 存储当前工作目录和环境变量PWD的缓冲区。

2. 环境信息获取

功能 : 获取当前用户、主机名和当前工作目录的信息,返回相应的字符串。

2.1 GetUserName

GetUserName: 获取当前用户的用户名,使用getenv从环境变量中获取,若未获取到,则返回"None"。 

string GetUserName()
{
    string name = getenv("USER");
    return name.empty() ? "None" : name;
}

2.2 GetHostName

GetHostName: 获取主机名,类似于获取用户名的方式。 

string GetHostName()
{
    string hostname = getenv("HOSTNAME");
    return hostname.empty() ? "None" : hostname;
}

2.3 GetPwd

GetPwd: 获取当前工作目录,使用getcwd函数获取并更新环境变量PWD。如果获取失败,返回"None"。

string GetPwd()
{
    if (nullptr == getcwd(pwd, sizeof(pwd)))
        return "None";
    snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd);
    putenv(pwdenv);
    return pwd;
}

补充知识:

snprintf 是一个安全的字符串格式化函数,用于将格式化的数据写入字符串。

pwdenv 是另一个字符数组,用于存储更新后的环境变量字符串。

这里,"PWD=%s" 是格式化字符串,%s 被当前工作目录的值(即 pwd 中的值)替换。最终结果类似于 PWD=/home/user(假设当前工作目录是 /home/user)。

putenv 是一个函数,用于设置环境变量。

通过调用 putenv(pwdenv),将之前构造的环境变量字符串 PWD=/path/to/current/dir 添加到进程的环境中。

注意,使用 putenv 要确保 pwdenv 的内容在后续使用中依然有效,因为 putenv 不会复制传入的字符串。

2.4 LastDir

LastDir: 从当前路径中提取最后一个目录名称,方便在命令行提示符中显示。 

string LastDir()
{
    string curr = GetPwd();
    if (curr == "/" || curr == "None")
        return curr;
    size_t pos = curr.rfind("/");
    if (pos == std::string::npos)
        return curr;
    return curr.substr(pos + 1);
}

注意 : 这里curr == "/" || curr == "None" 我们都不需要额外处理, 直接返回就行, 因为curr为"/" 表示根目录, 为None时, 我们直接返会空就行 

3. 命令行提示符

功能:生成和打印命令行提示符,包含用户、主机和当前目录信息。 

3.1 MakeCommandLine

MakeCommandLine: 生成当前命令行提示符的字符串格式,包括用户、主机和当前目录。

string MakeCommandLine()
{
    //[XXX@XXXXX myshell]$ 这里就与普通命令行进行区分
    char command_line[basesize];
    snprintf(command_line, basesize, "[%s@%s %s]#", GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());
    return command_line;
}

3.2  PrintCommandLine

PrintCommandLine: 打印生成的命令行提示符到标准输出。 

void PrintCommandLine()
{
    printf("%s", MakeCommandLine().c_str());
    fflush(stdout);
}

4, 命令输入处理

功能:从标准输入获取用户输入的命令,并解析成命令及其参数。 

4.1 GetCommandLine

GetCommandLine: 从标准输入获取用户的命令行输入,使用fgets读取并处理换行符。

bool GetCommandLine(char command_buffer[], int size)
{
    //我们认为 : 我们要将用户出入的命令行 -> 当作一个完整的字符串
    //"ls -a -1 -n"
    char* result = fgets(command_buffer, size, stdin);
    if(!result)
    {
        return false;
    }
    command_buffer[strlen(command_buffer) - 1] = 0;
    if(strlen(command_buffer) == 0) return false;
    return true;
}

补充知识:

fgets 是一个标准库函数,用于从标准输入(stdin)读取一行字符,最多读取 size - 1 个字符,并将其存储在 command_buffer 中。

fgets 会在读取到换行符 ('\n') 或 EOF(文件结束符)时停止读取,并会在 command_buffer 的末尾自动添加一个空字符 ('\0')。

如果成功读取,result 将指向 command_buffer;如果失败(例如,输入错误或 EOF),result 将为 nullptr。

注意:  strlen(command_buffer) - 1 获取字符串的最后一个字符(换行符)的索引,并将其设为 0,以去掉换行符。

比如:我们输入 ls -a -l -n

fgets 会将缓冲区填充为: command_buffer = "ls -a -l -n\n\0"

经过strlen(command_buffer) - 1处理后 : "ls -a -l -n\0"

4.2 ParseCommandLine

ParseCommandLine: 使用strtok将输入的命令行字符串分割为单独的命令和参数,并存储在gargv数组中。

void ParseCommandLine(char command_buffer[], int len)
{
    (void)len;
    memset(gargv, 0, sizeof(gargv));
    gargc = 0;
    //"ls -a -l -n"
    const char *sep = " ";
    gargv[gargc++] = strtok(command_buffer, sep);
    //=是刻意写的
    while((bool)(gargv[gargc++] = strtok(nullptr, sep)));
    gargc--;
}

补充知识:

 strtok 函数会将 command_buffer 字符串中的第一个分隔符(空格)替换为 \0,并返回指向第一个标记的指针,存储到 gargv 数组中。

strtok(nullptr, sep) 意味着继续解析之前传入的字符串。

注意 (bool) 转换是为了确保表达式的结果为布尔值,这样可以明确表示循环的条件。

5. 命令调试

5.1 debug

功能 : 输出当前命令参数的数量和每个参数的值,以帮助开发和调试。 

void debug()
{
    printf("argc: %d\n", gargc);
    for(int i = 0; gargv[i]; i++)
    {
        printf("argv[%d]: %s\n", i, gargv[i]);
    }
}

6. 命令执行

6.1 ExecuteCommand

功能:通过fork()和execvpe()创建子进程执行用户命令,并等待其完成。 

bool ExecuteCommand()
{
    //让子进程进行执行
    pid_t id = fork();
    if(id < 0) return false;
    if(id == 0)
    {
        //子进程
        //1. 执行命令
        execvpe(gargv[0], gargv, genv);
        //2. 退出
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else
        {
            lastcode = 100;
        }
        return true;
    }
    return false;
}

子进程执行命令:

execvpe(gargv[0], gargv, genv);execvpe 用于执行指定的命令。它会用新的程序替换当前进程(子进程)。参数说明:

gargv[0]:命令的名称(通常是命令的路径)。

gargv:一个字符串数组,包含传递给命令的参数。

genv:一个字符串数组,包含环境变量。

如果 execvpe 成功,当前进程(子进程)将被新的程序替换,之后的代码将不会执行。

如果 execvpe 失败(例如命令未找到),子进程将执行 exit(1),返回状态 1,以指示错误。

等待子进程结束:

int status = 0;:用于存储子进程的退出状态。

waitpid(id, &status, 0):在父进程中调用,等待指定的子进程(由 id 指定)结束。结果存储在 status 变量中。

返回值 rid:如果成功,返回已结束子进程的 PID;如果失败,返回 -1。

处理子进程的返回状态: 

if(rid > 0):确认 waitpid 调用成功,rid 应该是大于 0 的子进程 PID。

WIFEXITED(status):这个宏检查子进程是否正常退出(通过 exit() 或 return 语句)。

WEXITSTATUS(status):如果子进程正常退出,获取其返回状态并将其存储在 lastcode 中。

如果子进程未正常退出(例如由于信号导致的终止),则将 lastcode 设置为 100,这通常用于指示异常情况。

return true;:表示成功执行命令并处理了其退出状态。

7. 环境变量管理

功能:动态分配内存,将新的环境变量添加到genv数组中。

7.1 AddEnv

void AddEnv(const char* item)
{
    int index = 0;
    while(genv[index])
    {
        index++;
    }
    genv[index] = (char*)malloc(strlen(item) + 1);
    strncpy(genv[index], item, strlen(item) + 1);
    genv[++index] = nullptr;
}

功能 : 添加环境变量:该函数接受一个字符串 item 作为参数,并将其添加到 genv 环境变量数组中。

逐行分析

int index = 0;:初始化索引为 0,用于遍历 genv 数组。

while(genv[index]):遍历 genv 数组,直到找到第一个空指针,表示可以插入新环境变量的位置。

genv[index] = (char*)malloc(strlen(item) + 1);:

为新的环境变量分配内存。malloc 分配的内存大小为 item 字符串的长度加 1(为字符串结束符 \0 留出空间)。

strncpy(genv[index], item, strlen(item) + 1);:

将 item 的内容复制到 genv[index] 中。使用 strncpy 复制到长度 strlen(item) + 1,确保包含字符串结束符。

genv[++index] = nullptr;:

在数组中下一个位置设置为 nullptr,标志着环境变量的结束。

 7.2 InitEnv

void InitEnv()
{
    extern char **environ;
    int index = 0;
    while(environ[index])
    {
        genv[index] = (char*)malloc(strlen(environ[index] + 1));
        strncpy(genv[index], environ[index], strlen(environ[index] + 1));
        index++;
    }
    genv[index] = nullptr;
}

功能 : 初始化环境变量:该函数从系统环境变量中读取所有环境变量并将其存储在 genv 数组中。

逐行分析

extern char **environ;:声明外部变量 environ,这是一个指向环境变量字符串数组的指针

int index = 0;:初始化索引为 0,用于遍历 environ。

while(environ[index]):遍历 environ 数组,直到找到第一个空指针,表示所有环境变量都已读取。

genv[index] = (char*)malloc(strlen(environ[index]) + 1);:

为每个环境变量分配内存。这里 strlen(environ[index]) 计算字符串的长度,并加 1 来包含 \0 结束符。

strncpy(genv[index], environ[index], strlen(environ[index] + 1));:

将 environ[index] 的内容复制到 genv[index] 中。这里同样存在一个错误,应该是 strncpy(genv[index], environ[index], strlen(environ[index]) + 1);。

index++;:增加索引,以便处理下一个环境变量。

genv[index] = nullptr;:在数组最后设置 nullptr,表示环境变量的结束

8. 内建命令处理

8.1 CheckAndExeBuiltCommand

bool CheckAndExeBuiltCommand()
{
    if(strcmp(gargv[0], "cd") == 0)
    {
        //内建命令
        if(gargc == 2)
        {
            chdir(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 1;
        }
        return true;
    }
    else if(strcmp(gargv[0], "export") == 0)
    {
        //export也是内建命令
        if(gargc == 2)
        {
            AddEnv(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 2;
        }
        return true;
    }
    else if(strcmp(gargv[0], "env") == 0)
    {
        for(int i = 0; genv[i]; i++)
        {
            printf("%d\n", genv[i]);
        }
        lastcode = 0;
        return true;
    }
    else if(strcmp(gargv[0], "echo") == 0)
    {
        if(gargc == 2)
        {
            //echo $?
            //echo $PATH
            //echo hello
            if(gargv[1][0] == '$')
            {
                if(gargv[1][1] == '?')
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else
                {
                    printf("%s\n", gargv[1]);
                    lastcode = 0;
                }
            }
            else
            {
                lastcode = 3;
            }
            return true;
        }
        return false;
    }
}

8.1.1 cd命令

功能:用于更改当前工作目录。

逻辑:

检查参数个数 gargc 是否等于 2(即命令和目录)。

如果参数正确,调用 chdir 函数改变目录,并将 lastcode 设置为 0,表示成功。

如果参数不正确,将 lastcode 设置为 1,表示错误。

chdir() : 将调用进程的当前工作目录更改为path中指定的目录 

8.1.2 export命令

功能:用于设置或导出环境变量。

逻辑:

同样检查参数个数 gargc 是否为 2。

如果正确,调用 AddEnv 函数添加环境变量,并将 lastcode 设置为 0。

如果参数不正确,设置 lastcode 为 2。

8.1.3 evn命令

功能:列出所有环境变量。

逻辑:

遍历 genv 数组,打印每个环境变量。

将 lastcode 设置为 0。

8.1.4 echo命令 

功能:输出字符串或变量。

逻辑:

检查参数个数是否为 2。

如果是,检查 gargv[1] 的第一个字符是否为 $:

如果是 $?,输出 lastcode 的值。

如果是其他以 $ 开头的字符串,直接输出这个字符串(假设它是环境变量)。

如果没有 $,则将 lastcode 设置为 3。

如果参数个数不正确,返回 false。

8.2 为什么使用内建命令 

快速响应:内建命令通常直接在 shell 进程内执行,无需创建新的进程,减少了上下文切换的开销。这使得内建命令的执行速度更快。

基本功能:内建命令提供了一些与 shell 紧密相关的基本功能,如改变目录(cd)、设置环境变量(export)、获取当前环境(env)等,这些操作通常与 shell 的状态直接相关。

环境影响:某些内建命令(如 cd)会影响 shell 的状态(例如当前工作目录),如果用外部程序实现,改变的状态不会反映到 shell 中。

简化用户操作:用户可以直接使用内建命令而无需担心路径问题。例如,cd 命令只需要提供目标路径,而不必寻找相应的外部可执行文件。

状态保持:内建命令如 echo 和 export 能够直接访问 shell 的状态和环境变量,而不需要通过外部程序来管理,这样可以更好地处理如 $?(上一个命令的退出状态)这样的特殊情况。

9. 主函数

 功能:主程序循环,负责显示提示符、获取用户命令、解析命令、检查内建命令,并执行命令。

int main()
{
    InitEnv();
    char command_buffer[basesize];
    while(true)
    {
        //1. 命令行提示符
        PrintCommandLine();
        //command_buffer -> output

        //2. 获取用户命令
        if(!GetCommandLine(command_buffer, basesize))
        {
            continue;
        }
        
        //3. 解析命令
        ParseCommandLine(command_buffer, strlen(command_buffer));

        if(CheckAndExeBuiltCommand())
        {
            continue;
        }

        4. 执行命令
        ExecuteCommand();
    }
    return 0;
}

10. 效果展示:

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

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

相关文章

MatLab Desired port was :31515解决方案

前言&#xff1a;使用的MatLabR2022b今天突然出现了错误&#xff0c;在程序中打不开文件。后尝试了下面的方法&#xff0c;可以解决。 解决方法一&#xff1a; 搜索栏输入&#xff1a;firewall.cpl 找到相关项&#xff0c;右键属性&#xff0c;设置为允许。 之后就可以了…

超声波清洗机双十一值得买吗?2024四款顶级超声波清洗机推荐!

许多朋友们可能还在犹豫不决&#xff0c;思考着在双十一这个购物狂欢节里&#xff0c;究竟应该选择哪一款品牌的超声波清洗机来购买呢&#xff1f;不用担心&#xff0c;今天我将为大家详细解读2024年四款备受瞩目的顶级超声波清洗机&#xff0c;让你在双十一的购物狂欢中不再感…

地理空间与交通流量数据集:TaxiNYC、TaxiBJ、BikeDC和BikeNYC

目录 TaxiNYC数据集TaxiBJ数据集BikeDC数据集1. **数据来源与时间范围**2. **数据内容**3. **区域划分与站点处理**4. **图结构构建**5. **人群流动计算**6. **数据集的应用场景**7. **预测任务设置**8. **图的构建** BikeNYC数据集1. **数据来源与时间范围**2. **数据内容**3.…

单位评职称需要在指定媒体上投稿发表文章看我如何轻松应对

在职场中,晋升与评职称是一项不可或缺的任务,而在这个过程中,完成相关的投稿更是至关重要。作为单位的一名员工,当我得知自己需要在指定的媒体上发表文章以满足职称评审要求时,心中既期待又忐忑。起初,我选择了传统的邮箱投稿方式,然而却没想到,这条路竟让我倍感挫折。 刚开始,…

FlexMatch: Boosting Semi-Supervised Learning with Curriculum Pseudo Labeling

FlexMatch: Boosting Semi-Supervised Learning with Curriculum Pseudo Labeling 摘要:引言:背景3 flexMatch3.1 Curriculum Pseudo Labeling3.2 阈值预热3.3非线性映射函数实验4.1 主要结果4.2 ImageNet上的结果4.3收敛速度加速4.4 消融研究5 相关工作摘要: 最近提出的Fi…

◇【论文_20150225】 DQN_2015(nature) 〔Google DeepMind〕

整理代码 1&#xff1a;DQN CartPole_v1.ipynb https://www.nature.com/articles/nature14236 Human-level control through deep reinforcement learning 文章目录 摘要主体&#xff1a;要做什么 如何做的 要点keypoints实验 与 评估2 个指标和 各游戏的最好方法比较t-S…

数据湖新突破:Hudi让实时数据分析更高效!

开源数据湖对比 Hudi的使用收益 Hudi使用成效 Hudi内部机制 增量摄入与更新 Hudi使用一种混合日志存储模式(称为Copy-on-Write),可以同时处理基础数据文件(Parquet)和增量日志(HoodieLogFile)。以 MergeOnReadTable 的 upsert 操作为例,当有新数据到来时,Hudi会先将数据以行…

【OpenMMLab】MMagic入门

1. 概述 OpenMMLab 概述&#xff1a;OpenMMLab 是上海人工智能实验室的计算机视觉算法开源体系&#xff0c;是深度学习时代全球领域最全面、最具影响力的视觉算法开源项目&#xff0c;也是全球最大最全的开源计算机视觉算法库。特点&#xff1a; 丰富的算法库&#xff1a;已累…

第三天-128.最长连续序列

这道题我完全没有思路&#xff0c;求助gpt&#xff0c;让它给我思路&#xff1a; 这个问题要求找出数组中数字连续的最长序列&#xff0c;并且时间复杂度必须是 O(n)&#xff0c;可以采用 哈希集&#xff08;HashSet&#xff09;来帮助我们高效地判断数字是否存在。以下是解决…

AI周报(10.13-10.19)

AI应用-清华校友用AI破解162个高数定理 加州理工、斯坦福和威大的研究人员提出了LeanAgent——一个终身学习&#xff0c;并能证明定理的AI智能体。LeanAgent会根据数学难度优化的学习轨迹课程&#xff0c;来提高学习策略。并且&#xff0c;它还有一个动态数据库&#xff0c;有效…

Ubuntu如何显示pcl版本

终端输入&#xff1a; apt-cache show libpcl-dev可以看到&#xff0c;Ubuntu20.04&#xff0c;下载的pcl&#xff0c;应该都是1.10版本的

百易云资产管理运营系统 ufile.api.php SQL注入漏洞复现

0x01 产品描述&#xff1a; 百易云资产管理运营系统&#xff0c;是专门针对企业不动产资产管理和运营需求而设计的一套综合解决方案。该系统能够覆盖资产的全生命周期管理&#xff0c;包括资产的登记、盘点、评估、处置等多个环节&#xff0c;同时提供强大的运营分析功能&#…

执行php artisan storage:link报错

php artisan storage:link Call to undefined function Illuminate\Filesystem\symlink() 参考文章 https://learnku.com/laravel/t/73729

基于web的酒店客房管理系统【附源码】

基于web的酒店客房管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2系统结构 4.3.数据库设计 4.3.1数据库实体 4.3.2数据库设计表 5系统详细实现 5.1 用户信息管理 5.2 会员信息管理 5.3 客房信息管理 5.…

基于SpringBoot健康生活助手微信小程序【附源码】

基于SpringBoot健康生活助手微信小程序 效果如下&#xff1a; 管理员登录界面 管理员主界面 用户管理界面 健康记录管理界面 健康目标管理界面 微信小程序首页界面 活动信息界面 留言反馈界面 研究背景 近年来&#xff0c;由于计算机技术和互联网技术的飞速发展&#xff0c;…

SAP PP之功能 动态安全库存(Dynamic Safety stock)配置及计算逻辑说明测试

SAP动态安全库存&#xff08;Dynamic Safety stock&#xff09;配置及计算逻辑说明测试 概念及计算逻辑&#xff1a; 动态安全库存&#xff08;Dynamic Safety stock&#xff09;&#xff1a; 它根据平均的日需求&#xff08;Average daily requirements&#xff09;数量&am…

父子元素中只有子元素设置margin-bottom的问题

问题代码如下所示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>.div1 {background-color: red;width: 80px;height: 80px;border: 1px solid orange;}.div2 {bac…

STM32—FLASH闪存

1.FLASH简介 STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过闪存存储器接口&#xff08;外设&#xff09;可以对程序存储器和选项字节进行擦除和编程 我们怎么操作这些存储器呢&#xff1f;这就需要用到这个闪存存储器接口了&#xff0c;闪…

联系拯救者Y9000P2022笔记本电脑进入BIOS快捷键

联系拯救者Y9000P2022笔记本电脑进入BIOS快捷键 文章目录 联系拯救者Y9000P2022笔记本电脑进入BIOS快捷键1. 进入BIOS快捷键2. 快速进入BIOS设置界面3. 快速进入启动项选择界面 1. 进入BIOS快捷键 进入BIOS设置界面的快捷键为F2快速进入启动项选择界面的快捷键为F12 2. 快速进…

充电桩高压快充发展趋势

一、为什么要升级充电电压 1、新能源发展的困境 随着电动汽车加快发展&#xff0c;用户对电动汽车接受度不断提高&#xff0c;充电问题是影响电动车普及的重要因素&#xff0c;用户快速补能的需求强烈&#xff0c;例如节假日经常会遇到&#xff0c;高速充电1小时&#xff0c;…