Linux自行实现的一个Shell(15)

文章目录

  • 前言
  • 一、头文件和全局变量
    • 头文件
    • 全局变量
  • 二、辅助函数
    • 获取用户名
    • 获取主机名
    • 获取当前工作目录
    • 获取最后一级目录名
    • 生成命令行提示符
    • 打印命令行提示符
  • 三、命令处理
    • 获取用户输入
    • 解析命令行
    • 执行外部命令
  • 四、内建命令
    • 添加环境变量
    • 检查和执行内建命令
  • 五、初始化
    • 初始化环境变量
    • 主循环
  • 总结


前言

  MyShell源代码公开

  本篇是对之前知识的一个综合运用,也是检验你是否对前置知识有个较为透彻的理解的好时机

在这里插入图片描述


一、头文件和全局变量

头文件

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

全局变量

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;// 全局的命令行参数表
char *gargv[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:存储当前工作目录

二、辅助函数

获取用户名

string GetUserName()
{string name = getenv("USER");return name.empty() ? "None" : name;
}
  • 通过 getenv(“USER”) 获取当前用户名
  • 如果获取失败返回 “None”

获取主机名

string GetHostName()
{string hostname = getenv("HOSTNAME");return hostname.empty() ? "None" : hostname;
}
  • 通过 getenv(“HOSTNAME”) 获取主机名
  • 如果获取失败返回 “None”

获取当前工作目录

string GetPwd()
{if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);putenv(pwdenv); // PWD=XXXreturn pwd;
}
  • 使用 getcwd() 获取当前工作目录
  • 如果失败返回 “None”
  • 将当前目录设置到环境变量 PWD 中
  • 返回当前目录路径

获取最后一级目录名

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);
}
  • 获取当前目录
  • 如果是根目录或无效目录直接返回
  • 查找最后一个 ‘/’ 的位置
  • 返回最后一个 ‘/’ 之后的部分

生成命令行提示符

string MakeCommandLine()
{char command_line[basesize];snprintf(command_line, basesize, "[%s@%s %s]# ",\GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());return command_line;
}
  • 生成类似 [user@host dirname]# 的提示符

打印命令行提示符

void PrintCommandLine() // 1. 命令行提示符
{printf("%s", MakeCommandLine().c_str());fflush(stdout);
}
  • 打印提示符
  • fflush(stdout) 确保立即显示

三、命令处理

获取用户输入

bool GetCommandLine(char command_buffer[], int size)
{char *result = fgets(command_buffer, size, stdin);if(!result){return false;}// 因为 command_line 里有一个 \n,我们把它替换成 \0 即可command_buffer[strlen(command_buffer)-1] = '\0';if(strlen(command_buffer) == 0) return false;return true;
}
  • 使用 fgets 读取用户输入
  • 移除末尾的换行符
  • 检查是否为空输入

解析命令行

  获取用户输入后,我们需要将接收到的字符串拆分为命令及其参数

  将接收到的字符串拆开

  通过 strtok 函数,我们可以将一个字符串按照特定的分隔符打散,依次返回子串

void ParseCommandLine(char command_buffer[], int len)
{(void)len;memset(gargv, 0, sizeof(gargv));gargc = 0;const char *sep = " ";gargv[gargc++] = strtok(command_buffer, sep);while((bool)(gargv[gargc++] = strtok(nullptr, sep)));gargc--;
}
  • 重置参数数组和计数器
  • 使用 strtok 以空格为分隔符分割命令
  • 将分割后的参数存入 gargv 数组
  • 调整 gargc 为实际参数数量

执行外部命令

bool ExecuteCommand()
{pid_t id = fork();if(id < 0) return false;if(id == 0){execvpe(gargv[0], gargv, genv);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 执行命令
  • 父进程等待子进程结束
  • 保存子进程退出状态到 lastcode

四、内建命令

  内建命令是指直接内置在操作系统内核中的一些命令,与普通的外部命令(外部程序文件)不同。这些内建命令是直接由shell解释器(如Bash、Zsh等)所处理,而不需要通过外部文件的方式来执行。这些内建命令通常在操作系统的shell环境中被频繁使用,并且执行速度更快,因为它们不需要创建新的进程来执行

  在Unix和类Unix操作系统中,通常会有一些内建命令,比如cd、echo、exit等。这些命令不需要单独的可执行文件,而是直接由shell内核提供支持。当用户在shell中输入这些命令时,shell会直接处理它们,而不需要通过搜索系统路径来找到可执行文件

  值得一提的是,某些shell也允许用户通过自定义的方式添加新的内建命令,这样用户可以根据自己的需求来扩展shell的内建功能

添加环境变量

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;
}
  • 找到环境变量数组的末尾
  • 分配内存并复制新环境变量
  • 确保数组以 NULL 结尾

检查和执行内建命令

bool CheckAndExecBuiltCommand()
{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){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("%s\n", genv[i]);}lastcode = 0;return true;}else if(strcmp(gargv[0], "echo") == 0){if(gargc == 2){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;
}

支持的内建命令有:

  1. cd:改变工作目录
  2. export:设置环境变量
  3. env:显示所有环境变量
  4. echo:打印内容或上一条命令的退出码

五、初始化

初始化环境变量

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

从父进程复制环境变量

主循环

int main()
{InitEnv();char command_buffer[basesize];while(true){PrintCommandLine();if( !GetCommandLine(command_buffer, basesize) ){continue;}ParseCommandLine(command_buffer, strlen(command_buffer));if ( CheckAndExecBuiltCommand() ){continue;}ExecuteCommand();}return 0;
}

主循环流程:

  1. 打印提示符
  2. 获取用户输入
  3. 解析命令
  4. 尝试执行内建命令
  5. 如果不是内建命令,则执行外部命令

总结

  感觉如何呢!

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

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

相关文章

RocketMQ和kafka 的区别

一、数据可靠性与容错机制 数据可靠性 RocketMQ支持同步刷盘和同步复制&#xff0c;确保消息写入磁盘后才返回确认&#xff0c;单机可靠性高达10个9&#xff0c;即使操作系统崩溃也不会丢失数据。而Kafka默认采用异步刷盘和异步复制&#xff0c;虽然吞吐量高&#xff0c;但极端…

在 openEuler 24.03 (LTS) 操作系统上添加 ollama 作为系统服务的步骤

以下是在 openEuler 操作系统上添加 ollama 作为系统服务的步骤&#xff1a; 创建 systemd 服务文件 sudo vi /etc/systemd/system/ollama.service将以下内容写入服务文件&#xff08;按需修改参数&#xff09;&#xff1a; [Unit] DescriptionOllama Service Afternetwork.…

光谱相机的关键技术参数

光谱相机的关键技术参数直接影响其数据获取能力和应用场景适配性。以下是核心参数的详细解析&#xff0c;涵盖光谱性能、空间性能、硬件性能及环境适应性&#xff1a; 一、光谱性能参数‌ ‌1. 光谱范围&#xff08;Spectral Range&#xff09;‌ ‌定义‌&#xff1a;相机可…

ARM内核与寄存器

ARM内核与寄存器详解 目录 ARM架构概述ARM处理器模式 Cortex-M3内核的处理器模式Cortex-A系列处理器模式 ARM寄存器集 通用寄存器程序计数器(PC)链接寄存器(LR)堆栈指针(SP)状态寄存器(CPSR/SPSR) 协处理器寄存器NEON和VFP寄存器寄存器使用规范常见ARM指令与寄存器操作 ARM架…

Git 拉取时常见冲突及解决方法总结

Git 拉取时常见冲突及解决方法总结 一、常见错误场景1. 本地修改与远程修改冲突解决方法 2. 未跟踪文件与远程文件冲突解决方法 3. 子模块权限问题解决方法 二、总结 在日常开发中&#xff0c;使用 Git 进行团队协作和代码管理时&#xff0c;经常会遇到拉取代码&#xff08;git…

深度学习、图像算法学习记录

深度学习加速 综述文档&#xff1a; https://chenzomi12.github.io/02Hardware01Foundation/02ArchSlim.html winograd: https://zhuanlan.zhihu.com/p/260109670 ncnn 1.修改模型结构&#xff0c;优化模型内存访问次数&#xff0c;加速。 VGG 和 InceptionNet &#xff1a; …

Java中的Exception和Error有什么区别?还有更多扩展

概念 在Java中&#xff0c;Exception和Error都是Throwable的子类&#xff0c;用于处理程序中的错误和异常情况。 然而&#xff0c;它们在用途和处理方式上有显著的不同&#xff1a; Exception&#xff1a; 用于表示程序在正常运行过程中可能出现的错误&#xff0c;如文件未找…

文章记单词 | 第26篇(六级)

一&#xff0c;单词释义 actor&#xff1a;名词&#xff0c;演员mask&#xff1a;名词&#xff0c;面具&#xff1b;口罩&#xff1b;遮盖物&#xff1b;动词&#xff0c;掩饰&#xff1b;戴面具&#xff1b;遮盖construct&#xff1a;动词&#xff0c;建造&#xff1b;构造&a…

LeetCode算法题(Go语言实现)_38

题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 一、代码实现 type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode }func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {if root nil || root p || root q {return root}left : lowes…

Java 基础语法、Java注释

Java 基础语法 一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫…

用VScode来编写前后端——构建基础框架

前言 我写这一个板块的原因是我参加了我们学校的新生项目课&#xff0c;需要创立一个系统&#xff0c;我们小组选的标题的基于计算机视觉的商品识别系统&#xff0c;那么我们需要一个网站来展示我们的功能&#xff0c;故写这些来记录一下自己&#xff0c;大家如果有什么问题的话…

git clone阻塞问题

问题描述 git clone采用的ssh协议&#xff0c;在克隆仓库的时候&#xff0c;会经常卡一下&#xff0c;亦或是直接卡死不动。 最开始以为是公司电脑配置的问题&#xff0c;想着自己实在解决不了找it帮忙。 查阅资料发现&#xff0c;最终发现是git版本的问题&#xff0c;这个是…

WEB攻防-Java安全JNDIRMILDAP五大不安全组件RCE执行不出网不回显

目录 1. RCE执行-5大类函数调用 1.1 Runtime方式 1.2 Groovy执行命令 1.3 脚本引擎代码注入 1.4 ProcessImpl 1.5 ProcessBuilder 2. JNDI注入(RCE)-RMI&LDAP&高版本 2.1 RMI服务中的JNDI注入场景 2.2 LDAP服务中的JNDI注入场景 攻击路径示例&#…

【Hadoop入门】Hadoop生态之Sqoop简介

1 什么是Sqoop&#xff1f; 在企业的数据架构中&#xff0c;关系型数据库与Hadoop生态系统之间的数据流动是常见且关键的需求。Apache Sqoop&#xff08;SQL-to-Hadoop&#xff09;正是为解决这一问题而生的高效工具&#xff0c;它专门用于在结构化数据存储&#xff08;如RDBMS…

如何自动检测使用的组件库有更新

&#x1f916; 作者简介&#xff1a;水煮白菜王&#xff0c;一位前端劝退师 &#x1f47b; &#x1f440; 文章专栏&#xff1a; 前端专栏 &#xff0c;记录一下平时在博客写作中&#xff0c;总结出的一些开发技巧和知识归纳总结✍。 感谢支持&#x1f495;&#x1f495;&#…

Go语言编写一个进销存Web软件的demo

Go语言编写一个进销存Web软件的demo 用户现在要求用。之前他们已经讨论了用Django实现的方案&#xff0c;现在突然切换到Go&#xff0c;可能有几个原因。首先&#xff0c;用户可能对Go语言感兴趣&#xff0c;或者他们公司的技术栈转向了Go。其次&#xff0c;用户可能希望比较不…

【前缀和】矩阵区域和(medium)

矩阵区域和&#xff08;medium&#xff09; 题⽬描述&#xff1a;解法&#xff1a;代码Java 算法代码&#xff1a;C 算法代码&#xff1a; 题⽬描述&#xff1a; 题⽬链接&#xff1a;1314. 矩阵区域和 给你⼀个 m x n 的矩阵 mat 和⼀个整数 k &#xff0c;请你返回⼀个矩阵 …

Java学习手册:Java发展历史与版本特性

Java作为全球最流行的编程语言之一&#xff0c;其发展历程不仅见证了技术的演进&#xff0c;也反映了软件开发模式的变革。从1995年的首次发布到如今的持续更新&#xff0c;Java始终保持着强大的生命力和广泛的影响力。本文将简要回顾Java的发展历程&#xff0c;并重点介绍其关…

winserver2022备份

安装备份&#xff0c;然后等待安装完成即可 然后可以在这里看到安装好的win server2022备份 一直下一步然后到这里 不要用本地文件夹备份 备份到远程服务器&#xff0c;远程服务器路径 然后确定备份即可 如何恢复呢&#xff1f; 点击右侧的恢复就可以了 打开任务计划程序 这…

Unity 设置弹窗Tips位置

根据鼠标位于屏幕的区域&#xff0c;设置弹窗锚点以及位置 public static void TipsPos(Transform tf) {//获取ui相机var uiCamera GetUICamera();var popup tf.GetComponent<RectTransform>();//获取鼠标位置Vector2 mousePos Input.mousePosition;float screenWidt…