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

2024-06-06 08:12

本文主要是介绍【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的返回值。 


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

这篇关于【Linux进程篇】Linux进程管理应用——虚假的shell脚本的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1035555

相关文章

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

python中列表应用和扩展性实用详解

《python中列表应用和扩展性实用详解》文章介绍了Python列表的核心特性:有序数据集合,用[]定义,元素类型可不同,支持迭代、循环、切片,可执行增删改查、排序、推导式及嵌套操作,是常用的数据处理... 目录1、列表定义2、格式3、列表是可迭代对象4、列表的常见操作总结1、列表定义是处理一组有序项目的

C#中的Converter的具体应用

《C#中的Converter的具体应用》C#中的Converter提供了一种灵活的类型转换机制,本文详细介绍了Converter的基本概念、使用场景,具有一定的参考价值,感兴趣的可以了解一下... 目录Converter的基本概念1. Converter委托2. 使用场景布尔型转换示例示例1:简单的字符串到

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方