[Linux]编写一个极简版的shell(版本1)

[Linux]编写一个极简版的shell-version1

文章目录

  • [Linux]编写一个极简版的shell-version1
    • 命令行提示符打印
    • 接收命令行参数
    • 将命令行参数进行解释
    • 执行用户命令
    • 完整代码

本文能够帮助Linux系统学习者通过代码的角度更好地理解命令行解释器的实现原理。

命令行提示符打印

Linux操作系统运行时,就需要shell进程进行命令行解释,然后让系统完成对应的命令,因此打印命令行提示符时要采用死循环打印的方式,具体的代码逻辑如下:

int main()
{while(1){printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));fflush(stdout);//sleep(200); -- 当前阶段用于演示效果}
  • 为了和系统的shell进行区分因此打印两个$符号
  • 命令行提示符打印的信息都是通过环境变量得来的,因此只需要调用系统接口获取相应的环境变量即可
    • USER环境变量 – 当前使用的用户名
    • HOSTNAME环境变量 – 当前的主机名称
    • PWD环境变量 – 当前用户所处绝对路径
  • 由于显示器采用的是行缓冲的策略,因此需要手动刷新缓冲区才能让命令行提示符显式到屏幕上

由于环境变量PWD是当前用户所处的绝对路径,因此需要编写一个函数getpath来获取当前所处的目录名称,具体的代码逻辑如下:

const char* getpath(char* path)
{int length = strlen(path);if(length == 1) return "/"; //根目录int i = length - 1;while((path[i] != '/')) i--;return path+i+1;
}
  • 如果绝对路径的字符串长度为1,表示所处为根目录,返回"/"
  • 其余路径下都要将绝对路径里当前目录前的所有路径分割掉,包括路径分隔符,譬如当前记录绝对路径环境变量为PWD=/home/qxm/linux-warehouse/review/mybash/version1getpath的返回值为verson1字符串的首地址

效果演示:

image-20230904145622641

接收命令行参数

接受命令行参数只需要设置一个字符串数组,将用户的输入接收即可,具体的代码逻辑如下:

#define MAX 1024int main()
{while(1){char commandstr[MAX] = { 0 }; //接收命令行参数printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));fflush(stdout);char* s = fgets(commandstr, sizeof(commandstr), stdin);commandstr[strlen(commandstr) - 1] = '\0'; //去除输入时末尾的'\n'//printf("%s\n", commandstr); -- 当前阶段用于演示效果}return 0;
}
  • 由于用户输入时会按下回车也就是会在输入时在末尾出现一个'\n',命令行参数使用时需要将其去除掉,譬如用户输入ls -a -l\n,命令行参数只需要ls -a -l

效果演示:

image-20230904151119734

将命令行参数进行解释

获取用户输入的命令行参数后,需要将命令行参数根据输入的空格将字符串分割为多个字串,譬如用户输入ls -a -l,要分割为ls, -a, -l,具体的代码逻辑如下:

#define MAX 1024
#define ARGC 64
#define SEP " "int split(char commandstr[], char* argv[])//命令行参数解释
{argv[0] = strtok(commandstr, SEP);if (argv[0] == NULL) return -1; //字符串为空int i = 1;while(1){argv[i] = strtok(NULL, SEP);if(argv[i] == NULL) break;i++;}return 0;
}void debugPrint(char* argv[])//--当前阶段用于演示效果
{int i = 0;for (i = 0; argv[i] != NULL; i++){printf("%s\n", argv[i]);}
}int main()
{while(1){char commandstr[MAX] = { 0 }; //接收命令行参数char* argv[ARGC] = { NULL }; //存储命令行参数printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));fflush(stdout);char* s = fgets(commandstr, sizeof(commandstr), stdin);commandstr[strlen(commandstr) - 1] = '\0'; //去除输入时末尾的'\n'int n = split(commandstr, argv);if(n != 0) continue; //用户输入空串//debugPrint(argv);  -- 当前阶段用于演示效果}return 0;
}
  • 调用C语言库函数strtok获取分割空格字符后字串的首地址,并且将空格置为'\0',并将字串的首地址存储起来

效果演示:

image-20230904154551480

执行用户命令

创建子进程,进程进程程序替换,让子进程完成用户所输入的命令,具体的代码逻辑如下:

#define MAX 1024
#define ARGC 64
#define SEP " "const char* getpath(char* path)
{int length = strlen(path);if(length == 1) return "/"; //根目录int i = length - 1;while((path[i] != '/')) i--;return path+i+1;
}int split(char commandstr[], char* argv[])
{argv[0] = strtok(commandstr, SEP);if (argv[0] == NULL) return -1; //字符串为空int i = 1;while(1){argv[i] = strtok(NULL, SEP);if(argv[i] == NULL) break;i++;}return 0;
}int main()
{while(1){char commandstr[MAX] = { 0 }; //接收命令行参数char* argv[ARGC] = { NULL };  //存储命令行参数printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));fflush(stdout);char* s = fgets(commandstr, sizeof(commandstr), stdin);commandstr[strlen(commandstr) - 1] = '\0'; //去除输入时末尾的'\n'int n = split(commandstr, argv);if(n != 0) continue; //用户输入空串pid_t id = fork();//创建子进程完成命令执行if (id == 0){//子进程execvp(argv[0], argv); //进程程序替换exit(0);}int status = 0;waitpid(id, &status, 0);//回收子进程}return 0;
}
  • 由于实现的shell用于执行系统命令,并且获取了记录命令行参数的数组,因此采用execvp进程程序替换

效果演示:

shell-1演示

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define MAX 1024
#define ARGC 64
#define SEP " "const char* getpath(char* path)
{int length = strlen(path);if(length == 1) return "/"; //根目录int i = length - 1;while((path[i] != '/')) i--;return path+i+1;
}int split(char commandstr[], char* argv[])
{assert(commandstr);assert(argv);argv[0] = strtok(commandstr, SEP);if (argv[0] == NULL) return -1; //字符串为空int i = 1;while(1){argv[i] = strtok(NULL, SEP);if(argv[i] == NULL) break;i++;}return 0;
}int main()
{while(1){char commandstr[MAX] = { 0 }; //接收命令行参数char* argv[ARGC] = { NULL };  //存储命令行参数printf("[%s@%s %s]$$ ", getenv("USER"), getenv("HOSTNAME"), getpath(getenv("PWD")));fflush(stdout);char* s = fgets(commandstr, sizeof(commandstr), stdin);assert(s); //对fgets函数的结果断言(void)s;//保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用commandstr[strlen(commandstr) - 1] = '\0'; //去除输入时末尾的'\n'int n = split(commandstr, argv);if(n != 0) continue; //用户输入空串pid_t id = fork();assert(id >= 0);(void)id;if (id == 0){//子进程execvp(argv[0], argv); exit(0);} int status = 0;waitpid(id, &status, 0);}return 0;
}
);if(n != 0) continue; //用户输入空串pid_t id = fork();assert(id >= 0);(void)id;if (id == 0){//子进程execvp(argv[0], argv); exit(0);} int status = 0;waitpid(id, &status, 0);}return 0;
}

说明: 该版本shell只能够执行系统命令,并且不能够支持所有系统命令比如cd命令。

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

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

相关文章

常用命令之mysql命令之show命令

一、mysql show命令简介 mysql数据库中show命令是一个非常实用的命令&#xff0c;SHOW命令用于显示MySQL数据库中的信息。它可以用于显示数据库、表、列、索引和用户等各种对象的信息。我们常用的有show databases&#xff0c;show tables&#xff0c;show full processlist等&…

SpringMVC常用注解、参数传递及页面跳转

一.SpringMVC常用注解 1.1.RequestMapping RequestMapping注解是一个用来处理请求地址映射的注解&#xff0c;可用于映射一个请求或一个方法&#xff0c;可以用在类或方法上。 标注在方法上运行代码 用于方法上&#xff0c;表示在类的父路径下追加方法上注解中的地址将会访…

无涯教程-JavaScript - NORMDIST函数

NORMDIST函数替代Excel 2010中的NORM.DIST函数。 描述 该函数返回指定均值和标准差的正态分布。此功能在统计中有非常广泛的应用,包括假设检验。 语法 NORMDIST(x,mean,standard_dev,cumulative)争论 Argument描述Required/OptionalXThe value for which you want the dis…

Redis——认识Redis

简单介绍 Redis诞生于2009年&#xff0c;全称是Remote Dictionary Server&#xff0c;远程词典服务器&#xff0c;是一个基于内存的键值型NoSQL数据库。 特征 键值&#xff08;Key-value&#xff09;型&#xff0c;value支持多种不同数据结构&#xff0c;功能丰富单线程&…

《C++ Primer》第2章 变量(一)

参考资料&#xff1a; 《C Primer》第5版《C Primer 习题集》第5版 2.1 基本内置类型&#xff08;P30&#xff09; C 定义的基本类型包括算术类型&#xff08;arithmetic type&#xff09;和空类型&#xff08;void&#xff09;&#xff0c;其中算术类型包括字符、整型、布尔…

Linux CentOS7 awk的反转功能

处理文本文件&#xff0c;经常会遇到反向输出的要求。 可用命令rev对待处理的文件或标准输入快速完成。 可用命令tac对文件快速完成反向查看。 而对行中字符串(单词)可借助其他命令达到反向输出的目标。 我们在文章《Linux CentOS7sed的替换及逆转功能》讨论了sed流编辑器对…

学习Bootstrap 5的第五天

目录 图像 图像形状 实例 对齐图像 实例 居中图像 实例 响应式图像 实例 Jumbotron 实例 图像 图像形状 .rounded 类可以用于为图像或任何具有边框的元素添加圆角。这个类适用于Bootstrap的所有版本&#xff0c;并且在最新版本中得到了进一步的增强。 实例 <…

CS420 课程笔记 P6 - 游戏逆向中的虚拟内存

文章目录 IntroVirtual memoryExample!Static example Intro 在上个视频中&#xff0c;我们知道有些地址在你重进游戏时就会无效&#xff0c;有的有时有效&#xff0c;我们需要了解称为虚拟内存的东西 记住这些信息&#xff1a;当你双击打开 Squally.exe 游戏时&#xff0c;系…

C# winform控件和对象双向数据绑定

实现目的&#xff1a; 控件和对象双向数据绑定 实现结果&#xff1a; 1. 对象值 -> 控件值 2. 控件值 -> 对象值 using System; using System.Windows.Forms;namespace ControlDataBind {public partial class MainForm : Form{People people new People();public Mai…

JVM的故事——虚拟机类加载机制

虚拟机类加载机制 文章目录 虚拟机类加载机制一、概述二、类加载的时机三、类加载的过程四、类加载器 一、概述 本章将要讲解class文件如何进入虚拟机以及虚拟机如何处理这些class文件。Java虚拟机把class文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#…

KMP超高效匹配算法

简介&#xff1a; KMP算法是一种改进的字符串匹配算法&#xff0c;其中&#xff0c;KMP算法的运用核心是利用匹配失败后的信息&#xff0c;最大进度的减少模式串与目标串的匹配次数以达到快速匹配的效果。算法与暴力求解的改进在于每当一趟匹配过程中出现的字符比较不相等时&am…

2023开学礼中国海洋大学《乡村振兴战略下传统村落文化旅游设计》许少辉新海洋图书馆

2023开学礼中国海洋大学《乡村振兴战略下传统村落文化旅游设计》许少辉新海洋图书馆

SOME/IP TTL 在各种Entry 中各是什么意思?有什么限制?

1 服务发现 SOME/IP SD 服务发现主要用于 定位服务实例检测服务实例状态是否在运行发布/订阅行为管理SOME/IP SD 也是 SOME/IP 消息,遵循 SOME/IP 消息格式,有固定的 Message ID、Request ID 以及 Message Type 等。并对 SOME/IP Payload 进行了详细的定义。 SOME/IP SD …

面试中的自我介绍:首印象决定一切

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

C语言基础之——结构体

前言&#xff1a;小伙伴们又见面啦&#xff0c;那么本篇文章&#xff0c;我们就将对C语言基础知识的最后一个章节——结构体展开讲解。 世上无难事&#xff0c;只要肯攀登&#xff01; 目录 一.什么是结构体 二.结构体讲解 1.结构体的声明和变量的定义 2.结构体成员的类型…

【Linux】多线程2——线程互斥与同步/多线程应用

文章目录 1. 线程互斥1.1 问题引入1.2 线程互斥的相关概念1.3 互斥量mutex1.4 互斥量实现原理1.5 死锁 2. 线程安全和可重入函数3. 线程同步3.1 同步概念3.2 条件变量 4. 生产消费模型4.1 基于阻塞队列的cp模型4.2 基于环形队列的cp模型POSIX信号量 5. 线程池5.1 互斥量RAII版本…

二叉树的介绍

写在前面&#xff1a; 二叉树是数据结构课程中非常重要的内容&#xff0c;我们针对二叉树的概念、性质以及类型展开详细介绍。 一、概念 二叉树&#xff08;Binary Tree&#xff09;是n&#xff08;n>0&#xff09;个结点的有限集合&#xff0c;该集合或者空集&#xff0…

英语之美:用一句话解释句子结构

以下是一个包含主语、谓语、宾语、表语、定语、同位语、补足语和状语的扩展句子&#xff0c;使用 “I love you” 作为基础&#xff1a; “I, the person who truly loves you, consider our love a beautiful gift, and I love you more deeply with each passing day.” 在…

Android开机动画

Android开机动画 1、BootLoader开机图片2、Kernel开机图片3、系统启动时&#xff08;BootAnimation&#xff09;动画3.1 bootanimation.zip位置3.2 bootanimation启动3.3 SurfaceFlinger启动bootanimation3.4 播放开机动画playAnimation3.6 开机动画退出检测3.7 简易时序图 4、…

【微服务部署】三、Jenkins+Maven插件Jib一键打包部署SpringBoot应用Docker镜像步骤详解

前面我们介绍了K8SDockerMaven插件打包部署SpringCloud微服务项目&#xff0c;在实际应用过程中&#xff0c;很多项目没有用到K8S和微服务&#xff0c;但是用到了Docker和SpringBoot&#xff0c;所以&#xff0c;我们这边介绍&#xff0c;如果使用Jenkinsjib-maven-plugin插件打…