设计一个 shell 命令行程序

目录

实现 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 inputif(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
#endifreturn strlen(command);
}
void commandSplit(char* in, char* out[])
{int argc = 0;out[argc++] = strtok(in, SEP);while(out[argc++] = strtok(NULL, SEP));
#ifdef Debugfor(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 commandexecvp(argv[0], argv);exit(1);}else // father{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){ // wait successlastcode = 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 inputchar* argv[SIZE]; // a table store commands// getUsercommand and print command_line promptint n = getUserCommand(usercommand, sizeof(usercommand));if(n <= 0) continue; // if only have a '\n'// split commands and store in arraycommandSplit(usercommand, argv);n = doBulidin(argv); // check or do bulidin_commandif(n) continue;execute(argv);}   
}

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

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

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

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

相关文章

Android进阶之旅(第5天)

充实的一天又过去了&#xff0c;今天真的好冷啊&#xff0c;我们这里雪很大&#xff0c;早上最傻逼的决定就是穿了一个短的棉袜出来&#xff0c;漏脚踝&#xff0c;冷成傻子 接下来老规矩&#xff0c;看下昨天计划的完成情况&#xff1a; 今日计划&#xff1a; 1.过bug 2.看…

Vivado MIG ip核使用教程

Step 1 在ip catalog中搜索mig ip核并打开&#xff0c;检查硬件配置 Step 2 Step 3 选择对其他芯片类型的兼容性&#xff0c;若无此方面需求&#xff0c;可直接点击next Step 4 选择存储器类型 Step 5 配置DDR3芯片工作频率、用户时钟、mig ip核输入时钟、DDR3芯片类型…

《隐私计算简易速速上手小册》第2章:关键技术介绍(2024 最新版)

文章目录 2.1 同态加密2.1.1 基础知识2.1.2 主要案例:云计算数据分析2.1.3 拓展案例 1:医疗数据分析2.1.4 拓展案例 2:金融风险评估2.2 安全多方计算(SMC)2.2.1 基础知识2.2.2 主要案例:跨机构金融数据共享2.2.3 拓展案例 1:医疗研究合作2.2.4 拓展案例 2:跨国界数据交…

Echarts —— 关系图+路径图+散点图(动态箭头)

文章目录 1 效果预览2 实现代码 1 效果预览 可将以下代码复制到Echarts示例在线预览效果 2 实现代码 const categories [{name: 数据中心,symbol:path://M936.33 732.203c-9.872-10.814-23.349-17.123-37.953-17.778-14.849-0.451-28.613 4.383-39.427 14.255-0.215 0.195-0.…

【初始RabbitMQ】发布订阅的实现

发布确认原理 生产者将信道设置成 confirm 模式&#xff0c;一旦信道进入 confirm 模式&#xff0c;所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始)&#xff0c;一旦消息被投递到所有匹配的队列之后&#xff0c;broker 就会发送一个确认给生产者(包含消息的…

Linux之ACL权限chmod命令

一. chmod命令 chmod命令来自英文词组change mode的缩写&#xff0c;其功能是改变文件或目录权限的命令。默认只有文件的所有者和管理员可以设置文件权限&#xff0c;普通用户只能管理自己文件的权限属性。 设置权限时可以使用数字法&#xff0c;亦可使用字母表达式&#xff0…

GO-ICP的使用(一)

一、代码下载以、修改以及使用 下载&#xff1a; 链接&#xff1a;yangjiaolong/Go-ICP: Implementation of the Go-ICP algorithm for globally optimal 3D pointset registration (github.com) 解压之后 &#xff1a; 首先visual studio项目&#xff0c;配置好PCL环境&…

更简单地介绍 CUDA

这篇文章是对 CUDA 的超级简单介绍&#xff0c;CUDA 是 NVIDIA 流行的并行计算平台和编程模型。我之前在2013年写过一篇文章《CUDA简单介绍》&#xff0c;多年来一直很受欢迎。但 CUDA 编程变得更加容易&#xff0c;GPU 也变得更快&#xff0c;所以是时候进行更新&#xff08;甚…

SQL-Labs46关order by注入姿势

君衍. 四十六关 ORDER BY数字型注入1、源码分析2、rand()盲注3、if语句盲注4、时间盲注5、报错注入6、Limit注入7、盲注脚本 四十六关 ORDER BY数字型注入 请求方式注入类型拼接方式GET报错、布尔盲注、延时盲注ORDER BY $id 我们直接可以从界面中得知传参的参数为SORT&#x…

Yolo v9 “Silence”模块结构及作用!

论文链接&#xff1a;&#x1f47f; YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information 代码链接&#xff1a;&#x1f47f; https://github.com/WongKinYiu/yolov9/tree/main Silence代码 class Silence(nn.Module):def __init__(self):supe…

vue2和vue3对比(语法层面)

阅读文章你将收获&#xff1a; 1 了解不使用组件化工具时&#xff0c;vue在html是如何使用的 2 知道vue2的生命周期函数有哪些 3 知道如何在组件化开发中使用vue 4 大致了解了vue2和vue3在使用上什么不同 最后&#xff1a;vue2和vue3除了下面我列出的有差异化的地方&…

day41打卡

day41打卡 46. 携带研究材料&#xff08;第六期模拟笔试&#xff09; 状态表示 ​ 二维&#xff1a;dp[i] [j] 表示从下标为[0-i]的物品里任意取&#xff0c;放进容量为j的背包&#xff0c;价值总和最大是多少。 一维&#xff1a; ​ dp[j]表示&#xff1a;容量为j的背包&a…

模型 HBG(品牌增长)

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_总纲目录。品牌增长法。 1 HBG(品牌增长)模型的应用 1.1 江小白使用HBG模型提高品牌知名度和销售额 选择受众市场&#xff1a;江小白的目标客户是年轻人&#xff0c;他们喜欢简单、时尚的产品。因此&#xff0c;江…

数据结构D4作业

1.实现单向循环链表的功能 loop.c #include "loop.h" loop_p create_loop() { loop_p H(loop_p)malloc(sizeof(loop)); if(HNULL) { printf("创建失败\n"); return NULL; } H->len0; H->nextH; ret…

基于ElementUI封装省市区四级联动下拉选择

基于ElementUI封装的省市区下拉级联选择 效果 数据 最新省市区JSON数据获取&#xff1a;https://xiangyuecn.github.io/AreaCity-JsSpider-StatsGov/ 参数说明 参数说明inputNumShow下拉框的数量&#xff0c;最多4个defaultAddress默认显示省市区 例&#xff1a;[‘安徽’, …

【C++初阶】--类和对象(下)

目录 一.const成员 1.权限放大问题 2.权限的缩小 二.再谈构造函数 1.构造函数体赋值 2.初始化列表 (1)概念 (2)使用 ①在对象实例化过程中&#xff0c;成员变量先依次进行初始化 ②再进行函数体内二次赋值 3.explicit关键字 (1)C为什么要存在自动隐式类型转换…

算法打卡day1|数组篇|Leetcode 704.二分查找、27.移除元素

数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合&#xff0c;可以方便的通过下标索引的方式获取到下标下对应的数据。 1.数组下标都是从0开始的。 2.数组内存空间的地址是连续的。 正是因为数组的在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添…

【深度学习笔记】3_5 图像分类数据集fashion-mnist

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 3.5 图像分类数据集&#xff08;Fashion-MNIST&#xff09; 在介绍softmax回归的实现前我们先引入一个多类图像分类数据集。它将在后面的章节中被多次使用&#xff0c…

《Docker 简易速速上手小册》第1章 Docker 基础入门(2024 最新版)

文章目录 1.1 Docker 简介与历史1.1.1 Docker 基础知识1.1.2 重点案例&#xff1a;Python Web 应用的 Docker 化1.1.3 拓展案例 1&#xff1a;使用 Docker 进行 Python 数据分析1.1.4 拓展案例 2&#xff1a;Docker 中的 Python 机器学习环境 1.2 安装与配置 Docker1.2.1 重点基…

消息队列-RabbitMQ:发布确认—发布确认逻辑和发布确认的策略

九、发布确认 1、发布确认逻辑 生产者将信道设置成 confirm 模式&#xff0c;一旦信道进入 confirm 模式&#xff0c;所有在该信道上面发布的消息都将会被指派一个唯一的 ID (从 1 开始)&#xff0c;一旦消息被投递到所有匹配的队列之后&#xff0c;broker 就会发送一个确认给…