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

【Linux系统编程】—— 从零开始实现一个简单的自定义Shell

文章目录

  • 什么是自主shell命令行解释器?
  • 实现shell的基础认识
  • 全局变量的配置
  • 初始化环境变量
  • 实现内置命令(如 cd 和 echo)
    • cd命令:
    • echo命令:
  • 构建命令行提示符
  • 获取并解析用户输入的命令
  • 执行内置命令与外部命令
  • Shell的主循环
  • 最后头文件献上

前言:在前文当中,我们学习了进程的概念以及进程等待、终止、替换等等。 本篇博客的主题是关于写一个⾃主Shell命令⾏解释器

什么是自主shell命令行解释器?

自主Shell命令行解释器(通常称为“shell”)是一个允许用户与操作系统进行交互的命令行界面。用户可以通过Shell输入命令,Shell负责将这些命令传递给操作系统内核,然后执行相应的任务。

在云服务器(虚拟机)上,我们可以使用系统所提供的Bash,即Linux下常见的shell
Shell解释器的功能包括:

  1. 命令执行:它会解析用户输入的命令,并将其传递给操作系统内核以执行。
  2. 脚本执行:Shell能够执行一系列命令(称为脚本),通常用于自动化任务。
  3. 输入输出重定向:Shell允许用户将命令的输入输出重定向到文件或其他命令,以实现更灵活的任务处理。
  4. 环境管理:它管理用户的环境变量和配置,允许用户定制Shell的行为。
  5. 交互式与批处理模式:Shell不仅可以作为交互式的命令行工具使用,也可以用来批量执行命令和脚本。

实现shell的基础认识

我们可以通过参考Linux当中的shell做出我们自己的shell
命令行的外表
用户名, 主机名,当前路径都保存在环境变量中。
在这里插入图片描述
所以在我们的要实现的代码当中,我们可以将这三个值通过封装3个函数得到:

const char *GetUserName()
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()
{
    const char *pwd = getcwd(cwd, sizeof(cwd));
    if (pwd != NULL)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
        putenv(cwdenv);  // 更新PWD环境变量
    }
    return pwd == NULL ? "None" : pwd;
}

通过getenv(), getcwd(),这些内置函数,我们可以知道需要的一些shell的值
当然也可以不用这么复杂的得到

//获取用户名
const char*getusername()
{
	return getenv("USER");
}
//获取主机名
const char*gethostname()
{
	return getenv("HOSTNAME");
}
//获取路径名
const char*getpwd()
{
	return getenv("PWD");
}

这样也是可以简单的得到的,为了完整性,我们选择较复杂的那种,当然,自己想要简单实现可以使用下面这一种.

全局变量的配置

#define MAXARGC 128  // 最大命令行参数数量
char *g_argv[MAXARGC];  // 存储命令行参数
int g_argc = 0;  // 命令行参数数量

#define MAX_ENVS 100  // 最大环境变量数量
char *g_env[MAX_ENVS];  // 存储环境变量
int g_envs = 0;  // 环境变量数量

std::unordered_map<std::string, std::string> alias_list;  // 存储命令别名

char cwd[1024];  // 当前工作目录
char cwdenv[1024];  // 用于存储更新后的PWD环境变量

int lastcode = 0;  // 上一个命令的退出状态

初始化环境变量

你需要初始化Shell的环境变量,并从系统中获取它们。通过 environ 可以访问到当前系统的环境变量。

void InitEnv()
{
    extern char **environ;
    memset(g_env, 0, sizeof(g_env));
    g_envs = 0;

    // 获取系统环境变量
    for (int i = 0; environ[i]; i++)
    {
        g_env[i] = (char *)malloc(strlen(environ[i]) + 1);
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    g_env[g_envs++] = (char *)"HAHA=for_test";  // 测试环境变量
    g_env[g_envs] = NULL;

    // 设置环境变量
    for (int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}

实现内置命令(如 cd 和 echo)

在Shell中,通常有一些内置命令,如 cd 和 echo。你需要编写函数来实现这些命令。

cd命令:

bool Cd()
{
    if (g_argc == 1)
    {
        std::string home = GetHome();
        if (home.empty()) return true;
        chdir(home.c_str());  // 切换到家目录
    }
    else
    {
        std::string where = g_argv[1];
        chdir(where.c_str());  // 切换到指定目录
    }
    return true;
}

echo命令:

void Echo()
{
    if (g_argc == 2)
    {
        std::string opt = g_argv[1];
        if (opt == "$?")
        {
            std::cout << lastcode << std::endl;
            lastcode = 0;  // 重置退出状态
        }
        else if (opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if (env_value)
                std::cout << env_value << std::endl;
        }
        else
        {
            std::cout << opt << std::endl;
        }
    }
}

构建命令行提示符

在每次等待用户输入时,你需要显示一个命令行提示符。这个提示符通常包括用户名、主机名和当前工作目录。

void MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}

获取并解析用户输入的命令

Shell需要读取用户的命令并对其进行解析,将命令行分解成不同的参数。

bool GetCommandLine(char *out, int size)
{
    char *c = fgets(out, size, stdin);
    if (c == NULL) return false;
    out[strlen(out) - 1] = 0;  // 清除换行符
    if (strlen(out) == 0) return false;
    return true;
}

bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc = 0;
    g_argv[g_argc++] = strtok(commandline, SEP);
    while ((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

执行内置命令与外部命令

你需要检查命令是否为内置命令。如果是内置命令,则直接执行相应的功能;否则,创建子进程来执行外部命令。
检查并执行内置命令:

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if (cmd == "cd")
    {
        Cd();  // 执行cd命令
        return true;
    }
    else if (cmd == "echo")
    {
        Echo();  // 执行echo命令
        return true;
    }
    return false;
}

执行外部命令:

int Execute()
{
    pid_t id = fork();
    if (id == 0)
    {
        execvp(g_argv[0], g_argv);  // 子进程执行命令
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);  // 等待子进程结束
    if (rid > 0)
    {
        lastcode = WEXITSTATUS(status);  // 获取退出状态
    }
    return 0;
}

Shell的主循环

在Shell的主循环中,你需要不断显示命令提示符,获取用户输入,解析命令,并根据命令类型执行相应操作。

int main()
{
    InitEnv();  // 初始化环境变量
    while (true)
    {
        PrintCommandPrompt();  // 打印提示符

        // 获取用户输入的命令行
        char commandline[COMMAND_SIZE];
        if (!GetCommandLine(commandline, sizeof(commandline)))
            continue;

        // 解析命令行
        if (!CommandParse(commandline))
            continue;

        // 检查并执行内置命令
        if (CheckAndExecBuiltin())
            continue;

        // 执行外部命令
        Execute();
    }
    return 0;
}

最后头文件献上

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

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

通过上面的每一步就可以在Linux当中做出自己的简单shell。


http://www.kler.cn/a/513286.html

相关文章:

  • freecad1.0的编译
  • ConvBERT:通过基于跨度的动态卷积改进BERT
  • k8s集群换IP
  • 【QT】已解决:Qt4.11.0无法使用MSVC编译器问题
  • [gdb调试] gdb调试基础实践gdb指令汇总
  • 内存故障原因与诊断(Reasons and Diagnosis of Memory Failure)
  • 基于昇腾300I-Duo推理卡部署Embedding与Rerank模型
  • 【Linux环境变量与命令行参数】常见环境变量 | 环境变量的全局属性 | 命令行参数
  • logback日志自定义占位符
  • redis热Key问题
  • 关于JS浅拷贝和深拷贝的理解
  • Axial Attention in Multidimensional Transformers参考文献翻译
  • 算法-键盘行
  • 分布式系统通信解决方案:Netty Marshalling 全面解析
  • Flutter:carousel_slider 横向轮播图、垂直轮播公告栏实现
  • IP属地:是身份证还是手机归属地?
  • C#,入门教程(01)—— Visual Studio 2022 免费安装的详细图文与动画教程
  • 基于注解实现去重表消息防止重复消费
  • JMeter + Grafana +InfluxDB性能监控
  • 第17章 安全培训筑牢梦想根基
  • 洛谷P8837
  • csapp笔记——2.2节整数表示
  • 智能仓储管理:从自动化到智能化的演进与挑战
  • leetcode:511. 游戏玩法分析 I
  • 数据结构详解——排序
  • 学会与人交谈