[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,一经查实,立即删除!

相关文章

ARP欺骗原理和防护

ARP是什么&#xff1f; ARP是在局域网中根据上层协议的IP查找它的的Mac地址的网络层协议。 ARP欺骗原理 如果主机A要和主机B通信&#xff0c;它首先要检查自己的ARP缓存表&#xff0c;查看其中是否有和主机B对应的Mac地址&#xff0c;如果没有&#xff0c;则需要发送广播寻找主…

常用命令之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…

大数据课程K19——Spark的电影推荐案例推荐系统的冷启动问题

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Spark的案例——电影推荐; ⚪ 掌握Spark的模型存储; ⚪ 掌握Spark的模型加载; ⚪ 掌握Spark的推荐系统的冷启动问题; 一、案例——电影推荐 1. 基于用户的推荐 1. 说明 我们现…

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;其中算术类型包括字符、整型、布尔…

菜鸟教程《Python 3 教程》笔记(17):输入和输出

菜鸟教程《Python 3 教程》笔记&#xff08;17&#xff09; 17 输入和输出17.1 读取键盘输入17.2 读和写文件17.3 文件对象的方法17.3.1 read()、readline()、readlines() 17.3.2 tell()17.3.3 seek()17.4 pickle 模块&#xff08;没看懂&#xff09; 笔记带有个人侧重点&#…

LeetCode 面试题 03.01. 三合一

文章目录 一、题目二、C# 题解 一、题目 三合一。描述如何只用一个数组来实现三个栈。 你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标&#xff0c;value表示压入的值。 构造函数会传入一个stackSize参数&#x…

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语言中,指针和函数指针是两个不同的概念

指针&#xff1a;指针是一个变量&#xff0c;其值为另一个变量的地址&#xff0c;即&#xff0c;内存位置的直接地址。我们可以通过这个指针来访问该地址存储的值 int a 10; int *p; p &a; // p存储了变量a的地址 printf("%d", *p); // 这会打印出10…

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; 文章图文…

【网络安全带你练爬虫-100练】第21练:批量获取文件夹中文件名

目录 一、目标1&#xff1a;使用python爬取指定文件夹中的文件名 二、目标2&#xff1a;在文件夹指定目录打开命令行 一、目标1&#xff1a;使用python爬取指定文件夹中的文件名 方法一&#xff1a;使用os模块 将/path/to/folder替换为实际的文件夹路径。os.listdir()函数用…