Linux-Shell命令行解释器的模拟实现

news2024/11/26 22:18:59

引言:本篇文章主要是简单实现一个shell命令行解释器,可以支持基础常见的linux的命令,支持内建命名echo、cd,同时支持重定向的操作!

一、代码剖析

1. 头文件引入:

 因代码是在linux下实现,引入的大多头文件是Linux的系统调用,建议在linux环境下使用。

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

 这些头文件包含了一些需要使用的库函数和系统调用。

 2. 定义一些常量和全局变量:

这些常量和变量用于存储命令行输入、命令参数、重定向类型和重定向文件等信息。 

#define OPTION 64
#define NUM 1024
//定义重定向类型
#define NONE_REDIR   0
#define INPUT_REDIR  1
#define OUTPUT_REDIR 2
#define APPEND_REDIR  3
//定义个宏函数,用于将字符串指针移动到无空格的第一个字符上
#define trimSpace(start) do{\
  while(*start == ' '){\
      start++;}\
}while(0)
//保存键盘上输入的命令
char lineComend[NUM];
char* myargv[OPTION];//定义一个char*的指针数组,用于存放参数
int  lastCode = 0;//父进程记录上次子进程执行结果
int  lastSig = 0;
//记录是否是重定向和类型
int redirType = NONE_REDIR;
char *redirFile = NULL;//记录重定向文件名

 3.  定义一个辅助函数find_Redirect:

这个函数用于查找命令行参数中的重定向符号(>和<)并解析相关的重定向文件和类型。

void find_Redirect(char* argv){
  char* start = NULL;
  for(int i = 0;i<(int)strlen(argv);i++){
    if(argv[i] == '>'||argv[i]=='<'){
      if(argv[i]=='<'){
        //cat <   log.txt
        argv[i] = '\0';
        //去除重定向后面的空格,只保留文件名部分,存放到全局变量redirFile中
        start = argv+i+1;
        trimSpace(start);
        redirFile = start;    
        redirType = INPUT_REDIR;
      }else{
        if(argv[i+1] == '>'){
          //cat xiaomi >> log.txt
          argv[i] = '\0';
          start = argv+i+2;
          trimSpace(start);
          redirFile = start;
          redirType = APPEND_REDIR;
        }else{
          argv[i] = '\0';
          start = argv+i+1;
          trimSpace(start);
          redirFile = start;
          redirType = OUTPUT_REDIR;
        }
      }
    }
  }
}

4. 主函数

  • 主函数包含一个无限循环,在每次循环中等待用户输入命令,并处理命令执行、重定向和内置命令等逻辑。
  • 在循环的开始,通过printf输出命令提示符,然后使用fgets读取用户输入的命令行,并处理去除结尾的换行符。
  • 接下来,调用find_Redirect函数来查找重定向符号,并解析重定向文件和类型。
  • 然后,使用strtok函数对输入命令进行切割,将切割后的命令参数存储到myargv数组中。同时,处理一些特殊的内置命令,如cdecho
  • 对于echo命令,根据重定向类型进行输出重定向。
  • 如果不是内置命令,则创建子进程,并在子进程中执行命令。根据重定向类型,进行输入输出重定向。
  • 最后,使用waitpid等待子进程执行完毕,并获取子进程的退出状态码和信号。
int main(){
  //一开始先初始化重定向文件和类型
  while(1){
  redirFile = NULL;
  redirType = NONE_REDIR;
    printf("[suhh@ziqiang address]$ ");
    fflush(stdout);
    //用户输入
    assert(fgets(lineComend,sizeof(lineComend)-1,stdin)!=NULL);
    //清除数组最后一个\n
    lineComend[strlen(lineComend)-1] = '\0';
   //ls -a -l 
   //切割字符串 靠' '
   find_Redirect(lineComend);
   myargv[0] = strtok(lineComend," ");
  // char * echo_str = NULL;
   if(strcmp(myargv[0],"echo")==0){
      myargv[1] = myargv[0] + strlen(myargv[0]) + 1;//取出剩余字串
      goto echo_start;
  }
   //strtok 会把剩余的放到静态变量中,下次调用只用穿null
   int i = 1;
   //myargv[end]=NULL
   if(myargv[0]!=NULL&&(strcmp(myargv[0],"ls")==0||strcmp(myargv[0],"ll")==0)){
     myargv[i++] = (char*)"--color=auto";
     if(strcmp(myargv[0],"ll")==0) {
      myargv[i++] = (char*)"-l";
      myargv[0] = (char*)"ls";
    }
     //让ls命令默认加上颜色
   }
   while(myargv[i++] = strtok(NULL," ")){}
   if(strcmp(myargv[0],"cd")==0){
     //如果是cd命令,不能用子进程执行,如果用子进程执行不会影响父进程,达不到效果
     //这时这就是内建命令
     if(myargv[1]!=NULL)chdir(myargv[1]);
     continue;
   }
echo_start:
   if(myargv[0]!=NULL && strcmp(myargv[0],"echo")==0){
     //这也是内建命令
     //判断重定向类型
     int fd = 0;
     if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
        int flags = O_WRONLY | O_CREAT;
        if(redirType == APPEND_REDIR) flags = flags | O_APPEND;
        else flags = flags | O_TRUNC;
        fd = open(redirFile,flags,0666);
        //先把标准输出流备份一下
        dup2(1,5);
        //重定向
        dup2(fd,1);
     }
     if(myargv[1]!=NULL&&strcmp(myargv[1],"$?")==0){
       printf("%d,%d\n",lastCode,lastSig);
     }else{
       if(myargv[1]!=NULL){
         //如果用户输入 echo "hello"  去除“”
         if(myargv[1][0] =='"'){
           myargv[1] = (char*)&myargv[1][1];
         }
        int str_len =  strlen(myargv[1]);
        if((myargv[1][str_len-1] == '"')||(myargv[1][str_len -2] == '"')){
         if(myargv[1][str_len-1] == '"') str_len --;
          else str_len -= 2;
        }
         for(int i = 0 ;i<str_len;i++){
           printf("%c",myargv[1][i]);
         }
         printf("\n");
       }
     }
     if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
       //恢复标准输出流
       close(fd);
       dup2(5,1);
     }
     continue;
   }
   pid_t it = fork();
   assert(it>=0);
   if(it == 0){
     //判断重定向
     //cat < log.txt
     if(redirType == INPUT_REDIR){
         int rfd = open(redirFile,O_RDONLY);
         if(rfd == -1){
          perror("错误:open");
          exit(errno);
         }
         dup2(rfd,0);
     }else if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
      int flags = O_WRONLY | O_CREAT; 
      if(redirType == OUTPUT_REDIR) flags |= O_TRUNC;
      else flags |= O_APPEND;
      int wfd = open(redirFile,flags,0666);
      assert(wfd>=0);
      (void)wfd;
      dup2(wfd,1);
     }
     //子进程执行进程程序替换
     execvp(myargv[0],myargv);
     exit(1);
   }
   int status = 0;
   int wp = waitpid(it,&status,0);//阻塞等待
   assert(wp>=0);
   (void)wp;
   lastCode = (status>>8)&0xFF;
   lastSig = status&0x7F;
   printf("exit_code:%d,exit_signal:%d\n",lastCode,lastSig);
  }
  return 0;
}

 二、 涉及知识点

 1. fork()函数

fork函数属于系统调用,用于创建子进程,返回子进程pid给父进程,返回0给子进程.

  • fork之后会有两个进程分别执行后续的代码(其实在fork函数体内,父子进程已经产生,所以有两个返回值)
  • 子进程拥有自己的PCB和虚拟地址,这些都是和父进程一样的,也就是说,父进程的虚拟地址的内容拷贝了一份给子进程。
  • 因为子进程拥有和父进程一样的虚拟地址,体现在代码上,就是子进程也可以”共有“父进程定义的变量等,但如果子进程要改变它的值,就会发生写时拷贝,操作系统会在物理内存中重新开辟一段空间,再通过页表,重新映射到虚拟地址处。换句话说,子进程以为访问的虚拟地址是父进程一起共用的,实际上是另一个物理地址。

2. 进程程序替换

将指定进程加载到内存中,替换原本进程的代码段和数据段,让这个进程以为在执行自己的代码,其实是在执行别人的代码。

 

 在C语言中提供了丰富的函数用于程序替换

函数原型: int execl(const char *path, const char *arg, ...); 

举例:execl("/user/bin/ls","ls",NULL);

解释:第一个参数是填要替换的程序在哪,路径

           第二个参数是要填这个程序要怎么执行

           第三个参数是要填这个程序要带什么参数,要以NULL结尾

错误返回-1

 函数原型:int execlp(const char *file, const char *arg, ...);

举例:execlp("ls","ls",NULL);

解释:在上一个函数原型的基础上了一个p,代表path,此时无需传入程序地址,只需告诉程序叫什么,会自动在环境变量中进行可执行程序的查找。

           第一个参数填程序名

           第二个参数是要填这个程序要怎么执行

           第三个参数是要填这个程序要带什么参数,要以NULL结尾

错误返回-1

 函数原型:int execv(const char *path, char *const argv[])

举例:char* argv[] ={"ls","-a","-l","--color = auto",NULL};

           execv("/user/bin/ls",argv);

解释:这次加了一个v,可以将所有可执行参数放到一个字符串指针数组中,统一传递。

           第一个参数是填要替换的程序在哪,路径

           第二个参数是要填字符串指针数组

错误返回-1

 函数原型:int execvp(const char *file, char *const argv[]);

举例:execvp("ls",argv);

解释:这个加了v和p,拥有了前两个的特性。本文中的代码就是用的这个

           第一个参数填程序名

           第二个参数是要填字符串指针数组

错误返回-1

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

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

相关文章

性能测试知多少---了解前端性能

我的上一篇博文中讲到了响应时间&#xff0c;我们在做性能测试时&#xff0c;能过工具可以屏蔽客户端呈现时间&#xff0c;通过局域网的高宽带可以忽略数据传输速度的障碍。这并不是说他们不会对系统造成性能影响。相反&#xff0c;从用户的感受来看&#xff0c;虽然传输速度受…

阿里巴巴1688商品详情 API 接口示例

1688.item_get 公共参数 请求地址: https://o0b.cn/anzexi 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_…

软件外包开发质量控制方法

在软件外包开发项目中&#xff0c;质量控制是确保交付的软件符合预期质量标准的关键步骤。以下是一些常用的软件外包开发质量控制方法&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 需求明确&#x…

wpf添加Halcon的窗口控件报错:下列控件已成功添加到工具箱中,但未在活动设计器中启用

报错截图如下&#xff1a; 注意一下新建工程的时候选择wpf应用而不是wpf应用程序。 添加成功的控件&#xff1a;

博弈论——霍特林博弈(Hotelling Game)

0 引言 前一篇文章在特殊的伯特兰德博弈模型的基础上&#xff0c;解释了伯特兰德悖论&#xff0c;我们先简单回顾一下&#xff1a; 三个假设&#xff1a; &#xff08;1&#xff09;各寡头厂商通过选择价格进行竞争&#xff1b; &#xff08;2&#xff09;各寡头厂商生产的产品…

Devchat-AI 编程助手:Devchat-AI 尝鲜测评+场景实践

本心、输入输出、结果 文章目录 Devchat-AI 编程助手&#xff1a;Devchat-AI 尝鲜测评场景实践前言DevChat 简介DevChat 是什么DevChat AI 编程助手有哪些优势 DevChat 的申请和使用运行环境要求DevChat 的申请DevChat 激活DevChat 定价DevChat 的安装DevChat 的简单使用 相关图…

【GEE】使用GEE批量查询下载Landsat8数据

刚发了一篇Landsat8地表温度反演的博文&#xff0c;顺便分享一下如何使用GEE批量查询、下载Landsat8数据集。代码比较简单就是查询函数和导出函数&#xff0c;然后还有一个显示函数。网上的教程一大堆&#xff0c;都差不多的代码&#xff0c;在这里要感谢一些前辈们的无私奉献。…

JUC-1-并发编程基础

一 并发编程简介 1 什么是并发编程&#xff1f; 所谓并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。 并发编程&#xff0c;从程序设计的角度来说&#xff0c;是希望通过某些机制让计算机可以在一个时间段内…

技术分享 | web自动化测试-PageObject 设计模式

为 UI 页面写测试用例时&#xff08;比如 web 页面&#xff0c;移动端页面&#xff09;&#xff0c;测试用例会存在大量元素和操作细节。当 UI 变化时&#xff0c;测试用例也要跟着变化&#xff0c; PageObject 很好的解决了这个问题。 使用 UI 自动化测试工具时&#xff08;包…

LangChain+LLM实战---Embedding、从入门到生产使用

搜索功能已经深入到我们的日常生活中&#xff0c;我们常说“Google一下就知道了”&#xff0c;用户已经开始期望几乎每个应用程序和网站都提供某种类型的搜索功能。随着有效搜索变得越来越相关(双关语)&#xff0c;寻找新的方法和体系结构来改进搜索结果对于架构师和开发人员来…

Vue-SplitPane可拖拽分隔面板(随意拖动div)

npm install vue-splitpane一、使用 &#xff08;1&#xff09;局部使用&#xff1a; 在vue文件中 import splitPane from vue-splitpane export default {componnets: { splitPane } }&#xff08;2&#xff09;全局使用&#xff1a; 在main.js文件注册 import splitPane…

openGauss学习笔记-116 openGauss 数据库管理-设置数据库审计-审计概述

文章目录 openGauss学习笔记-116 openGauss 数据库管理-设置数据库审计-审计概述116.1 背景信息116.2 操作步骤 openGauss学习笔记-116 openGauss 数据库管理-设置数据库审计-审计概述 116.1 背景信息 数据库安全对数据库系统来说至关重要。openGauss将用户对数据库的所有操作…

放卷开环张力控制(伺服转矩模式应用)

收放卷张力开环闭环控制算法,请参考下面文章链接: PLC张力控制(开环闭环算法分析)_RXXW_Dor的博客-CSDN博客文章浏览阅读4k次,点赞3次,收藏3次。里工业控制张力控制无处不在,也衍生出很多张力控制专用控制器,磁粉制动器等,本篇博客主要讨论PLC的张力控制相关应用和算…

leetcode:2926. 平衡子序列的最大和 【树状数组维护最大前缀和】

题目链接 lc2926 题目描述 题目思路 定义b[i] nums[i] - i 目标是从b中找到一个非降子序列使得元素和最大 # b[i] nums[i] - i # 找到b的一个非降子序列使得元素和最大 # f[i]: 子序列最后一个数下标是i&#xff0c;对应的最大子序列 # f[i] max (max f[j], 0) nums[i] …

【2023Q3_技术考核经验】

单选题&#xff1a; 1.下列有关影刀全局变量的概念说法错误的是&#xff1a;© B.和流程参数一样&#xff0c;只有事先给全局变量赋值&#xff0c;全局变量才可以用到其他流程当中去 C.全局变量适合用在变量值频繁更换的场景 解释&#xff1a;在右下角设置全局变量&#x…

本地电脑监控软件

本地电脑监控软件是一种用于监控计算机使用行为的软件&#xff0c;它可以帮助企业管理者了解员工的工作状态和行为&#xff0c;保护企业的计算机资源。 本地电脑监控软件可以监控员工的计算机使用行为&#xff0c;包括屏幕监控、键盘记录、文件操作等。这些功能可以帮助企业管理…

ansible安装和常见模块

文章目录 ansible的安装1.1 yum install epel-release.noarch1.2配置epel源的baseurl1.3安装ansible1.4安装ansible报错问题1.5 yum卸载 ansible的安装 ansible是由epel源提供的&#xff0c;所以需要配置epel源。要么通过配置好的baseos源直接执行“yum install epel-release.…

LabelImg使用笔记

LabelImg使用笔记 文章目录 LabelImg使用笔记一、LabelImg简介1.1、特性1.2、LabelImg的热键 二、LabelImg安装三、3种格式的使用3.1、VOC格式标注3.2、yolo格式标注3.3、json格式 四、LabelMe 和 LabelImg适用场景 一、LabelImg简介 LabelImg 是一个用于图像标注的开源工具&a…

vuepress 打包后左侧菜单链接 404 问题解决办法

背景 上周看到一本开源书 《深入架构原理与实践》&#xff0c;是基于 vuepress 搭建的&#xff0c;下载了源码&#xff0c;本地部署了一下&#xff0c;本文记录如何打包该源码遇到的路径问题及思考。 结论&#xff1a; vuepress 插件的 sideBar 的菜单路径默认是相对 / 的&am…

Python学习笔记--类的继承

七、类的继承 1、定义类的继承 说到继承&#xff0c;你一定会联想到继承你老爸的家产之类的。 类的继承也是一样。 比如有一个旧类&#xff0c;是可以算平均数的。然后这时候有一个新类&#xff0c;也要用到算平均数&#xff0c;那么这时候我们就可以使用继承的方式。新类继…