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

news2025/1/22 14:29:50

文章目录

  • 什么是自主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.coloradmin.cn/o/2280413.html

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

相关文章

认识BOM

BOM 弹出层 可视窗口尺寸 屏幕宽高 浏览器内核和其操作系统的版本 剪贴板 是否允许使用cookie 语言 是否在线

[c语言日寄]结构体的使用及其拓展

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

Linux系统的第一个进程是什么?

Linux进程的生命周期从创建开始&#xff0c;直至终止&#xff0c;贯穿了一个进程的整个存在过程。我们可以通过系统调用fork()或vfork()来创建一个新的子进程&#xff0c;这标志着一个新进程的诞生。 实际上&#xff0c;Linux系统中的所有进程都是由其父进程创建的。 既然所有…

5. 马科维茨资产组合模型+AI金融智能体(qwen-max)识别政策意图方案(理论+Python实战)

目录 0. 承前1. AI金融智能体1.1 What is AI金融智能体1.2 Why is AI金融智能体1.3 How to AI金融智能体 2. 数据要素&计算流程2.1 参数集设置2.2 数据获取&预处理2.3 收益率计算2.4 因子构建与预期收益率计算2.5 协方差矩阵计算2.6 投资组合优化2.7 持仓筛选2.8 AI金融…

PostMan最新版本及离线安装指南

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;PostMan是一款流行的API测试工具&#xff0c;它提供了一个直观的用户界面&#xff0c;方便Web开发者和测试人员进行接口测试。本文将指导你如何安装最新版的PostMan&#xff0c;包括在线安装和离线安装两种方法。…

记录一次k8s起不来的排查过程

我在k8s集群&#xff0c;重启了一个node宿主机&#xff0c;竟然发现kubelet起不来了&#xff01;报错如下 这个报错很模糊&#xff0c;怎么排查呢。这样&#xff0c;开两个界面&#xff0c;一个重启kubelet&#xff0c;一个看系统日志(/var/log/message:centos&#xff0c;/va…

grafana + Prometheus + node_exporter搭建监控大屏

本文介绍生产系统监控大屏的搭建&#xff0c;比较实用也是实际应用比较多的方式&#xff0c;希望能够帮助大家对监控系统有一定的认识。 0、规划 grafana主要是展示和报警&#xff0c;Prometheus用于保存监控数据&#xff0c;node_exporter用于实时采集各个应用服务器的事实状…

2024年博客之星主题创作|从零到一:我的技术成长与创作之路

2024年博客之星主题创作&#xff5c;从零到一&#xff1a;我的技术成长与创作之路 个人简介个人主页个人成就热门专栏 历程回顾初来CSDN&#xff1a;怀揣憧憬&#xff0c;开启创作之旅成长之路&#xff1a;从平凡到榜一的蜕变持续分享&#xff1a;打卡基地与成长复盘四年历程&a…

Golang的网络编程安全

Golang的网络编程安全 一、Golang网络编程的基本概念 作为一种现代化的编程语言&#xff0c;具有优秀的并发特性和网络编程能力。在Golang中&#xff0c;网络编程是非常常见的需求&#xff0c;可以用于开发各种类型的网络应用&#xff0c;比如Web服务、API服务、消息队列等。Go…

【2024年华为OD机试】(C/D卷,200分)- 5G网络建设 (JavaScriptJava PythonC/C++)

一、问题描述 题目描述 现需要在某城市进行5G网络建设&#xff0c;已经选取N个地点设置5G基站&#xff0c;编号固定为1到N。接下来需要各个基站之间使用光纤进行连接以确保基站能互联互通。不同基站之间假设光纤的成本各不相同&#xff0c;且有些节点之间已经存在光纤相连。 …

消息队列篇--原理篇--RabbitMQ和Kafka对比分析

RabbitMQ和Kafka是两种非常流行的消息队列系统&#xff0c;但它们的设计哲学、架构特点和适用场景存在显著差异。对比如下。 1、架构设计 RabbitMQ&#xff1a; 基AMQP协议&#xff1a;RabbitMQ是基于AMQP&#xff08;高级消息队列协议&#xff09;构建的&#xff0c;支持多…

玻璃样式的登录界面

AI越来越火了,我们想要不被淘汰就得主动拥抱。推荐一个人工智能学习网站,通俗易懂,风趣幽默,最重要的屌图甚多,忍不住分享一下给大家。点击跳转到网站 先看样式: 源码: <div class="wrapper">

Python数据可视化(够用版):懂基础 + 专业的图表抛给Tableau等专业绘图工具

我先说说文章标题中的“够用版”啥意思&#xff0c;为什么这么写。 按照我个人观点&#xff0c;在使用Python进行数据分析时&#xff0c;我们有时候肯定要结合到图表去进行分析&#xff0c;去直观展现数据的规律和特定&#xff0c;那么我们肯定要做一些简单的可视化&#xff0…

物联网网关Web服务器--CGI开发实例BMI计算

本例子通一个计算体重指数的程序来演示Web服务器CGI开发。 硬件环境&#xff1a;飞腾派开发板&#xff08;国产E2000处理器&#xff09; 软件环境&#xff1a;飞腾派OS&#xff08;Phytium Pi OS&#xff09; 硬件平台参考另一篇博客&#xff1a;国产化ARM平台-飞腾派开发板…

HTML新春烟花

系列文章 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心&#xff08;双心版&#xff09;1…

从结构嵌套的幻梦里:递归与数据构建的精巧和鸣

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 这一节我们来学习递归的相关知识 函数递归 一、什么是递归1.1 递归的思想 二、递归的限制条件三、递归的…

【Linux系统】—— 编译器 gcc/g++ 的使用

【Linux系统】—— 编译器 gcc/g 的使用 1 用 gcc 直接编译2 翻译环境2.1 预处理&#xff08;进行宏替换&#xff09;2.2 编译&#xff08;生成汇编&#xff09;2.3 汇编&#xff08;生成机器可识别代码&#xff09;2.4 链接2.5 记忆小技巧2.6 编译方式2.7 几个问题2.7.1 如何理…

【Unity3D】3D物体摆放、场景优化案例Demo

目录 PlaceManager.cs(放置管理类) Ground.cs(地板类) 和 GroundData.cs(地板数据类) 额外知识点说明 1、MeshFilter和MeshRenderer的Bounds区别 2、Gizmos 绘制一个平行于斜面的立方体 通过网盘分享的文件&#xff1a;PlaceGameDemo2.unitypackage 链接: https://pan.baid…

智能系统的感知和决策

智能系统在感知和决策过程中具备的关键能力表现在智能感知/自主判定上&#xff0c;下面可以从感知的本质、自主判断的含义及其在智能系统中的作用进行深入分析。 1、智能感知&#xff1a;信息获取与理解 智能感知是指智能系统通过传感器或其他数据采集手段获取环境中的信息&…

Spring 中的事件驱动模型

事件驱动的基本了解 事件模式也就是观察者模式&#xff0c;当一个对象改变的时候&#xff0c;所有依赖的对象都会收到一个通知。 Subject&#xff1a;抽象主题 Observer&#xff1a;具体主题 Concrete Subject&#xff1a;抽象观察者&#xff0c;在得到更新通知之后去更新自…