Linux——shell程序的简单实现

shell程序的简单实现

本章思维导图:

在这里插入图片描述注:本章思维导图对应的.xmind.png文件都已同步导入至资源,可免费查阅


在学习完有关进程的知识后,我们就可以开始尝试自己实现一个简单的shell程序了。

注:在编写简单的shell程序之前,你首先需要掌握:

👉进程控制

👉环境变量

👉进程替换

1. 实现交互 interact()

首先,和真正的shell程序一样,我们启动程序,shell就会打印出命令行提示符,并等待用户的输入

在这里插入图片描述

因此,我们首先要做的,就是要正确打印出命令行提示符,并等待接收用户输入的命令。

注:

命令行提示符的基本格式为:[用户名@主机名 当前路径]&

  • 需要注意,如果当前用户为root 用户,那么&就应该变为#

那么,我们该如何获取我们需要的有用户名、主机名和路径信息呢?答案便是通过环境变量来获取

  • 环境变量USER记录了当前的用户信息
  • 环境变量HOSTNAME记录了当前的主机信息
  • 环境变量PWD记录了当前的路径信息

可以利用系统调用getenv()来获取对应的信息,并进行打印

等待并接受用户的输入这一操作十分简单,定义一个字符数组,并用函数fgets()进行接收即可。

这样,我们就实现了第一部分的功能:

//形参out为一个输出型参数,用于接收用户输入的命令
void interact(char* out)
{printf("[%s@%s %s]$ ", getenv("USER"), getenv("HOSTNAME"), getenv("PWD"));fgets(out, SIZE, stdin);out[strlen(out) - 1] = '\0';	//fgets()会将用户输入的换行符读入,因此要将这个符号去除
}

2. 分割命令 split()

在进程替换一节中我们提到,如果要将当前的进程替换为另一个程序,那么就需要使用exec系列函数来进行进程程序替换:

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 命令参数要么以参数列表的形式arg, ...传入,要么以字符串数组argv的方式传入

  • 但是我们在第一步interact()的过程中只接受了用户的一长串命令,这并不能直接作为参数传入程序替换函数中

  • 因此,我们就需要对之前输入的字符串以空格‘ ’为分隔符进行分割

如何分割?——可以用库函数strtok解决

char * strtok ( char * str, const char * delimiters );
  • delimiters分割符
  • 返回值即为被分割的字符串,分割结束返回NULL
  • 关于参数str,当要对用一个字符串多次调用时:
    • 第一次调用时,即为要被分割字符串str
    • 之后的所有调用,参数str都为NULL

如此,我们便可以实现功能分割功能了:

//参数command为用户输入的命令
//参数out为输出型参数,用于存储被分割的字符串集合
void split(char* command, char** out)
{int i = 0;out[i++] = strtok(command, " ");while (out[i++] = strtok(NULL, " "));
}

3. 执行命令

获得了正确的命令参数后,我们就可以开始程序替换了。

但是应该注意,如果程序替换成功,那么原程序之后的所有代码便都不会再执行了。

因此,为了确保shell能够一直处理用户输入的命令,我们应该创建一个子进程来进行进程程序替换

我们可以很容易的写出这样的代码:

//参数argv即为存储命令字符串的数组
void execute(char** argv)
{//创建子进程pid_t pid = fork();if (pid == 0){//子进程进行进程程序替换execvp(argv[0], argv);exit(1);}//子进程退出后父进程进行等待,并获取子进程的退出码int status;pid_t rid = waitpid(pid, &status, 0);EXIT = WEXITSTATUS(status);
}

我们再对上面两部分代码进行整合,就可以得到我们shell的简单版本了:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>#define SIZE 1024
#define ARGC 64int EXIT = 0;	//进程退出码void interact(char* out)
{printf("[%s@%s %s]$ ", getenv("USER"), getenv("HOSTNAME"), getenv("PWD"));fgets(out, SIZE, stdin);out[strlen(out) - 1] = '\0';
}void split(char* command, char** out)
{int i = 0;out[i++] = strtok(command, " ");while (out[i++] = strtok(NULL, " "));
}void execute(char** argv)
{pid_t pid = fork();if (pid == 0){execvp(argv[0], argv);exit(1);}int status;pid_t rid = waitpid(pid, &status, 0);EXIT = WEXITSTATUS(status);
}int main()
{while(1){//获取命令行参数char command[SIZE] = {0};interact(command);if (strlen(command) == 0)continue;//将命令行拆分成多个字符串char* argv[ARGC];split(command, argv);execute(argv);}return 0;
}

我么可以执行来看看:

在这里插入图片描述

可以发现我们执行catlsclear这些命令的时候没有出现问题,但是当我们执行cdechoexport这些命令的时候,却得不到正确的结果。这是为什么?

  • 应该清楚,我们是用子进程进行的进程替换,子进程执行完后便会退出终止。
  • 因此,子进程的改变不会影响到父进程,即不会影响到shell进程
  • 例如我们使用cd命令修改当前路径,我们修改的只是子进程的路径,而其父进程shell并未受任何影响
  • 同样,对于export,我们只是对子进程添加了环境变量,父进程的环境变量同样不会改变

所以,当遇到类似cd这种命令时,我们要对其进行特殊处理

3.1 执行内建命令

在Linux中,诸如echocdexport这样的命令我们称其为内建命令

我们可以利用枚举的方法来对内建命令进行处理

//参数argv即为命令字符串集合
//返回值如果为0,说明不是内建命令;如果是1,说明是内建命令
int buildCommand(char** argv)
{int ret = 0;//处理“cd”if (strcmp(argv[0], "cd") == 0){ret = 1;char* path = argv[1];	//命令cd后面跟的就是新的路径char put[SIZE];char absolutePath[SIZE];if (path == NULL)path = getenv("HOME");chdir(path);getcwd(absolutePath, SIZE);	//将新路径存入字符数组absolutePathsnprintf(put, SIZE, "%s%s", "PWD=", absolutePath);	//修改环境变量PWDputenv(put);}//处理”export“else if (strcmp(argv[0], "export") == 0){ret = 1;char env[SIZE];if (argv[1]){strcpy(env, argv[1]);putenv(env);}/*一定不能直接写成:putenv(argv[1]);否则当输入新的命令时,argv[1]的值就会改变,环境变量也会跟着变*/}//处理“echo”else if (strcmp(argv[0], "echo") == 0){ret = 1;if (argv[1] == NULL)printf("\n");else{  if (argv[1][0] == '$'){//"echo $?"即输出最近一个进程的退出码if (argv[1][1] == '?'){printf("%d\n", EXIT);EXIT = 0;}//否则输出对应的环境变量else {char* env = getenv(argv[1] + 1);if (env == NULL)printf("\n");else printf("%s\n", env);}} //否则为向屏幕输出字符串else printf("%s\n", argv[1]);                    }}return ret;
}

3.2 执行非内建命令

利用buildCommand()的返回值

  • 如果返回值为0,那么就说明该命令为非内建命令,开始创建子进程进行进程替换
  • 如果返回值为1,那么就说明该命令为内建命令,已经经过处理,直接等待下一条命令的输入即可
//处理内建命令
int ret = buildCommand(argv);//执行命令
if (!ret)execute(argv);

4. 实现代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>#define SIZE 1024
#define ARGC 64int EXIT = 0;void interact(char* out)
{printf("[%s@%s %s]$ ", getenv("USER"), getenv("HOSTNAME"), getenv("PWD"));fgets(out, SIZE, stdin);out[strlen(out) - 1] = '\0';
}void split(char* command, char** out)
{int i = 0;out[i++] = strtok(command, " ");while (out[i++] = strtok(NULL, " "));
}int buildCommand(char** argv)
{int ret = 0;if (strcmp(argv[0], "cd") == 0){ret = 1;char* path = argv[1];char put[SIZE];char absolutePath[SIZE];if (path == NULL)path = getenv("HOME");chdir(path);getcwd(absolutePath, SIZE);snprintf(put, SIZE, "%s%s", "PWD=", absolutePath);putenv(put);}else if (strcmp(argv[0], "export") == 0){ret = 1;char env[SIZE];if (argv[1]){strcpy(env, argv[1]);putenv(env);}}else if (strcmp(argv[0], "echo") == 0){ret = 1;if (argv[1] == NULL)printf("\n");else{  if (argv[1][0] == '$'){if (argv[1][1] == '?'){printf("%d\n", EXIT);EXIT = 0;}else {char* env = getenv(argv[1] + 1);if (env == NULL)printf("\n");else printf("%s\n", env);}} else printf("%s\n", argv[1]);                    }}return ret;
}void execute(char** argv)
{pid_t pid = fork();if (pid == 0){execvp(argv[0], argv);exit(1);}int status;pid_t rid = waitpid(pid, &status, 0);EXIT = WEXITSTATUS(status);
}int main()
{while(1){//获取命令行参数char command[SIZE] = {0};interact(command);if (strlen(command) == 0)continue;//将命令行拆分成多个字符串char* argv[ARGC];split(command, argv);//处理内建命令int ret = buildCommand(argv);//执行命令if (!ret)execute(argv);}return 0;
}

本篇完
如果错误敬请指正

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

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

相关文章

Spring - 基本用法参考

Spring 官方文档 Spring容器启动流程&#xff08;源码解读&#xff09; BeanFactoryPostProcessor vs BeanPostProcessor vs BeanDefinitionRegistryPostProcessor&#xff1a; From java doc&#xff1a; BeanFactoryPostProcessor may interact with and modify bean defin…

openGaussdb5.0单点企业版部署_Centos7_x86

本文档环境&#xff1a;CentOS7.9 x86_64 4G1C40G python2.7.5 交互式初始化环境方式 介绍 openGauss是一款开源关系型数据库管理系统&#xff0c;采用木兰宽松许可证v2发行。openGauss内核深度融合华为在数据库领域多年的经验&#xff0c;结合企业级场景需求&#xff0c;持续…

群晖安装Drive Server与Office实现团队固定公网地址远程办公

文章目录 本教程解决的问题是&#xff1a;1. 本地环境配置2. 制作本地分享链接3. 制作公网访问链接4. 公网ip地址访问您的分享相册5. 制作固定公网访问链接 本教程解决的问题是&#xff1a; 1.Word&#xff0c;PPT&#xff0c;Excel等重要文件存在本地环境&#xff0c;如何在编…

基于Prompt Learning的信息抽取

PTR: Prompt Tuning with Rules for Text Classification 清华&#xff1b;liuzhiyuan&#xff1b;通过规则制定subpromptRelation Extraction as Open-book Examination: Retrieval-enhanced Prompt Tuning Relation Extraction as Open-book Examination: Retrieval-enhance…

防御保护(2-6)

第二天 防火墙 防火墙的主要职责---控制和防护---安全策略---翻过去可以根据安全策略来抓取流量之后做出对应的动作。 防火墙分类 按物理特性划分&#xff1a;软件防火墙、硬件防火墙 按性能划分&#xff1a;百兆级防火墙、千兆级防火墙 按防火墙结构划分&#xff1a;单一主机…

为何PostgreSQL性能优于MySQL?

为何PostgreSQL性能优于MySQL? 在数据库选择过程中&#xff0c;性能是一个至关重要的考虑因素。PostgreSQL和MySQL是两个广泛使用的关系型数据库管理系统&#xff0c;它们在性能方面有着不同的特点。本文将深入探讨为何PostgreSQL在某些方面表现优于MySQL&#xff0c;并解释其…

Kotlin基础——高阶函数和内联函数

高阶函数 高阶函数以另一个函数作为参数或者返回值&#xff0c;其可用Lambda或函数引用表示 函数类型 下面将Lambda存储在sum变量中&#xff0c;其是函数类型 val sum { x: Int, y: Int -> x y }完整的函数类型为(para1,prar2…) -> returnValue val a: Int 0 va…

座位预约|座位预约小程序|基于微信小程序的图书馆自习室座位预约管理系统设计与实现(源码+数据库+文档)

座位预约小程序目录 目录 基于微信小程序的图书馆自习室座位预约管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员服务端功能模块 2、学生微信端功能模块 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 …

js实现动漫拼图2.0版

比较与1.0版&#xff0c;2.0版就更像与华容道类似的拼图游戏&#xff0c;从头到尾都只能控制白色块移动&#xff0c;而且打乱拼图和求助的实现与1.0都不相同 文章目录 1 实现效果2 实现思路2.1 打乱拼图2.2 求助功能2.3 判赢 3 代码实现 js实现动漫拼图1.0版 https://blog.csdn…

网络协议与攻击模拟_10DHCP攻击与DHCP欺骗

一、DHCP的报文格式 Message type&#xff1a;消息类型&#xff08;1表示请求&#xff0c;2表示响应&#xff09;Hardware type&#xff1a;硬件类型Hardware address length&#xff1a;硬件地址长度Hops&#xff1a;DHCP报文经过中继的数目。Transaction ID&#xff1a;事务…

MySQL中使用percona-xtrabackup工具 三种备份及恢复 (超详细教程)

CSDN 成就一亿技术人&#xff01; 今天讲讲再MySQL中使用percona-xtrabackup这个开源工具来实现在线备份。 CSDN 成就一亿技术人&#xff01; 目录 介绍percona-xtrabackup 安装Percona 完整备份 备份流程 恢复流程 1.模拟文件损坏 2.滚回日志 3.恢复数据目录 4.授权…

C语言指针进阶(1)(超详细)

前言&#xff1a; 指针其实就是地址&#xff0c;而凡是存储在内存中的值都会有属于自己的地址&#xff0c;指针指向地址&#xff0c;这样我们就能通过指针间接操作变量。我们在指针初阶中介绍了指针的基本概念&#xff1a;如指针大小、野指针问题、指针间的关系运算等&#xff…