LInux下C语言模拟实现 —— 极简版的命令行解释器

news2025/2/28 19:46:00

根据对进程的理解,我们知道然后去使用系统接口去调用程序和加载程序,因此我们可以利用接口去实现一个简易版的命令行解释器,核心思路就是获取用户输入的指令信息,然后利用指令信息去调用相关的接口,因此首先就是要如何获取用户输入的字符串

获取字符串 —— fgets

函数声明 : char * fgets (char *str,int size,FILE * stream);

头文件:#include<stdio.h>

说明:该函数用来从stream流中获取size大小的数据字符,并将其存到str中,若是读取文件流失败则会返回空指针,成功则返回str的指针。

利用该函数我们可以从缓存区中用户输入的字符串

如:

测试好没有问题后,我们开始需要将字符串进行一定的处理,去得到能让exec类接口认识的形式,因此我们需要去对字符串进行切割,根据方便,我们最终比较容易得到的是指令的名称和使用方式的数组形式,因此我们选择execvp

处理字符串 —— strtok

函数声明:char * strtok(char *str,const char *delim);

头文件:#include <string.h>

函数说明:该函数用来对字符串进行切割,第一个参数是被切割的字符串(注意,该函数会改变原字符串),第二个参数是以什么字符或字符串为切割点,返回切割后的字符串指针,若是想多次切割则下次第一个参数传NULL,多次调用该函数,函数会自动在上次切割的位置,继续往后找切割位置切,并且返回,直到找到最后一段找不到切割符时,则返回剩下的最后一段,再次调用返回NULL

我们封装一个函数,定义一个用来存放指令和指令参数的字符串数组(字符指针数组),还有需要被切割的字符串,将其交给这个函数,完成切割并将切割好的字符放到字符串数组中,若是成功则返回,失败了则返回0,失败的情况我们可以认为是输入了无效的字符指令,此时不做任何处理,直接continue即可

以上,对输入指令这一步就处理好了,接着就是创建子进程去让子进程调用系统接口,而父进程只需要等待进程结束然后回收即可

创建子进程 —— fork

函数声明:pid_t fork(void);

头文件:#include<unistd.h>

函数说明:该函数用于创建子进程,调用函数后对父进程返回子进程的pid,子进程返回0,创建失败则返回-1

进程等待 —— waitpid

函数声明:pid_t waitpid(pid_t pid, int *status, int options);

头文件:#include<sys/wait.h>

函数说明:第一个参数可以用来指定子进程的pid,第二个参数则是用来返回子进程的结束状态的,第三个参数用来选择是阻塞等待还是非阻塞轮询的方式去等待,具体可以参考上一篇博客《进程控制》,里面有较为详细的介绍到进程创建、进程结束、进程等待和进程替换。

进程替换 —— execvp

函数声明:int execvp(const char *file, char *const argv[ ])

头文件:#include<unistd.h>

函数说明:第一次参数输入指令的名称,第二个输入如何使用该指令的方法数组

以上关于进程创建、进程等待和进程替换具体可以看上一篇博客介绍,这里简单复述一下

接下来我们只需要创建子进程,然后让子进程去调用execvp即可

测试一下是否能够执行一些基本的指令,例如“ls -a -l”等等,基本功能通过测试后,再来对一些细节进行完善

ls的自动配色方案 "--color=auto"

首先是关于ls,我们发现我们自己写的命令行解释器中的ls指令没有配色,这是因为我们没有设置自动的配色方案,我们可以对ls指令单独进行一下处理,当检查到ls指令时,我们对切割好后的字符串数组后加上自动配色方案“--color=auto”

内置命令(内建命令)

基本的功能完成后,我会发现有部分指令不能成功的实现想要的效果,例如cd命令,和对环境变量进行各种操作的命令等等,这些命令不能通过子进程去执行,而是需要在父进程中直接调用系统接口去操作的

改变当前工作路径 —— chdir

函数声明:int chdir(const char *path);

头文件:#include<unistd.h>

函数说明:该函数用于改变当前工作路径,第一个参数传参为具体的路径

利用chdir函数,我们在父进程部分直接对cd命令进行处理,当检测到cd命令时,我们再检查其路径是否为空,若不为空,则将路径交给函数执行即可,不管是否成功,都continue

接下来还有关于对环境变量进行操作的指令,export

我们使用export的目的是对当前进程的环境变量进行操作,而不是对子进程,因此同样需要对这个函数进行操作,实际上,与环境变量相关的操作都不能让子进程去操作,而是直接在父进程进行处理

添加环境变量 —— putenv

函数声明:int putenv(char* string);

头文件:#include<stdlib.h>

函数说明:该函数用于添加环境变量,但需要注意,使用该函数传过去的指针位置,需要靠自己去进行维护,函数内部记录的事你传参过去的地址位置,该位置记录的环境变量若是后续被修改,则之前传入的环境变量也会被同样修改,所以需要自己去维护传入的变量

因此,我们可以先定义一个二维字符数组和对应下标去维护(c语言就是麻烦),然后再使用这个函数去实现添加环境变量即可

此时,我们直接调用env函数去测试是否成功时,我们看到的是这个进程通过子进程调用的env,此时看到的环境变量是通过这个进程继承下去的,我们同样可以看到结果是否正确,但是我们实际想要看到的是当前进程的环境变量,因此我们对env指令也进行处理,让它打印出父进程的环境变量表

获取当前进程的环境变量表 —— extern char **environ;

这是个声明,想获得当前的环境变量表,需要上面的声明即可,environ这个字符串数组(二级字符指针)内存的就是当前环境变量表

我们只需要打印一条条打印出来即可

接着,我们再最后完善一个指令,echo指令,echo指令作为输出指令,大部分情况可以交给子进程去做,但是涉及到环境变量的话,我们需要echo指令打印的是当前进程的环境变量,因此需要特殊处理,还有"echo $?"得到的是最近一次进程结束的退出码,这个也需要我们特殊处理

获取指定环境变量 —— getenv

函数声明:char *getenv(const char *name);

头文件:#include<stdlib.h>

函数说明:该函数用于获取指定名称的环境变量,给函数传参环境变量的名称,会返回该环境变量的内容

通过这个函数,我们可以去判断echo $... 若是识别到$,则我们可以判断是否是要找环境变量,当然也可能是$?,所以分类判断,先解决环境变量的问题

然后就是解决最近一次进程结束的退出码问题了,这里最近一次的进程,就是上一次子进程调用指令后结束的退出码,我们可以通过一个值去存下来,至于如何获取这个退出码,进程等待waitpid中有个输出型参数status,其中配套使用的宏WEXITSTATUS可以解析出退出码

WIFEXITED(status) 和 WEXITSTATUS(status)

WIFEXITED(status):检查进程是否正常退出

WEXITSTATUS(status):获取子进程的退出码

注意:这里的status是waitpid函数中第二个参数获取的status值

综上,基本的一个简单版的命令行解释器模拟实现就到这,当前有很多不完善的地方,目前这个简单版的命令行解释器,核心目的是为了在实现的过程中,更加深刻的理解进程创建、进程结束、进程等待和进程替换这四个步骤,在这个过程中熟悉和掌握各种函数接口的使用和理解,最后附加上完成的源码以供参考。

源码参考

#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/types.h>

#define MAX 1024
#define ARGC 64

int split(char* com_str,char* argv[])
{
  assert(com_str);
  assert(argv);
  char* tmp = NULL;
  tmp = strtok(com_str," ");
  int i = 0;
  if(tmp == NULL) return -1;
  while(tmp != NULL)
  {
    argv[i++] = tmp;
    tmp =  strtok(NULL," ");
  }
  return 0;
}

void test_print(char* s[])
{
  char* tmp = s[0];
  int i = 1;
  while(tmp)
  {
    printf("%s\n",tmp);
    tmp = s[i++];
  }
}

int main()
{
  extern char** environ;
  char myenv[64][256];
  int env_i = 0;
  int exit_id = 0;//子进程的退出码
  while(1)
  {

    char com_str[MAX] = {'0'};
    char* argv[ARGC] = {NULL};
    printf("输入你的指令:");
    fflush(stdout);
    char* s = fgets(com_str,sizeof(com_str),stdin);
    assert(s);
    (void)s;
    com_str[strlen(com_str)-1] = '\0';//去掉末尾的换行
    int n = split(com_str, argv);
    if(n == -1) continue;
    //关于指令接受的工作做好以后,接下来就是创建子进程让子进程去调用程序,出错后返回等等
    
    //对ls的细节处理
    if(strcmp(argv[0],"ls") == 0)
    {
      int pos = 0;
      while(argv[pos]) pos++;
      argv[pos] = (char*)"--color=auto";
    }
    //内置命令的处理
    //cd /path
    if(strcmp(argv[0],"cd") == 0)
    {
      if(argv[1] != NULL) chdir(argv[1]);
      continue;
    }
    // export myint=5
    if(strcmp(argv[0],"export")==0)
    {
      if(argv[1] != NULL)
      {
        strcpy(myenv[env_i],argv[1]);
        putenv(myenv[env_i++]);
      }
      continue;
    }
    //env
    if(strcmp(argv[0],"env") == 0)
    {
      int i;
      for(i = 0; environ[i];i++)
      {
        printf("%d : %s \n",i+1,environ[i]);
      }
      continue;
    }
    //处理echo 环境变量或者获取退出码的问题
    if(strcmp(argv[0],"echo") == 0)
    {
      if(argv[1][0] == '$')
      {
        if(argv[1][1] == '?') // echo $?
        {
          printf("%d\n",exit_id);
        }
        else//echo $环境变量名称
        {
          if(getenv(argv[1]+1))
            printf("%s\n",getenv(argv[1]+1));
        }
        continue;
      }
    }

    pid_t id = fork();
    assert(id>=0);
    (void)id;

    if(id == 0)//子进程
    {
      execvp(argv[0],argv);
      exit(1);//若是替换成功,则不应该继续执行下去,因此如果失败,我们设置退出码为1
    }
    //父进程
    int status = 0;
    waitpid(id,&status,0);
    if(WIFEXITED(status))
    {
      exit_id = WEXITSTATUS(status);
    }
  }
    return 0;
}

总结

本篇博客循序渐进的实现了一个简单的简易版Linux命令行解释器,每一步都有知识点的介绍和简单的分析说明,并且最后附上源码提供参考,模拟实现的目的是为了更好的理解上一节课学习的进程相关的四个步骤,熟悉和掌握其中的接口,更加深刻的理解进程控制的概念

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

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

相关文章

Linux安装mysql 8.0

1.使用root登录服务器 2.创建安装包存放目录 # mkdir /software # cd /software3.下载并解压mysql安装包 # wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.21-linux-glibc2.12-x86_64.tar.xz # tar xvJf mysql-8.0.21-linux-glibc2.12-x86_64.tar.xz # mv m…

Redis的特性与安装

回顾 Redis是一个在内存中存储数据的中间件&#xff0c;可以用来当数据库用&#xff0c;也可以作为缓存用(这里的缓存往往是对数据库缓存)。 中间件&#xff1a;和业务无关的服务&#xff0c;功能更加通用&#xff0c;如&#xff1a;数据库&#xff0c;缓存&#xff0c;消息队…

基于springboot实现音乐网站管理系统项目【项目源码+论文说明】计算机毕业设计

基于SpringBoot实现音乐网站管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了音乐网站的开发全过程。通过分析音乐网站管理的不足&#xff0c;创建了一个计算机管理音乐网站的方案。文章介绍了音乐…

LeetCode-热题100:230. 二叉搜索树中第K小的元素

题目描述 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 个最小元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a; root [3,1,4,null,2], k 1 输出&#xff1a; 1 示例 2&#…

算法课程笔记——List

缺点&#xff1a;不能用下标计算得到 只能 一步步来 这样才是赋值 只是得到拷贝的结果 很多容器都需要&#xff08;int&#xff09;强制转化 list可以用sort 但是 例如&#xff0c;sort(L2.begin(), L2.end());&#xff0c;这种是algorithm标准算法类提供&#xff0c;属于…

钡铼IOy系列模块在智能装备制造中发挥重要作用提升整体效能

随着科技的不断发展&#xff0c;智能装备制造已经成为推动工业进步的重要力量之一。在智能装备制造领域&#xff0c;钡铼IOy系列模块在智能装备制造中起关键作用&#xff0c;对生产效率、产品质量和工厂管理也有一定的影响。 首先&#xff0c;钡铼IOy系列模块在智能装备制造中…

阿里云服务器多少钱一年?2024年阿里云服务器租用费用一览

阿里云服务器租用价格表2024年最新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核…

po+selenium+unittest自动化测试项目实战

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

电脑缺失api-ms-win-core-path-l1-1-0.dll的5种解决方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是"api-ms-win-core-path-l1-1-0.dll丢失"。这个问题可能会导致某些软件无法正常运行或系统功能受限。那么&#xff0c;如何解决这个问题呢&#xff1f;下面将详细介绍api-ms-win-co…

【Android Studio报错】:* What went wrong:Out of memory. Java heap space

项目场景&#xff1a; 今天&#xff0c;刚打开自己的安卓项目发现报错&#xff1a; 报错&#xff1a; * What went wrong: Out of memory. Java heap space Possible solution: - Check the JVM memory arguments defined for the gradle process in: gradle.properties in…

windows C++fmt库下载

下载地址 https://github.com/fmtlib/fmt vs2019 debug x64进行编译 安装包如下 https://download.csdn.net/download/qq_36314864/89163873

10 Python进阶:AI绘画

Python AI 绘画 本文我们将为大家介绍如何基于一些开源的库来搭建一套自己的 AI 作图工具。 需要使用的开源库为 Stable Diffusion web UI&#xff0c;它是基于 Gradio 库的 Stable Diffusion 浏览器界面 Stable Diffusion web UI GitHub 地址&#xff1a;https://github.co…

【C++庖丁解牛】C++11---统一的列表初始化 | auto | decltype | nullptr | STL中一些变化

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. C11简介2. 统一的列表…

AWB学习记录

主要参考食鱼者博客&#xff1a;https://blog.csdn.net/wtzhu_13/article/details/119301096&#xff0c;以及相关的论文&#xff0c;感谢食鱼者老师整理分享。 灰度世界和完全反射 灰度世界法和完全反射法分别是基于(Rmean, Gmean, Bmean)和(Rmax, Gmax, Bmax)来进行白平衡校…

Nmap的下载与安装

目录 什么是nmap Nmap的下载 Nmap的安装 Nmap使用命令行打开 什么是nmap Nmap被誉为"扫描器之王"&#xff0c;Nmap是一个开源工具&#xff0c;提供跨平台&#xff08;Windows、linux、mac os&#xff09; Nmap的下载 第一步&#xff1a;访问nmap的官网 第二步…

「小程序教程」如何将微信小程序转成短链/二维码?

本文介绍的方法&#xff0c;可能是全网最简单最快捷的方法了&#xff0c;无需输入复杂的appid/路径/秘钥。 小程序转成短链或者二维码的好处 场景一、产品线下活动海报推广&#xff0c;放一个小程序的二维码&#xff0c;用户扫码访问后&#xff0c;短链平台可以实时查看访问数…

从零构建生产级 AI 应用 ChatWithPDF

前言 目前 AIGC 的浪潮很火&#xff0c;不管你是什么方向的程序员&#xff0c;都可以通过大模型去构建自己的 App&#xff0c;但是搜索资料学习的时候发现&#xff1a; 目前构建 AI 应用的中文资源比较少&#xff0c;大多数都是英文的。教程大多是 demo 级别&#xff0c;没有…

冯喜运:4.18黄金原油晚间行情走势预测及操作策略

【 黄金消息面分析】&#xff1a;周四(4月18日)亚市盘尾&#xff0c;黄金价格宽幅震荡&#xff0c;目前交投于2378.60美元/盎司。金价周三回落0.9%&#xff0c;收报2361.14美元/盎司&#xff0c;美国降息希望减弱造成的压力盖过了中东地缘政治动荡引发的避险需求带来的提振。随…

精确号码比例放通算法的设计与实现

精确号码比例放通算法的设计与实现 引言背景问题定义算法设计1. 数据结构2. 算法流程3. 伪代码4. C语言实现 结论参考文献 引言 随着通信技术的飞速发展&#xff0c;呼叫中心和电信运营商面临着日益增长的呼叫管理需求。在某些情况下&#xff0c;为了确保服务质量或者遵守特定…