【Hello Linux】命令行解释器

news2024/11/17 12:42:36

作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:使用进程的基础知识和进程控制知识做出一个简单的shell程序

命令行解释器

  • 介绍
  • 搭架子
    • 缓冲区
  • 获取命令
    • 如何从标准输入中获取字符串
    • 解析命令
    • strtok分隔字符串
  • 进程替换
  • 内建命令
    • 工作目录
    • chdir函数
  • 全部代码

介绍

我们从之前的学习可以知道 实际上我们的命令行解释器就是一个进程 bash
我们在执行例如 ls ll这些命令的时候实际上就是 bash 在创建子进程 让子进程执行这些命令 那么我们能不能模仿 bash 做一个简单的命令行解释器呢?
答案显然是可以的
本篇博客会使用到进程相关概念以及进程控制相关知识 如果没有前置知识的储备建议先看我之前写的两篇博客
进程相关概念
进程控制相关

搭架子

我们首先先观察我们自己使用的命令行是什么格式的

在这里插入图片描述
我们发现 这里的格式就是 用户名@主机名+地址

我们可以直接复制到我们的程序中使用

int main()    
{    
  while(1)    
  {    
    printf("[root@VM-8-3-centos lesson11]# ");    
    sleep(1);    
  }    
  return 0;    
}      

我们之后运行这个程序

在这里插入图片描述
我们发现 这个程序竟然没有任何输出的内容

这是为什么呢?

缓冲区

我们这里再次介绍下缓冲区的概念

为了提高效率 我们在打印数据的时候并不是直接打印到输出设备当中而是会先把数据存放到一个叫做输出缓冲区的地方 直到我们主动去刷新这个输出缓冲区里面的数据才会打印到输出设备
输入同理

我们平时打印语句的时候一般会在末尾加上一个 \n 换行符 实际上它就具有刷新缓冲区的功能

此外我们还可以使用fflush(stdout) 去主动刷新缓冲区

至于stdout是什么 我们在后面的基础IO部分会着重讲解

加上刷新缓冲区代码之后我们的代码如下

int main()
  {
    while(1)
    {
      printf("[root@VM-8-3-centos lesson11]# ");
      fflush(stdout);
      sleep(10);                                                                           
    }
    return 0;
 }

之后我们再次编译运行便会出现这样的情况

在这里插入图片描述
我们现在已经可以基本模拟bash的框架了

当然为了区分我们自己的minishell和bash 我们将最前面的用户改为shy

获取命令

搭好架子之后我们就可以开始模仿bash的行为了

bash的本质是一个命令行解释器

所以说我们必须要先获命令是什么

我们可以使用字符数组来获取我们输入的命令

我们定义一个字符数组 它的默认大小为128

#define NUM = 128
char command[128];

之后我们每次输入命令之后要清空字符串 这里有一种十分简便的方式

command[0] = 0;

因为这个字符数组的第一位被设置为了0 所以说这个字符串就是为空的

如何从标准输入中获取字符串

我们可以使用c语言中的scanf来获取我们输入的字符串吗

答案显然是不行的

因为我们输入的命令是这样子的格式

ls -a -l

我们可以发现 中间使用了空格号分割 但是我们的scanf遇到空格就是停止读取

这样子我们就只能使用另外的函数

这里我们使用fgets这个函数

在这里插入图片描述
这个函数具有三个参数 我们下面分别介绍下这三个参数

char* str 是我们要读取数据到哪里

int num 是我们要读取数据的个数

FILE* stream 是我们要从哪个文件读取数据

所以我们可以这么写

  fgets(command , NUM , stdin);

它的意思是从标准输入流中读取数据 读取NUM个数据到command中

我们实验下到目前为止的代码

  8  #define NUM 120
  9 
 10 
 11 int main()
 12 {
 13   char command[NUM];
 14   while(1)
 15   {
 16     command[0] = 0;                                                                      
 17     printf("[shy@VM-8-3-centos lesson11]# ");
 18     fgets(command, NUM , stdin);
 19     printf("echo: %s",command);
 20     fflush(stdout);
 21     sleep(10);
 22   }
 23   return 0;
 24 }

我们编译运行下试试看

在这里插入图片描述
我们输入在缓冲区的数据确实是被打印到屏幕上了

可是为什么这里多打了一个换行呢?(/n)

这里其实是因为我们敲回车的时候往缓冲区多输入了一个/n

要解决这个办法也很简单 我们找到这个/n字符的所在位置 将它置0就好了

在这里插入图片描述

它在缓冲区中应该是这个样子的

我们只要将字符串有效字符后一位置0就可以了

在这里插入图片描述
我们使用strlen测出长度之后减去一便是\n的下标

将它置0就可以

代码表示如下

  command[strlen(str)-1];

在这里插入图片描述
这样就能完美获取标准输入中的字符串了

解析命令

在这里插入图片描述

我们输入的命令是这样子的格式

要将这一串字符串解析成一个个的命令我们必须要分隔字符串

在c语言中 我们使用strtok命令来分隔字符串

strtok分隔字符串

在这里插入图片描述

它的返回值会返回我们分隔字符串的第一个字符位置

它有两个参数

第一个参数 char * str 表示要分割的字符串

第二个参数 const char *delim 表示分隔符

如果我们想要一直获取同一个字符串 则第一个参数在后面的时候都要传入空指针

我们可以写一段代码验证下这个函数的使用

#include <stdio.h>
#include <string.h>

int main()
{
    char str[] = "Hello, World! Welcome to C programming.";
    const char delim[] = " ,.!"; // 以空格、逗号和句号作为分隔符
    char *token;

    // 获取第一个子字符串
    token = strtok(str, delim);

    // 依次获取剩下的子字符串
    while (token != NULL)
    {
        printf("%s\n", token);
        token = strtok(NULL, delim);
    }

    return 0;
}

它运行的结果如下

在这里插入图片描述
我们使用strtok去获取我们的指令到一个字符数组中

   24     int i = 1;
   25     char* delim = " ";
   26     argv[0] = strtok(command , delim);
   27     while(argv[i] = strtok(NULL , delim))
   28     {
   29       i++;
   30     } 

进程替换

我们这里要想清楚一点 绝对不能使用父进程去进行进程替换

因为如果我们使用父进程进行进程替换的话父进程的代码和数据就会全部被覆盖

这样子的话命令行就成一次性工具了

所以说我们的父进程需要创建一个子进程去做这个任务

父进程在此时等待子进程做完任务回收资源

代码表示如下

   32     if (fork() == 0)                                                              
   33     {                                                                             
   34       // child                                                                    
   35       execvp(aegv[0] , argv);                                                     
   36       exit(1);                                                                    
   37     }                                                                             
   38 
   39     waitpid(-1 , NULL , 0); 

此时我们的程序就基本完成了

我们运行下试试效果

在这里插入图片描述
我们发现它基本上可以模仿bash进程的各种行为了

中途有一次我敲击了 ll 命令系统没有反应是因为我们没有定义这个命令

内建命令

内建命令 就是由 Bash 自身提供的命令

但是我们目前的代码仍然会遇到一些问题

我们敲出下面的命令

在这里插入图片描述
我们发现我们使用cd … 命令之后竟然没有回到上一级目录 这是为什么呢

工作目录

实际上我们的进程中有一个叫做工作目录的东西

我们可以在 /proc/进程号中看到
在这里插入图片描述
该路径也叫做进程的当前路径

我们使用cd …命令的时候实际上就是对于这个路径在进行操作

回到我们之前的问题 为什么我们使用cd命令的时候系统不能识别呢?

因为我们是通过子进程去调用的cd命令 子进程有着自己的进程目录

它的修改经过写时拷贝不会影响父进程

所以说我们如果是遇到内建命令的话就需要父进程自己去执行之

那么我们如何改变父进程的工作目录呢?

chdir函数

我们可以通过chdir函数来改变 当前进程的工作目录

它的使用方式如下

在这里插入图片描述
我们直接在它后面输入我们要改变的路径就可以

知道如何修改之后我们继续编写代码

   32     // 内建命令
   33     if (strcmp(argv[0], "cd") == 0)
   34        {
   35          if (argv[1] != NULL)
   36          {
   37            chdir(argv[1]);
   38          }
   39 
   40          continue; // 不执行下面了                                                    
   41        }

在这里插入图片描述

这样子我们的所有代码就完成了

全部代码

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <unistd.h>
    4 #include <sys/wait.h>
    5 #include <sys/types.h>
    6 #include <string.h>
    7 
    8 #define NUM 120
    9 #define CMD_NUM 40
   10 
   11 int main()
   12 {
   13   char command[NUM];
   14   char* argv[CMD_NUM];
   15   while(1)
   16   {
   17     command[0] = 0;
   18     printf("[shy@VM-8-3-centos lesson11]# ");
   19     fflush(stdout);
   20     fgets(command, NUM , stdin);
   21     command[strlen(command)-1] = 0;
   22     //  printf("echo: %s",command);
   23     // ½âÎöÃüÁî
   24     int i = 1;
W> 25     char* delim = " ";
   26     argv[0] = strtok(command , delim);
W> 27     while(argv[i] = strtok(NULL , delim))
   28     {
   29       i++;
   30     }
   31 
   32     // ÄÚ½¨ÃüÁî
   33     if (strcmp(argv[0], "cd") == 0)
   34        {
   35          if (argv[1] != NULL)
   36          {
   37            chdir(argv[1]);
   38          }
   39 
   40          continue; // ÏÂÃæ²»Ö´ÐÐÁË                                                                                                                                                                                                                      
   41        }
   42 
   43     if (fork() == 0)
   44     {
   45       // child 
   46       execvp(argv[0] , argv);
   47       exit(1);
   48     }
   49 
   50     waitpid(-1 , NULL , 0);
   51   }
   52   return 0;
   53 }

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

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

相关文章

Python3 爬虫实战教程 ,网页审查元素【Python学习连续,请关注】

在讲解爬虫内容之前&#xff0c;我们需要先学习一项写爬虫的必备技能&#xff1a;审查元素&#xff08;如果已掌握&#xff0c;可跳过此部分内容&#xff09;。1、审查元素在浏览器的地址栏输入URL地址&#xff0c;在网页处右键单击&#xff0c;找到检查。(不同浏览器的叫法不同…

爬虫(三)selenium

文章目录1. Selenium 安装2. Selenium 基本功能2.1 初始化浏览器2.2 其他功能3. 查找元素3.1 八大定位方法3.2 查找相对元素3.3 键盘事件4. 元素方法5. JS执行运行环境&#xff1a; selenium4.7.2 1. Selenium 安装 Selenium是一个用于Web应用程序测试的工具。Selenium测试直接…

成都欢蓬电商:抖音带话题春日好物节活动规则

抖音带话题“春日好物节”&#xff0c;投稿瓜分优质内容激励&#xff0c;快来投稿参与本次抖音活动&#xff01; 一、活动玩法 活动时间&#xff1a;3月3日-3月16日 活动形式&#xff1a; 玩法说明&#xff1a; 若同一id同时参加获奖&#xff0c;则不重复激励; 因视频投流涉…

易基因:RRBS揭示晚年锻炼可以减缓骨骼肌表观遗传衰老(甲基化年龄)|新研究

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。2021年12月21日&#xff0c;美国阿肯色大学、德克萨斯大学和肯塔基大学的研究人员合作在《Aging Cell》杂志发表了题为“Late-life exercise mitigates skeletal muscle epigenetic aging”…

还是要学好数学啊

有一个无穷大的二维网格图&#xff0c;一开始所有格子都未染色。给你一个正整数 n &#xff0c;表示你需要执行以下步骤 n 分钟&#xff1a;第一分钟&#xff0c;将任一格子染成蓝色。之后的每一分钟&#xff0c;将与蓝色格子相邻的 所有 未染色格子染成蓝色。下图分别是 1、2、…

每日一问-ChapGPT-20230308-关于技术与思考的问题

文章目录每日一问-ChapGPT系列起因每日一问-ChapGPT-20230308-关于技术与思考的问题matplotlib_venn 中 venn2函数调用时&#xff0c;subsets传入A list (or a tuple) containing two set objects&#xff0c;怎么理解plt.pie() 包含哪些参数&#xff0c;以及每个参数的意义mat…

云端地球2月更新了这些功能,你都用过了吗?

时光飞逝、转眼已到2023年的第三个月&#xff0c;武汉的天气也逐渐转好&#xff0c;温度步步高升。云端地球产研团队的脚步也越走越快&#xff0c;虽然春节仿佛还是昨天的事&#xff0c;但云端地球已经完成了四次迭代&#xff0c;为广大建模爱好者带来了更多实用功能&#xff0…

BOSHIDA 模块电源的安装与维护

BOSHIDA三河博电科技 模块电源的安装与维护 由于各生产的模块电源的类别、系列、规格品种难以数计&#xff0c;故其功能特性和物理特性不尽相同&#xff0c;因此在安装、使用与维护方面亦各有不同&#xff0c;但应在以下几方面引起注意。 &#xff08;1&#xff09;打开包装后…

【Redis】搭建哨兵集群

目录 集群结构 准备实例和配置 启动 测试 集群结构 这里我们搭建一个三节点形成的Sentinel集群&#xff0c;来监管之前的Redis主从集群。如图&#xff1a; 三个sentinel实例信息如下&#xff1a; 节点IPPORTs1192.168.150.10127001s2192.168.150.10127002s3192.168.150.…

10 卷积神经网络及python实现

1 卷积神经网络简介 卷积神经网络(Convolutional Neural Network, CNN)由LeCun在上世纪90年代提出。 LeCun Y., Bottou L., Bengio Y., and Haffner P., Gradient-based learning applied to document recognition, Proceedings of the IEEE, pp. 1-7, 1998. 卷积核和特征图&…

STM32定时器Timer(PWM呼吸灯)

目录 定时器介绍 定时器工作原理&#xff1a; 定时器分类&#xff1a; STM32F103C8T6定时器资源&#xff1a; ​编辑 通用定时器介绍&#xff1a; 定时器计数模式&#xff1a; 定时器时钟源&#xff1a; ​编辑 定时器溢出时间计算公式&#xff1a; 定时器中断实验…

「IT女神勋章」挑战赛#

缓存 本地缓存 本地缓存为了保证线程安全问题&#xff0c;一般使用ConcurrentMap的方式保存在内存之中 分布式缓存。 常见的分布式缓存则有Redis&#xff0c;MongoDB等。 一致性&#xff1a;本地缓存由于数据存储于内存之中&#xff0c;每个实例都有自己的副本&#xff0c…

完全解读低通滤波,并且用其C语言实现

1、什么是低通滤波 低通滤波是一种信号处理技术&#xff0c;它可以用于去除高频信号成分&#xff0c;只保留低频信号成分。低通滤波器的本质是一个线性时不变系统&#xff0c;它可以通过差分方程或者频域响应的形式来描述。 在差分方程的形式下&#xff0c;低通滤波器可以表示…

MySQL数据库引擎(MyIsAm和InnoDB)

一、数据库引擎 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的&#xff0c;而不是基于库的&#xff0c;所以存储引擎也可被称为表类型。MySQL5.5版本之后默认InnoDB&#xff0c;之前是MyIsAm。 PS&#xff1a; 设置引擎语句&#xff1…

计算机网络:RIP协议以及距离向量算法

RIP协议 RIP是一种分布式的基于适量向量的路由选择协议&#xff0c;最大优点是简单。要求网络中的每一个路由器都要维护从它自己到其他每一个目的网络的唯一最佳&#xff08;最短&#xff09;距离记录&#xff0c;最多包含15个路由器&#xff0c;距离为16就表示网络不可达&…

每天一道大厂SQL题【Day15】微众银行真题实战(五)

每天一道大厂SQL题【Day15】微众银行真题实战(五) 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&…

DBT 收购 Transform,指标平台已成现代数据栈关键拼图

今年 2 月初&#xff0c;现代数据技术栈独角兽 DBT 宣布完成对 Transform 的并购。在现代数据栈的体系中&#xff0c;DBT 和 Transform 都扮演着重要角色&#xff0c;DBT 侧重于整个分析链路上的数据转换处理&#xff0c;而 Transform 则聚焦在以指标为中心搭建业务分析应用。 …

【java】Java 封装

文章目录Java 封装封装的优点实现Java封装的步骤实例Java 封装 在面向对象程式设计方法中&#xff0c;封装&#xff08;英语&#xff1a;Encapsulation&#xff09;是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。 封装可以被认为是一个保护屏障&#xff0c;防…

14个Python处理Excel的常用操作分享

自从学了Python后就逼迫用Python来处理Excel&#xff0c;所有操作用Python实现。目的是巩固Python&#xff0c;与增强数据处理能力。本文为大家整理了14个Python处理Excel的常用操作&#xff0c;非常好用&#xff0c;希望对大家有所帮助目录自从学了Python后就逼迫用Python来处…

[AI助力] 2023.3.9 考研英语学习 2013 英语二翻译

[AI助力] 2023.3.9 考研英语学习 2013 英语二翻译 文章目录[AI助力] 2023.3.9 考研英语学习 2013 英语二翻译2013 英语二 翻译真题总结Powered with AI用perfect prompt 生成 prompt然后让它们评价&#xff0c;翻译&#xff0c;并改进New Bing 的回答让它改进~ &#xff08;太惊…