设计一个 shell 命令行程序

news2024/9/23 1:31:30

目录

实现 shell 主要思路 

代码(Linux)系统


实现 shell 主要思路 

1、要知道一个 shell 进程在运行起来都会在命令行呈现什么,如图是Xshell 登录成功后的界面:所以第一步要做的就是打印命令行提示符。

Xshell 命令行提示符的组成是:[用户名@主机名 工作目录]$,那么我们自己 shell 的命令行提示符就可以按照 Xshell 的为模板,用户名,主机名,工作目录这些可以通过环境变量(USER,HOSTNAME,PWD)来获取。

用户在输入的时候,会在命令与选项之间加上空格,所以不能使用 scanf 来输入,所以要使用 fgets或 gets 来获取用户输入。C语言默认打开三个默认输入输出流:stdin(键盘),stdout(显示器),stderror(显示错误信息)

但是在输入的时候,会默认加上一个回车(Enter),这就使得我们输入的字符串后面会有一个‘\n’,所以需要将 '\n' 的位置改为'\0'。

最后返回输入命令字符串的长度,方便后面判断命令字符串是否只有‘\n’。

2、然后就是获取用户在命令行输入的字符串,并且将这一个大字符串分割形成一个个子字符串,将分割形成的这些字符串作为参数,传递给 exec 系列的接口来实现程序替换。

在C语言中使用 strtok 函数,在C++ 中使用 string 的 substr 接口。用C语言 strtok 实现如下:

宏定义的SEP 是字符串分割符,“ ”(空格),具体参考strtok函数,它第一次需要传要分割的字符串,后面传 NULL ,每次返回一个被分割的子串。

将这些被分割的字符存入一个char* 类型的数组,结束当分割结束时,strtok 函数返回 NULL,刚好作为数组的最后一个元素,作为数组的最后一个元素,也方便后期调用 exec 系列的接口。

3、实现进程替换功能

因为之前已经将用户输入的字符串分割成子串放入 srgv 数组中了,所以argv[0] 和 argv 数组恰好匹配 execvp接口的两个参数,父进程用 rid 通过status 输出型参数在 waitpid 接口中获取子进程的退出码。

4、运行内建命令

有些命令在我们设计的 shell 中运行无效,如 cd、export、echo等,因为我们在执行的时候是让myshell 产生的子进程在执行命令,而像 cd、export、echo 这种,只有让父进程 bash 自己执行才有效的,这种命令叫内建命令。

内建命令就是 bash 自己执行的,类似于自己内部的一个函数,所以需要在进行进程替换前先判断用户输入的命令是否是内建命令,穷举出内建命令并以此比较,如果是某一内建命令就直接执行,如果不是内建命令就进行程序替换!

5、将 myshell 程序设置成一个死循环,使其可以一直执行打印命令行提示符、获取用户命令字符串,实现程序替换功能。

这个程序和系统的shell 程序一样,都是“死循环”,只能手动终止!

代码(Linux)系统

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#define NUM 1024
#define SIZE 64
#define SEP " "
//#define Debug 1
char cwd[NUM];
char enval[NUM];
int lastcode = 0; //exit code
// get environment variable
const char* getUsername()
{
  const char* name = getenv("USER");
  if(name) return name;
  else return "none";
}
const char* getHostname()
{
  const char* host = getenv("HOSTNAME");
  if(host) return host;
  else return "none";
}
const char* getCwd()
{
  const char* cwd = getenv("PWD");
  if(cwd) return cwd;
  else return "none";
}
int getUserCommand(char* command, int num)
{
  printf("[%s@%s %s]#", getUsername(), getHostname(), getCwd());
  char* r = fgets(command, num, stdin); // still have a '\' in the end when you input
  if(r == NULL) return -1;
  // remove the '\n'
  command[strlen(command) - 1] = '\0'; // input's input  at least have a '\n', so dont't will crossed
#ifdef Debug 
   printf("%s", usercommand); //test
#endif
  return strlen(command);
}
void commandSplit(char* in, char* out[])
{
  int argc = 0;
  out[argc++] = strtok(in, SEP);
  while(out[argc++] = strtok(NULL, SEP));
#ifdef Debug
  for(int i = 0; out[i]; i++)
  {
    printf("%d:%s\n", i, out[i]);
  }
#endif
}
int execute(char* argv[])
{
  pid_t id = fork();
  if(id < 0) return -1;
  else if(id == 0) // child 
  {
    // process replace   exec command
    execvp(argv[0], argv);
    exit(1);
  }
  else // father
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0){ // wait success
      lastcode = WEXITSTATUS(status);
    }
  }
  return 0;
}
void cd(char* path)
{
  chdir(path);
  char tmp[NUM];
  getcwd(tmp, sizeof tmp);
  sprintf(cwd, "PWD=%s", tmp);
  putenv(cwd);
}
int doBulidin(char* argv[])
{
  if(strcmp(argv[0], "cd") == 0)
  {
    char* path = NULL;
    if(argv[1] == NULL) path = ".";
    else path = argv[1];
    cd(path);
    return 1;
  }
  else if(strcmp(argv[0], "export") == 0)
  {
    if(argv[1] == NULL) return 1;
    strcpy(enval, argv[1]);
    putenv(enval);
    return 1;
  }
  else if(strcmp(argv[0], "echo") == 0)
  {
    char *val = argv[1] + 1; // $PATH  $?
    if(strcmp(val, "?") == 0) {
      printf("%d\n", lastcode);
      lastcode = 0;
    }
    else {
      printf("%s\n", val);
    }
    return 1;
  }
  return 0;
}
int main()
{
  while(1){
      char usercommand[NUM]; // store user's input
      char* argv[SIZE]; // a table store commands
      // getUsercommand and print command_line prompt
      int n = getUserCommand(usercommand, sizeof(usercommand));
      if(n <= 0) continue; // if only have a '\n'
      // split commands and store in array
      commandSplit(usercommand, argv);
      n = doBulidin(argv); // check or do bulidin_command
      if(n) continue;
      execute(argv);
  }   
}

总结:本质上说,自己设计一个 shell 进程,就是进行多次的程序替换。 

附:进程替换(函数)接口

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

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

相关文章

人工智能_普通服务器CPU_安装清华开源人工智能AI大模型ChatGlm-6B_003---人工智能工作笔记0098

前面的环境安装差不多了,这里我没有安装git,因为我认为用不到,好下面去下载算法: 首先是算法下载: https://codeload.github.com/THUDM/ChatGLM-6B/zip/refs/heads/main 算法的下载连接是这里: 可以看到下载以后得到这个ChatGLM-6B-main这个算法压缩包 然后我们再去: 然后…

Linux配置Maven环境变量

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

探究全链路压力测试的含义与重要性

全链路压力测试是指对整个应用系统的各个环节或组件进行压力测试&#xff0c;以模拟实际生产环境中的用户负载和流量&#xff0c;评估系统在高负载条件下的性能表现。 1. 全链路压力测试的含义 全链路压力测试涉及系统的所有组件和环节&#xff0c;包括前端用户界面、应用服务器…

Java多线程并发学习

一、Java 中用到的线程调度 1. 抢占式调度&#xff1a; 抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制&#xff0c;系统控制指的是在系统某种运行机制下&#xff0c;可能每条线程都分同样的执行时间片&#xff0c;也可能是某些线程执行的时间片较长&#xff0…

智胜未来,新时代IT技术人风口攻略-第七版(弃稿)

文章目录 前言鸿蒙生态科普调研人员画像角色先行结论 - 市场下的增量蛋糕高校助力鸿蒙 - 掀起鸿蒙教育热潮高校鸿蒙课程开设占比 - 巨大需求背后是矛盾冲突教研力量并非唯一原因 - 看重教学成果复用与效率 企业布局规划 - 多元市场前瞻视野全盘接纳仍需一段时间 - 积极正向的一…

神经网络系列---计算图基本原理

文章目录 计算图符号微分符号微分的步骤示例符号微分在计算图中的使用总结 数值微分前向差分法中心差分法数值微分的使用注意事项总结 自动微分1. 基本原理2. 主要类型3. 计算图4. 应用5. 工具和库6. 优点和缺点 计算图1. **计算图的建立**2. **前向传播**3. **反向传播**4. **…

IT廉连看——C语言——循环语句

IT廉连看——C语言——循环语句 循环语句分为三种&#xff1a; while for do while 一、while循环 我们已经掌握了&#xff0c;if语句&#xff1a; if(条件)语句; 当条件满足的情况下&#xff0c;if语句后的语句执行&#xff0c;否则不执行。 但是这个语句只会执行一次…

调度和管制机制

目录 1 调度机制 分组按优先级排队 公平排队 FQ (Fair Queuing) 加权公平排队 WFQ (Weighted Fair Queuing) WFQ 与 FIFO 的比较 2 管制机制 漏桶管制器 (leaky bucket policer) 3 漏桶机制与加权公平排队相结合 调度和管制机制是使互联网能够提供服务质量的重要措施。…

K线实战分析系列之三:吞没形态

K线实战分析系列之三&#xff1a;吞没形态 一、吞没形态二、看涨吞没形态三、看跌吞没形态四、吞没形态判别标准 一、吞没形态 两根或两根以上的K线形成的组合形态&#xff0c;吞没形态就是一种主要的反转形态。 这个形态由两根K线组成&#xff0c;前短后长&#xff0c;一阴一…

软考-中级-系统集成2023年综合知识(三)

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 软考中级专栏回顾 专栏…

Raspbian命令行RTSP/RTP服务

Raspbian命令行RTSP/RTP服务 1. 源由2. Raspbian摄像头2.1 命令行启动RTP摄像头2.2 命令行启动RTSP摄像头 3. 示例3.1 测试RTP摄像头3.2 测试RTSP摄像头3.3 QGroundControl测试3.3.1 RTSP配置3.3.2 RTP配置 4. 总结5. 参考资料 1. 源由 鉴于实际测试发现RTP协议下&#xff0c;…

K8S部署Java项目 pod报错 logs日志内容:no main manifest attribute, in app.jar

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

责任链模式与spring容器的搭配应用

背景 有个需求&#xff0c;原先只涉及到一种A情况设备的筛选&#xff0c;每次筛选会经过多个流程&#xff0c;比如先a功能&#xff0c;a功能通过再筛选b功能&#xff0c;然后再筛选c功能&#xff0c;以此类推。现在新增了另外一种B情况的筛选&#xff0c;B情况同样需要A情况的筛…

NestJS入门7:增加异常过滤器

前文参考&#xff1a; NestJS入门1 NestJS入门2&#xff1a;创建模块 NestJS入门3&#xff1a;不同请求方式前后端写法 NestJS入门4&#xff1a;MySQL typeorm 增删改查 NestJS入门5&#xff1a;加入Swagger NestJS入门6&#xff1a;日志中间件 本文代码基于上一篇文章《…

数字化转型导师坚鹏:政府数字化流程管理

政府数字化流程管理 课程背景&#xff1a; 很多政府存在以下问题&#xff1a; 不清楚数字化对流程有什么影响&#xff1f; 不知道政府业流程如何进行优化&#xff1f; 不知道政府业流程优化的具体案例&#xff1f; 课程特色&#xff1a; 有实战案例 有原创观点 …

【大厂AI课学习笔记NO.52】2.3深度学习开发任务实例(5)需求采集考虑维度

今天来学习&#xff0c;怎么做需求分析&#xff0c;如何明确数据采集需求。 我把自己考试通过的学习笔记&#xff0c;都分享到这里了&#xff0c;另外还有一个比较全的思维脑图&#xff0c;我导出为JPG文件了。下载地址在这里&#xff1a;https://download.csdn.net/download/g…

【C++私房菜】面向对象中的多态

文章目录 一、多态二、对象的静态类型和动态类型三、虚函数和纯虚函数1、虚函数2、虚析构函数3、抽象基类和纯虚函数4、多态的原理 四、重载、覆盖(重写)、隐藏(重定义)的对比 一、多态 OOP的核心思想是多态性(polymorphism)。多态性这个词源自希腊语&#xff0c;其含义是“多…

免费多域名证书,最多支持保护250个域名

随着企业规模扩大和多元化发展&#xff0c;拥有多个域名的需求变得普遍&#xff0c;此时&#xff0c;多域名SSL证书应运而生&#xff0c;并且这一类型的证书已经发展到能够安全地支持多达250个不同域名的加密需求。 多域名SSL证书&#xff0c;也称为SAN&#xff08;Subject Alt…

【《高性能 MySQL》摘录】第 2 章 MySQL基准测试

文章目录 2.1 为什么需要基准测试2.2 基准测试的策略2.2.1 测试何种指标 2.3 基准测试方法2.3.1 设计和规划基准测试2.3.2 基准测试应该运行多长时间2.3.3 获取系统性能和状态2.3.4 获得准确的测试结果2.3.5 运行基准测试并分析结果2.3.6 绘图的重要性 2.4 基准测试工具…

[面试] 什么是死锁? 如何解决死锁?

什么是死锁 死锁&#xff0c;简单来说就是两个或者多个的线程在执行的过程中&#xff0c;争夺同一个共享资源造成的相互等待的现象。如果没有外部干预线程会一直阻塞下去. 导致死锁的原因 互斥条件&#xff0c;共享资源 X 和 Y 只能被一个线程占用; 请求和保持条件&#xf…