Linux——自定义简单shell

shell

  • 自定义shell
    • 目标
      • 普通命令和内建命令(补充)
    • shell实现
      • 实现原理
      • 实现代码

自定义shell

目标

  • 能处理普通命令
  • 能处理内建命令
  • 要能帮助我们理解内建命令/本地变量/环境变量这些概念
  • 理解shell的运行

普通命令和内建命令(补充)

在Linux系统中,命令大致可以分为普通命令(通常指的是外部命令)和内建命令两大类。以下是关于这两类命令的详细解释:

一、普通命令(外部命令)

定义:普通命令,也被称为外部命令,是Linux系统中的实用程序部分。这些命令通常以单独的程序文件形式存在,并存储在系统的特定目录中(如/bin、/usr/bin、/sbin、/usr/sbin等)。

特点
功能强大:由于外部命令通常是独立的程序,因此它们可以包含复杂的功能和算法。

不随系统一起加载:在系统启动时,外部命令并不会被加载到内存中。它们只在被需要时,由shell程序通过PATH环境变量查找并加载到内存中执行。

执行速度相对较慢:由于需要查找和加载程序文件,以及创建子进程来执行命令,因此外部命令的执行速度通常比内建命令慢。
示例:常见的外部命令包括ls(列出目录内容)、vi(文本编辑器)、grep(文本搜索工具)等。

二、内建命令

定义:内建命令是shell程序的一部分,这些命令被直接集成在shell的源代码中,并随着shell程序的启动而被加载到内存中。

特点
执行速度快:由于内建命令是在shell程序内部执行的,因此它们不需要创建子进程或查找外部程序文件,从而提高了执行速度。

与shell紧密集成:内建命令与shell程序紧密集成,因此它们可以更方便地访问和操作shell的环境变量、函数等。

占用内存较少(但相对外部命令而言):虽然内建命令的执行速度更快,但它们通常会占用一定的内存空间。然而,由于这些命令是shell程序的一部分,因此它们所占用的内存空间通常是可以接受的。

示例:常见的内建命令包括cd(切换目录)、pwd(显示当前工作目录)、echo(输出字符串到标准输出)、history(显示命令历史记录)等。

三、区分方法

在Linux中,可以使用type命令来区分一个命令是内建命令还是外部命令。例如:

输入type cd,输出结果为cd is a shell builtin,表示cd是一个内建命令。
输入type ls,输出结果为ls is aliased to ‘ls --color=auto’(或类似信息),表示ls是一个外部命令(尽管这里显示了别名信息,但可以通过进一步使用type -t ls或查看其路径来确认其为外部命令)。

shell实现

实现原理

⽤下图的时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为sh的⽅块代表,它随着时间的流逝从左向右移动。shell从⽤户读⼊字符串"ls"。shell建⽴⼀个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
在这里插入图片描述

Shell的作用流程

  • 读取输入:
    Shell等待用户输入命令。这可以是直接在命令行提示符下输入的命令,也可以是来自脚本文件的命令。
  • 解析命令:
    Shell解析用户输入的命令,包括命令本身、参数和选项。它会检查命令的语法是否正确,以及命令是否存在。
  • 查找命令:
    Shell判断命令是内置命令还是外部命令。如果是内置命令,则直接执行;如果是外部命令,则在PATH环境变量中查找相应的程序文件。
  • 执行命令:
    Shell将解析后的命令传递给操作系统内核执行。内核会读取命令,并执行相应的操作。这包括调用系统资源、管理文件、执行程序等。
  • 返回输出:
    当命令执行完成后,Shell会返回执行结果或错误信息给用户。这可以是命令的输出内容、状态码等。
  • 环境变量:
    Shell使用环境变量来存储系统配置和用户信息。这些变量在命令执行和脚本编写中起着重要作用。例如,PATH变量指定了系统在哪些目录下查找可执行文件。
  • 重定向和管道:
    Shell支持将命令的输入和输出重定向到文件或其他设备上。同时,它还支持管道操作,可以将一个命令的输出作为下一个命令的输入。

实现代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
using namespace std;
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];string GetUserName()
{string name = getenv("USER");return name.empty() ? "None" : name;
}
string GetHostName()
{string hostname = getenv("HOSTNAME");return hostname.empty() ? "None" : hostname;
}
string GetPwd()
{if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);putenv(pwdenv); // PWD=XXXreturn pwd;//string pwd = getenv("PWD");//return pwd.empty() ? "None" : pwd;
}
string LastDir()
{string curr = GetPwd();if(curr == "/" || curr == "None") return curr;// /home/whb/XXXsize_t pos = curr.rfind("/");if(pos == std::string::npos) return curr;return curr.substr(pos+1);
}string MakeCommandLine()
{// [whb@bite-alicloud myshell]$ char command_line[basesize];snprintf(command_line, basesize, "[%s@%s %s]# ",\GetUserName().c_str(), GetHostName().c_str(),LastDir().c_str());return command_line;
}
void PrintCommandLine() // 1. 命令⾏提⽰符
{printf("%s", MakeCommandLine().c_str());fflush(stdout);
}bool GetCommandLine(char command_buffer[], int size)   //2. 获取⽤⼾命令
{// 我们认为:我们要将⽤⼾输⼊的命令⾏,当做⼀个完整的字符串// "ls -a -l -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;
}void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{(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--;}
void debug()
{printf("argc: %d\n", gargc);for(int i = 0; gargv[i]; i++){printf("argv[%d]: %s\n", i, gargv[i]);}
}
bool ExecuteCommand()   // 4. 执⾏命令
{// 让⼦进程进⾏执⾏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;
}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;}// shell⾃⼰执⾏命令,本质是shell调⽤⾃⼰的函数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){// 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("%s\n", genv[i]);}lastcode = 0;return true;}else if(strcmp(gargv[0], "echo") == 0){if(gargc == 2){// echo $?// echo $PATH// echo helloif(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;
}
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(); // 1. 	命令⾏提⽰符// command_buffer -> outputif( !GetCommandLine(command_buffer, basesize) )   // 2. 获取⽤⼾命令{continue;}//printf("%s\n", command_buffer);ParseCommandLine(command_buffer, strlen(command_buffer)); // 3. 分析命令if ( CheckAndExecBuiltCommand() ){continue;}ExecuteCommand();   // 4. 执⾏命令}return 0;}

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

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

相关文章

如何把Qt exe文件发送给其他人使用

如何把Qt exe文件发送给其他人使用 1、先把 Debug改成Release2、重新构建项目3、运行项目4、找到release文件夹5、新建文件夹&#xff0c;存放exe文件6、打开qt控制台串口7、下载各种文件8、压缩&#xff0c;发送压缩包给别人 1、先把 Debug改成Release 2、重新构建项目 3、运行…

Kafka的消费消息是如何传递的?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka的消费消息是如何传递的&#xff1f;】面试题。希望对大家有帮助&#xff1b; Kafka的消费消息是如何传递的&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;消息的消费是通过消费…

el-drawer如何实现增加resize拖曳改变宽度大小,通过小图标进行拖拽

请先看效果图 我主要是通过这个按钮来进行拖拽的&#xff0c;记住自行添加按钮图片 第一步 新建一个myDrawerDrag.js文件 import Vue from vueVue.directive(drawerDrag, {bind(el, binding, vnode, oldVnode) {const minWidth 400const dragDom el.querySelector(.el-drawe…

C#窗体简单登录

创建一个Windows登录程序&#xff0c;创建两个窗体&#xff0c;一个用来登录&#xff0c;一个为欢迎窗体&#xff0c;要求输入用户名和密码&#xff08;以个人的姓名和学号分别作为用户名和密码&#xff09;&#xff0c;点击【登录】按钮登录&#xff0c;登录成功后显示欢迎窗体…

【大数据学习 | Spark-SQL】定义UDF和DUAF,UDTF函数

1. UDF函数&#xff08;用户自定义函数&#xff09; 一般指的是用户自己定义的单行函数。一进一出&#xff0c;函数接受的是一行中的一个或者多个字段值&#xff0c;返回一个值。比如MySQL中的&#xff0c;日期相关的dateDiff函数&#xff0c;字符串相关的substring函数。 先…

linux——进程间通信及管道的应用场景

linux进程的控制-CSDN博客 liunx——进程间通信&#xff08;管道通信&#xff09;-CSDN博客 文章目录 文章目录 前言 二、管道的应用 1.创建子进程 1、描述&#xff1a; 2.创建进程及管理 3、子进程接受任务 4、控制子进程 总结 前言 上篇博客我们学习了进程间通信&…

FPGA实现串口升级及MultiBoot(十)串口升级SPI FLASH实现

本文目录索引 工程架构example9工程设计Vivado设计Vitis设计example9工程验证1、读取FLASH ID2、擦除整个FLASH3、Blank-Check4、烧写Golden区位流5、读取FLASH内容6、烧写MultiBoot区位流(升级位流)7、MultiBoot区位流(升级位流)启动example10工程设计Vivado设计Vitis设计exam…

AIGC引领金融大模型革命:未来已来

文章目录 金融大模型的应用场景1. **金融风险管理**2. **量化交易**3. **个性化投资建议**4. **金融欺诈检测和预防**5. **智能客户服务** 金融大模型开发面临的挑战应对策略《金融大模型开发基础与实践》亮点内容简介作者简介获取方式 在AIGC&#xff08;Artificial Intellige…

设计模式学习[10]---迪米特法则+外观模式

文章目录 前言1. 迪米特法则2. 外观模式2.1 原理阐述2.2 举例说明 总结 前言 之前有写到过 依赖倒置原则&#xff0c;这篇博客中涉及到的迪米特法则和外观模式更像是这个依赖倒置原则的一个拓展。 设计模式的原则嘛&#xff0c;总归还是高内聚低耦合&#xff0c;下面就来阐述…

BWO-CNN-BiGRU-Attention白鲸优化算法优化卷积神经网络结合双向门控循环单元时间序列预测,含优化前后对比

BWO-CNN-BiGRU-Attention白鲸优化算法优化卷积神经网络结合双向门控循环单元时间序列预测&#xff0c;含优化前后对比 目录 BWO-CNN-BiGRU-Attention白鲸优化算法优化卷积神经网络结合双向门控循环单元时间序列预测&#xff0c;含优化前后对比预测效果基本介绍模型描述程序设计…

【Linux】死锁、读写锁、自旋锁

文章目录 1. 死锁1.1 概念1.2 死锁形成的四个必要条件1.3 避免死锁 2. 读者写者问题与读写锁2.1 读者写者问题2.2 读写锁的使用2.3 读写策略 3. 自旋锁3.1 概念3.2 原理3.3 自旋锁的使用3.4 优点与缺点 1. 死锁 1.1 概念 死锁是指在⼀组进程中的各个进程均占有不会释放的资源…

Vue3之弹窗

文章目录 第一步、引入JS第二步、弹框 在前端开发语言Vue3&#xff0c;在管理端如何进行弹窗&#xff1f;下面根据API实现效果。 Element API文档&#xff1a; Element-plus文档 搭建环境可参考博客【 初探Vue3环境搭建与nvm使用】 第一步、引入JS <script lang"ts&…

2、Three.js初步认识场景Scene、相机Camera、渲染器Renderer三要素

三要素之间关系&#xff1a; 有了虚拟场景Scene&#xff0c;相机录像Camera&#xff0c;在相机小屏幕上看到的Renderer Scene当前空间 Mesh人在场景 Camera相机录像 Renderer显示器上 首先先描述下Scene&#xff1a; 这个场景为三要素之一&#xff0c;一切需要展示的东西都需…

cin/cout的性能优化和缓冲区同步问题

目录 背景导入 问题 1.1ios::sync_with_stdio(false) 1.2为什么要解除C/C IO流同步? 1.3使用场景 2.1cin和cout的绑定关系 2.2为什么要解除绑定关系? 2.3注意事项 背景导入 大家可以先看一下这段背景知识;后面我会谈谈自己的理解; 1.在C中&#xff0c;标准输⼊输出流…

node.js基础学习-url模块-url地址处理(二)

前言 前面我们创建了一个HTTP服务器&#xff0c;如果只是简单的http://localhost:3000/about这种链接我们是可以处理的&#xff0c;但是实际运用中一般链接都会带参数&#xff0c;这样的话如果我们只是简单的判断链接来分配数据&#xff0c;就会报404找不到链接。为了解决这个问…

基于springboot中小型制造企业质量管理系统源码和论文

信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古以来的…

服务器密码错误被锁定怎么解决?

当服务器密码错误多次导致账号被锁定时&#xff0c;解决方法需要根据服务器的操作系统&#xff08;如 Linux 或 Windows &#xff09;和具体服务器环境来处理。以下是常见的解决办法&#xff1a; 一、Linux 服务器被锁定的解决方法 1. 使用其他用户账号登录 如果有其他未被…

Java基础——(四)继承

1. 类、超类和子类 在Java中&#xff0c;通过关键字extends表示继承。extends表明正在构造的新类派生与一个已存在的类&#xff0c;已存在的类称为超类&#xff08;superclass&#xff09;、基类&#xff08;base class&#xff09;或父类&#xff08;parent class&#xff09…

Python语法基础(四)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 高阶函数之map 高阶函数就是说&#xff0c;A函数作为B函数的参数&#xff0c;B函数就是高阶函数 map&#xff1a;映射 map(func,iterable) 这个是map的基本语法&#xff0c;…

《datawhale2411组队学习 模型压缩技术7:NNI剪枝》

文章目录 一、NNI简介二、 NNI剪枝快速入门2.1 加载并训练模型2.2 模型剪枝2.3 模型加速&#xff08;剪枝永久化&#xff09;2.4 微调压缩模型2.5 Slim Pruner测试 三、 使用NNI3.0进行Bert压缩&#xff08;剪枝、蒸馏)3.1 数据预处理3.2 训练模型3.3 设置模型蒸馏函数3.4 修剪…