设计一个 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,一经查实,立即删除!

相关文章

计算机网络面经-HTTP 与 HTTPS 的区别?

目录 基本概念 HTTP 与 HTTPS 区别 HTTP 与 HTTPS 的区别? 安全性 不安全 安全 默认端口 80 443 资源消耗 较少 较多 是否需要证书 不需要 需要 报文是否加密 明文 密文 基本概念

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芯片类型…

golang中goroutine和线程的区别?

谈到 goroutine&#xff0c;绕不开的一个话题是&#xff1a;它和 thread 有什么区别&#xff1f; 参考资料【How Goroutines Work】告诉我们可以从三个角度区别&#xff1a;内存消耗、创建与销毀、切换。 内存占用 创建一个 goroutine 的栈内存消耗为 2 KB&#xff0c;实际运…

jax可微分编程的笔记(2)

jax可微分编程的笔记&#xff08;2&#xff09; 第2章 自动微分 自动微分和符号求导有诸多的相似之处&#xff1a;它们同样依赖于计算图 的构建&#xff0c;同样依赖于求导的递归实现。从某种意义上来说&#xff0c;它们 甚至有完全相同的数据结构。不过&#xff0c;二者的区…

React学习计划-react-hooks补充

React Hooks 1. 使用hooks理由 高阶组件为了复用&#xff0c;导致代码层级复杂生命周期的复杂 2. useState(保存组件状态) const [state, setstate] useState(initialState)3. useEffect(处理副作用)和useLayoutEffect(同步执行副作用) 使用方式&#xff1a; useEffect(…

《隐私计算简易速速上手小册》第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:跨国界数据交…

【git 使用】git pull 和 git fetch 的区别

恕我直言&#xff0c;我一直都用 git pull 从来没有用过 git fetch git fetch, git pull 都可以用于获取远程仓库的内容&#xff0c;但它们有不同的作用和用法。 git fetch 用途&#xff1a;git fetch 用于从远程仓库获取最新的提交&#xff0c;但不会自动合并或更新本地分支…

C语言学习第三十天(排序)

1、冒泡排序 冒泡排序是我们学习的第一个排序&#xff0c;原理是相邻两个数比较大小&#xff0c;从而决定是否交换 void BubbleSort1(int* a, int n) {for (int j 0; j < n; j){int flag 0;for (int i 1; i < n - j; i){if (a[i - 1] > a[i]){Swap(&a[i - 1]…

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 就会发送一个确认给生产者(包含消息的…

Mysql 时间格式化 date_format

有没有遇到过类似这样的问题&#xff0c;将日期 ‘2023-02-23‘ 的格式转化为 202302的格式。 可能有些小伙伴&#xff0c;会直接想到字符串拼接 concat(year(2023-02-23),month(2023-02-23))得到的也是 202302。那要是要求必须是时间格式呢&#xff0c;可能有些小伙伴会说再将…

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环境&…

2024年这些半导体行业告诉我,投资迎来高光

在下行的周期内&#xff0c;砍单、裁员、破产注销等关键词贯穿了2023一年。 我们挥挥手&#xff0c;告别掉过去&#xff0c;怀着对未来的美好希望奔向2024年。未来虽然代表着未知&#xff0c;也有着各种不确定性&#xff0c;但在产业的发展的过程中&#xff0c;行业趋势我们是…

更简单地介绍 CUDA

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

【Vue实现参数传递:查询参数 vs. 动态路由】

文章目录 查询参数传递1. 什么是查询参数&#xff1f;2. 在Vue中使用查询参数步骤 1&#xff1a;在路由配置中定义查询参数步骤 2&#xff1a;在组件中使用查询参数步骤 3&#xff1a;在页面中生成链接 3. 查询参数传递的优势 动态路由传递1. 什么是动态路由&#xff1f;2. 在V…

SQL-Labs46关order by注入姿势

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

Linux内核处理并发与竞争的一种方法:信号量

一. 简介 本文来学习Linux内核处理并发与竞争的一种方法:信号量。 本文主要对Linux内核提供的信号量进行简单的介绍。 二. Linux内核处理并发与竞争的一种方法:信号量 1. 信号量简介 大家如果有学习过 FreeRTOS 或者 UCOS 的话就应该对信号量很熟悉,因为信号量是同步…

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…