[Linux] Linux 模拟实现 Shell

embedded/2024/10/18 19:28:45/

标题:[Linux] Linux 模拟实现 Shell

个人主页@水墨不写bug(图片来源于网络)

目录

一、什么是shell

二、shell的理解

三、模拟实现shell

1)打印命令行提示

2)获取用户的输入字符串

3)分割命令字符串

4)检查是否是内建命令

1. 什么是内建命令

常见的内建命令

5)进程程序替换


 正文开始:

一、什么是shell

         Shell是用C语言编写的一个独立的可执行程序,它是用户与Linux或其他类Unix系统(如Mac OS)交流的桥梁,既是命令语言又是程序设计语言。

        定义:Shell是Linux内核的一个外层保护工具,并负责完成用户与内核之间的交互。

        功能:Shell是一个命令行解释器,它将用户输入的命令解析为操作系统所能理解的指令,实现用户与操作系统的交互。同时,Shell也提供了一种脚本编写功能,允许用户将一系列命令按照特定顺序排列,形成脚本文件,由Shell解释器逐行执行,以完成特定任务或实现一系列操作。

         Shell提供了丰富的内置命令和外部命令,如cd用于切换目录,ls用于列出目录内容,grep用于搜索文本等。这些命令可以组合使用,形成复杂的脚本,以实现各种功能。

 

二、shell的理解

         站在shell的角度,一个可执行程序,运行起来成为进程,它读取的一段指令其实一串字符串,操作系统无法理解这串字符串的具体意义,无法与用户交流,操作系统也就无法为用户服务。shell是外壳程序,就像一个包在操作系统外面的一层壳子,shell可以把用户输入的一串字符“翻译”

 给操作系统,把字符串转化为操作系统可以“听懂的”语言,这样操作系统就能听懂用户的需求,可以进一步为用户服务了。

        所以,根据这一理解,我们可以先设计一个框架,表明我们实现的shell的大致思路:

        1)打印命令行提示

        2)获取用户的输入字符串

        3)分割命令字符串,并存储到全局的字符串数组:myg_argv

        4)检查是否是内建命令,内建命令需要父进程自己执行

        5)进程程序替换

       

如果不知道什么是进程程序替换,可以浏览这一篇:《[Linux]进程程序替换》

        这也就表明了shell的运行机制,:

        假如shell读取到“ls”,shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

三、模拟实现shell

         由于我们当前实现的shell比较简单,目前没有分离编译最后链接,简单点说就是没有把声明和实现分离为源文件和头文件。而是简单的把一个一个的步骤封装为一个一个的函数。

1)打印命令行提示

         我们在与Linux的bash(shell的一种)命令行进行交互的时候,会发现每一次输入命令的前面,都会有一个命令行提示:

这个命令行提示包含的信息有:

用户名+主机名+当前目录 

        幸运的是,这些信息我们可以直接从环境变量中获取。我们可以通过<stdlib.h>中的getenv()接口获得环境变量,获取了这些环境变量之后就好办了,只需要用printf(标准格式化输出)即可实现打印命令行提示。具体实现如下:


const char *get_username()
{const char *name = getenv("USER");if (name == NULL){return "NONE";}return name;
}const char *get_hostname()
{const char *hostname = getenv("HOSTNAME");if(hostname == NULL){return "NONE";}return hostname;
}const char *get_cwd()
{const char *cwd = getenv("PWD");if (cwd == NULL){return "NONE";}return cwd;
}
void output_sng_line()
{// 直接打印输出到stdoutconst char *cwd = get_cwd();if (strlen(cwd) != 1)//特殊处理根目录“/”的情况{printf("%s@%s:~%s$ ", get_username(), get_hostname(), cwd);}else{printf("%s@%s:%s$ ", get_username(), get_hostname(), cwd);}fflush(stdout); // 刷新缓冲区
}

总结:

                通过库函数getenv获得环境变量,printf格式化输出即可;

                输出的格式可以对照你的本地的命令行提示符。

        上面的实例是按照我的阿里云服务器的命令行提示符来设计的,这是我的命令行提示符:

(包括普通目录和 特殊处理的根目录)

       

        注意:获取的环境变量PATH是一串完整的路径,与上图的路径不同,这就需要我们对路径进行截取,由于通过函数进行修改需要传递二级指针,比较麻烦,所以我们可以考虑使用宏替换函数实现:


// 这样写do{}while(0):形成代码块;while(0)后面可随便带分号
#define Re_back(p)          \do                      \{                       \p += strlen(p) - 1; \while (*p != '/')   \--p;            \} while (0)

修改后的函数具体实现:


void output_sng_line()
{// 直接打印输出到stdoutconst char *cwd = get_cwd();// 这个宏后面可以随便加分号,看起来更像一个函数Re_back(cwd);if (strlen(cwd) != 1){printf("%s@%s:~%s$ ", get_username(), get_hostname(), cwd);}else{printf("%s@%s:%s$ ", get_username(), get_hostname(), cwd);}fflush(stdout); // 刷新缓冲区
}


 

2)获取用户的输入字符串

         我们需要再设计一个缓冲区,用来存储读取的命令行信息——usercommand,SIZE的大小可以自己设计,可以大一些,这样允许一次输入比较长的指令。

        返回值表示获取命令是否成功,如果失败返回-1——如果失败,就没有运行的意义了,所以退出即可。

main函数接口调用:

获取用户的输入字符串 函数具体实现:

     一个小细节,在读取的时候,由于我们最后输入指令的时候,会附带一个换行符,表示输入命令结束,这个换行符也会被读入,于是需要将缓冲区的 '\0'的位置提前一个位置。


int get_user_command(char line[], int n)
{char *s = fgets(line, n, stdin);if (s == NULL){printf("err\n");return -1;}// 处理字符串结尾的\nline[strlen(line) - 1] = ZERO;return 0;// printf("%s\n",line);
}


 

3)分割命令字符串

main函数接口调用:

         分割命令字符串具体实现:(复用库函数strtok(),字符串分割函数)


void partation_command(char *command, int n)
{(void)n; // 暂时禁用n,防止编译老是提醒myg_argv[0] = strtok(command, SKP);int index = 1;// 有意的设计 “=” :先赋值再判断;当srtok无法分割时,返回NULL,正好让g_argc最后一个元素为NULLwhile ((myg_argv[index++] = strtok(NULL, SKP))){}
}

 


4)检查是否是内建命令

1. 什么是内建命令

        在Linux操作系统中,内建命令(builtin commands)是指那些直接由shell(如Bash、Zsh等)自身实现和提供的命令,而不是作为独立的可执行文件存在于文件系统中的命令。内建命令通常比外部命令(external commands)执行得更快,因为它们不需要启动一个新的进程来执行。 

常见的内建命令

以下是一些常见的Bash内建命令:

  • cd:更改当前工作目录。
  • echo:在标准输出上显示一行文本。
  • exec:用指定的命令替换当前的shell进程。

         等等....

        其实,内建命令之所以必须是内建命令,一定有一定的原因:

        以cd为例子,执行cd,如果父进程创建一个子进程让他帮自己执行,那么子进程的目录确实是切换了,但是父进程的目录还是没有变化啊!所以cd之所以必须是内建命令,是因为必须让父进程自身执行cd命令。让父进程自身切换工作路径,需要系统调用接口:

chdir() 

main函数接口调用:

        返回值表示:

        1——是内建命令,父进程需要自己完成命令,则不需要执行第五步操作;

        其他——不是内建命令,父进程可以通过创建子进程来让子进程替自己完成命令。 

 检查进程是否是内建命令其实是很简单朴素的,就是一个一个的条件判断,具体函数实现如下:

(这里也给出了CD内建命令的实现方式)


void _CD()
{char *newpath = myg_argv[1];// 说明只有一个cd,默认切换到家目录if (newpath == NULL){newpath = (char *)get_home();}chdir(newpath);// 这个时候,环境变量没有被改变,使用cd的时候,虽然可以切换路径,但是getpath打印出来的不变// path环境变量需要shell自己维护char tem[SIZE];getcwd(tem, sizeof(tem));snprintf(cwd, sizeof(cwd), "PWD=%s", tem);putenv(cwd);
}int check_inbuildcommand_execute()
// 内建命令需要父进程执行,如果子进程执行cd,则子进程目录切换,父进程没切换
{int yes = 0;// myg_argv[0]一定是命令char *inb_cmd = myg_argv[0];// 检查是否是cd命令,if (strcmp(inb_cmd, "cd") == 0){yes = 1;_CD();}else if (strcmp(inb_cmd, "echo") == 0 && strcmp(myg_argv[1], "$?") == 0){yes = 1;printf("%d\n", lastcode);}return yes;
}

 


 

5)进程程序替换

 main函数接口调用:

         父进程创建一个子进程帮自己干活,父进程只需要坐在一旁等着就行了:


void execute_command()
{pid_t id = fork();if (id == 0) // 子进程给父进程做事{int ret = execvp(myg_argv[0], myg_argv);if (ret == -1){exit(errno);}}else // 父进程{int status = 0;pid_t rid = waitpid(id, &status, 0); // 等待子进程退出if (rid > 0) // 等待子进程成功{if (WIFEXITED(status)){lastcode = WEXITSTATUS(status);}else{printf("lastcode 不重要了,子进程被信号杀掉\n");}}else{printf("waitpid fail");exit(-1); // 父进程shell退出}}
}

        最后不要忘了回收子进程的退出信息。 


完~

未经作者同意禁止转载


http://www.ppmy.cn/embedded/127425.html

相关文章

手机控车系统是一种高科技的汽车智能控制系统?

手机控车系统概述 系统概述 移动管家手机控车系统集成了汽车安防、智能化控制及专业配置产品&#xff0c;采用了先进的生产检测设备和质控体系&#xff0c;确保产品质量。该系统支持手机远程控车、远程报警、卫星定位、无匙进入、一键启动、自动升窗等全面功能&#xff0c;为用…

PWN二进制安全修仙秘籍【第二章#二进制文件篇02】ELF文件详解

什么是ELF文件&#xff1f; 英文全称就是Executable Linkable Format&#xff0c;即可执行可链接格式&#xff0c;Linux系统上所运行的就是ELF格式的文件&#xff0c;相关定义在“/usr/include/elf.h”文件里。 1. 编写示例代码 这里我们编写下面的示例代码&#xff0c;用来编…

提升正则表达式性能:全面解析Golang regexp/syntax包

提升正则表达式性能&#xff1a;全面解析Golang regexp/syntax包 介绍基本概念正则表达式简介regexp/syntax包的作用 regexp/syntax包的结构核心组件结构详解ParserRegexpOpInstProg 使用Parser解析正则表达式解析正则表达式AST的结构 分析解析后的正则表达式树AST节点类型分析…

Python轴承故障诊断 (八)基于EMD-CNN-GRU并行模型的故障分类

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Pytorch-LSTM轴承故障一维信号分类(一)-CSDN博客 Pytorch-CNN轴承故障一维信号分类(二)-CSDN博客 Pytorch-Transformer轴承故障一维信号分类(三)-CSDN博客 三十多个开源…

树莓派应用--AI项目实战篇来啦-9.OpenCV实现汽车检测

1.介绍 该项目使用的汽车检测使用的也是 haar 模型。这是一种基于机器学习的汽车检测算法。它使用了 Haar 特征来检测汽车&#xff0c;可以在图像中快速检测到汽车并输出其位置。采用该方法检测速度较快&#xff0c;但准确率略低。 2.OpenCV 实现汽车检测 可以采用官方自带的汽…

欧科云链研究院深掘链上数据:洞察未来Web3的隐秘价值

目前链上数据正处于迈向下一个爆发的重要时刻。 随着Web3行业发展&#xff0c;公链数量呈现爆发式的增长&#xff0c;链上积聚的财富效应&#xff0c;特别是由行业热点话题引领的链上交互行为爆发式增长带来了巨量的链上数据&#xff0c;这些数据构筑了一个行为透明但与物理世…

C/C++语言基础--C++神奇的多态

本专栏目的 更新C/C的基础语法&#xff0c;包括C的一些新特性 前言 通过前面几节课&#xff0c;我们学习了抽象、封装、继承相关的概念&#xff0c;接下来我们将讲解多态&#xff0c;多态他非常神奇&#xff0c;正式有了他&#xff0c;类才能出现多样性特征&#xff1b;C语言…

毕设开源 基于python的搜索引擎设计与实现

文章目录 0 简介1 课题简介2 系统设计实现2.1 总体设计2.2 搜索关键流程2.3 推荐算法2.4 数据流的实现 3 实现细节3.1 系统架构3.2 爬取大量网页数据3.3 中文分词3.4 相关度排序第1个排名算法&#xff1a;根据单词位置进行评分的函数第2个排名算法&#xff1a;根据单词频度进行…