【Linux进程篇】Linux进程管理应用——虚假的shell脚本

W...Y的主页 😊

代码仓库分享💕 

 


前言:我们已经了解了进程的工作原理,并且学习了进程创建、进程终止、进程等待以及进程程序替换。为了更好的巩固这些知识,我们可以创建一个简易的shell命令行。

目录

做一个简易的shell

观察shell命令行

获取命令行

解析命令行

执行命令行

处理内建指令

完整代码


做一个简易的shell

观察shell命令行

我们要做命令行就要观察其行为,考虑下面这个与shell典型的互动:

[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:

1. 获取命令行
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)

5. 父进程等待子进程退出(wait) 

根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了。

获取命令行

首先我们得先创建一个命令行提示符,在Linux不同版本下命令行提示符的格式不太一样。在这里我们使用centos os7的命令行提示符。

格式为:

[用户名@主机名 当前路径]$/#
$一般是普通用户,#一般是超级用户root

上面的用户名、主机名、当前路径在环境变量中都可以查询到,我们可以使用getenv函数进行获取:

const char* HostName()
{char *hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "None";
}const char* UserName()
{char *hostname = getenv("USER");if(hostname) return hostname;else return "None";
}const char *CurrentWorkDir()
{char *hostname = getenv("PWD");if(hostname) return hostname;else return "None";
}

并且我们要获取用户所输入的命令。在这里我们不能使用scanf获取输入,因为使用scanf遇到空格时会停止读入。所以我们使用fgets函数获取输入。

 char commandline[SIZE];
// 1. 打印命令行提示符,获取用户输入的命令字符串
int n = Interactive(commandline, SIZE);int Interactive(char out[], int size)
{// 输出提示符并获取用户输入的命令字符串"ls -a -l"printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());fgets(out, size, stdin);out[strlen(out)-1] = 0; //'\0', commandline是空串的情况?return strlen(out);
}

解析命令行

因为我们最后要让子进程进行进程程序替换,所以我们要使用enev*函数,这些函数需要将命令和拆开一步一步执行,下一步就是我们将获取的命令行按照空格的方式拆开,放到argv的指针数组中进行保存。进程程序替换博客

我们推荐使用C语言提供的接口,strtok函数可以将一个字符串按照特定的字符进行切割。

void Split(char in[])
{int i = 0;argv[i++] = strtok(in, SEP); // "ls -a -l"while(argv[i++] = strtok(NULL, SEP)); if(strcmp(argv[0], "ls") ==0){argv[i-1] = (char*)"--color";argv[i] = NULL;}
}//main函数
Split(commandline);

执行命令行

执行命令行时为了不让我们的本进程改变,所以要创建子进程进行替换。然后用进程程序替换函数进行替换。

void Execute()
{pid_t id = fork();if(id == 0){// 让子进程执行命名execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) lastcode = WEXITSTATUS(status); 
}

 这个简易的shell就已经做好了,当我们使用一些代码时就可以进行了。但是我们要注意当我们进行回车时会出现问题,所以我们得在解析命令行之前进行判断,如果获取的字符串个数为0,我们进行continue即可。

处理内建指令

但是还有一个问题,我们执行不了如cd + 路径等的内建指令。就拿cd指令做演示,因为cd命令是想改变当前路径的,但是我们使用子进程进行程序替换时,子进程进行完时就会退出没有实际意义,我们想要的其实是bash进程的切换,所以这种内建指令不能使用子进程进行替换,我们得进行特殊处理。

 首先我们得检测一个命令是否是内建命令,如果是返回值为1,不是内建命令返回值为0,当返回值为1时,我们不需要让子进程进行程序替换,我们直接continue即可。这里我们以cd命令作为例子。

我们只能使用strcmp函数与内建命令cd进行判断是否相等,如果相等我们拿去argv中的1号位置内容进行判断,如果cd后面没有指令直接获取家目录的路径。getenv函数即可。

char *Home()
{return getenv("HOME");
}

如果有cd命令后面有路径,我们直接使用chdir函数将更改工作目录的路径。

 

int BuildinCmd()
{int ret = 0;// 1. 检测是否是内建命令, 是 1, 否 0if(strcmp("cd", argv[0]) == 0){// 2. 执行ret = 1;char *target = argv[1]; //cd XXX or cdif(!target) target = Home();chdir(target);return ret;}
}

但是我们运行后发现执行后没有问题,但是在命令行提示符中的当前工作路径却没有改变,也就是说我们刚才写的获取当前路径的函数中获取环境变量中的路径没有改变,所以我们要实时对环境变量进行更新。

我们使用getcwd函数可以获取当前路径,然后再修改环境变量中的路径即可。

 更新后的代码:

int BuildinCmd()
{int ret = 0;// 1. 检测是否是内建命令, 是 1, 否 0if(strcmp("cd", argv[0]) == 0){// 2. 执行ret = 1;char *target = argv[1]; //cd XXX or cdif(!target) target = Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd, SIZE, "PWD=%s", temp);putenv(pwd);}return ret;
}

 我们的内建命令不止有cd,还有export,echo指令等待。所以我们也对这些命令做了特殊处理。这些代码直接放在完整代码中,想要了解的可以查看。

完整代码

一下是简易shell的完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; // for test
int lastcode = 0;const char* HostName()
{char *hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "None";
}const char* UserName()
{char *hostname = getenv("USER");if(hostname) return hostname;else return "None";
}const char *CurrentWorkDir()
{char *hostname = getenv("PWD");if(hostname) return hostname;else return "None";
}char *Home()
{return getenv("HOME");
}int Interactive(char out[], int size)
{// 输出提示符并获取用户输入的命令字符串"ls -a -l"printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());fgets(out, size, stdin);out[strlen(out)-1] = 0; //'\0', commandline是空串的情况?return strlen(out);
}void Split(char in[])
{int i = 0;argv[i++] = strtok(in, SEP); // "ls -a -l"while(argv[i++] = strtok(NULL, SEP)); if(strcmp(argv[0], "ls") ==0){argv[i-1] = (char*)"--color";argv[i] = NULL;}
}void Execute()
{pid_t id = fork();if(id == 0){// 让子进程执行命名execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) lastcode = WEXITSTATUS(status); //printf("run done, rid: %d\n", rid);
}int BuildinCmd()
{int ret = 0;// 1. 检测是否是内建命令, 是 1, 否 0if(strcmp("cd", argv[0]) == 0){// 2. 执行ret = 1;char *target = argv[1]; //cd XXX or cdif(!target) target = Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd, SIZE, "PWD=%s", temp);putenv(pwd);}else if(strcmp("export", argv[0]) == 0){ret = 1;if(argv[1]){strcpy(env, argv[1]);putenv(env);}}else if(strcmp("echo", argv[0]) == 0){ret = 1;if(argv[1] == NULL) {printf("\n");}else{if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n", lastcode);lastcode = 0;}else{char *e = getenv(argv[1]+1);if(e) printf("%s\n", e);}}else{printf("%s\n", argv[1]);}}}return ret;
}int main()
{while(1){char commandline[SIZE];// 1. 打印命令行提示符,获取用户输入的命令字符串int n = Interactive(commandline, SIZE);if(n == 0) continue;// 2. 对命令行字符串进行切割Split(commandline);// 3. 处理内建命令n = BuildinCmd();if(n) continue;// 4. 执行这个命令Execute();}return 0;
}

exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。
这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间。如下图

一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。 


以上就是本次的全部内容,感谢大家观看! 

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

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

相关文章

GAT1399协议分析(六)--校时

一、官方消息定义 DeviceIDType &#xff1a;GA/T1400.1,采集设备、 卡口点位、 采集系统、分析系统、视图库、应用平台等设备编码规则 TimeCorrectModeType&#xff1a; dateTime时间格式&#xff1a; TimeZone&#xff1a;时区&#xff0c;GAT1400里面没有找到具体内容&…

卧式搅拌机:一键自动称重轻松搞定

在现代化工业生产中&#xff0c;G效、精准的设备是提高生产效率、降低生产成本。近年来&#xff0c;卧式搅拌机一键自动称重包装机的出现&#xff0c;无疑为众多行业带来了变革。这种集搅拌、称重、包装于一体的智能化设备&#xff0c;以其G效、便捷、精准的特点&#xff0c;迅…

六轴机器手臂运动控制——直流伺服反馈系统设计(比赛项目计划书+设计总结+硬件+源代码+上位机等)

TW的硬件一直很强&#xff0c;这是难得的硬件创新比赛的优秀作品&#xff0c;资料非常完整理。 硬件设计&#xff0c;源码&#xff0c;项目计划书&#xff0c;甚至包含了事后的复盘总结文档。 是不可多得的好资料。 项目系统框架图 1. 硬件系统框架图 (请以方块图形式呈现) …

idea的代码没有提交到仓库怎么撤回到本地?

代码已经提交到变更列表但是还没有push推送到仓库上&#xff0c;可以用这个方法 点击日志-右键要撤回的记录-选择撤销提交 撤销的又回到本地变更 当然你只能撤销自己提交的&#xff0c;别人的你撤销不了

python-题库篇-为什么数组下标从0 开始而不是 1

为什么很多编程语言要把 0 作为第一个下标索引&#xff0c;而不是直观的 1 呢&#xff1f; 这个问题 Dijkstra 已经解答过了&#xff0c;没错&#xff0c;就是你知道的 Dijkstra&#xff0c;Dijkstra 最短路径算法&#xff0c;荷兰语全名是 Edsger Wybe Dijkstra&#xff0c;于…

定制高温隧道炉,如何判断质量好坏

在现代工业生产中&#xff0c;高温隧道炉扮演着不可或缺的角色。对于特定工艺要求&#xff0c;如陶瓷烧制、金属热处理等&#xff0c;定制化的高温隧道炉更是不可或缺。然而&#xff0c;面对市场上琳琅满目的产品&#xff0c;如何判断高温隧道炉的质量好坏成为了企业决策者面临…

js终止递归

终止递归 1. 实现目标&#xff1a;js 编写递归方法 查找指定节点&#xff1b; 2. 需解决问题&#xff1a;找到所需节点后&#xff0c;递归不会终止&#xff0c;直到所有节点遍历完成后才会停止&#xff0c;会消耗性能 3. 解决方案&#xff1a;优化递归方法&#xff0c;在找到…

解决vscode终端不显示conda环境变量名称问题【详细步骤!实测可行!!】

最近在使用Visual Studio Code (VSCode) 时候&#xff0c;发现终端没有正确显示激活的conda环境名称&#xff0c;搜了一下&#xff0c;找到原因&#xff0c;记录一下&#xff0c;如果有人也遇到同样的问题&#xff0c;可以收藏一下。   分别两种情况&#xff0c;一是windows系…

一周学会Django5 Python Web开发 - Django5内置Auth认证系统-用户登录实现

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计57条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

JVM学习-内存泄漏

内存泄漏的理解和分类 可达性分析算法来判断对象是否是不再使用的对象&#xff0c;本质都是判断一上对象是否还被引用&#xff0c;对于这种情况下&#xff0c;由于代码的实现不同就会出现很多内存泄漏问题(让JVM误以为此对象还在引用&#xff0c;无法回收&#xff0c;造成内存泄…

旭日X3与英伟达Orin NX通过TCP传输图片

观前提醒&#xff1a;本文主要内容为使用Python在局域网内建立TCP连接并传输图片信息&#xff0c;计算机为一块旭日X3和一块英伟达Orin NX。 一、什么是TCP TCP&#xff08;传输控制协议&#xff09;是一种可靠的、面向连接的协议&#xff0c;它确保数据包的顺序传输和完整性…

关于教务排课的那些事

在办学过程中&#xff0c;你是否被如下问题困扰&#xff1f; 1、排课功率低&#xff1a; 为了确保师资资源得到充分利用&#xff0c;教务教师排课要求了解每一个全职和兼职教师&#xff0c;了解每一个人的时刻组织和带班状况&#xff0c;因而在排课的时分需求处理很多的信息&a…

【Java数据结构】二叉树详解(四)

&#x1f512;文章目录&#xff1a; 1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; 2.给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 2.1第一种思路 2.2第二种思路 3.根据一棵树的前序遍历与中序遍历构造二叉树 4.根据一棵树的中序…

图文型LED显示屏的结构与安装

随着科技的不断进步&#xff0c;LED显示屏已成为商业广告、公共信息显示等领域不可或缺的一部分。图文型LED显示屏以其独特的优势和多样化的应用场景&#xff0c;受到了市场的广泛欢迎。本文将详细介绍图文型LED显示屏的结构特点、工作原理以及安装指南。 1、图文型LED显示屏的…

跨境电商|Facebook Marketplace怎么做?

2016 年&#xff0c;Facebook打造了同名平台 Facebook Marketplace。通过利用 Facebook 现有的庞大客户群&#xff0c;该平台取得了立竿见影的成功&#xff0c;每月访问量将超过 10 亿。对于个人卖家和小企业来说&#xff0c;Facebook Marketplace是一个不错的销货渠道&#xf…

通过ssr-echarts,服务端生成echarts图

ssr-echarts &#xff1a;一个开源项目&#xff0c;它能够服务端渲染 ECharts 图表&#xff0c;并直接生成 PNG 图片返回。该项目提供了多种主题&#xff0c;并且支持 GET 和 POST 请求。如果参数较多&#xff0c;建议使用 POST 方法。用户可以自己部署这个服务。 1. 服务端安装…

Three.js-实现加载图片并旋转

1.实现效果 2. 实现步骤 2.1创建场景 const scene new THREE.Scene(); 2.2添加相机 说明&#xff1a; fov&#xff08;视场角&#xff09;&#xff1a;视场角决定了相机的视野范围&#xff0c;即相机可以看到的角度范围。较大的视场角表示更广阔的视野&#xff0c;但可能…

SBOM是如何帮助医疗器械制造商提高产品透明度的?

SBOM&#xff08;软件物料清单&#xff09;通过以下方式帮助医疗器械制造商提高产品透明度&#xff1a; 1. 详细记录软件组成 SBOM详细列出了医疗器械所使用的所有软件组件、版本、作者、许可证信息等。这使得制造商能够清晰地了解产品的软件组成&#xff0c;包括每个组件的来…

基于springboot实现民族婚纱预定系统项目【项目源码+论文说明】

基于springboot实现民族婚纱预定系统的设计演示 摘要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本民族婚纱预定系统就是在这样的大环境下诞生&#xff0c;其可…

【Emgu CV教程】10.15、FillPoly()不规则形状填充颜色

文章目录 一、概念二、填充不规则图形1.原始素材2.代码3.运行结果 二、最大轮廓填充颜色1.原始素材2.代码3.运行结果 一、概念 图像的连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域&#xff0c;连通域分析是指在图像中寻找出彼此互相独立的连通域并将其标记出来…