[Linux - C] 自主Shell

[Linux - C] 自主Shell

  • [Linux - C语言] 自主Shell
    • 逻辑策划 main()
    • 打印命令行 void MakeCommandLineAndPrint()
        • 用户名 USER
        • 主机名 HOSTNAME
        • 当前目录 PWD
        • SkipPath 切割目录
        • 打印命令行
    • 获取用户字符串 int GetUserCommand()
    • 检查重定向 void CheckRedir()
    • 切割字符 void SplitCommand()
    • 内建命令 bool CheckBuildin()
        • Cd
    • 执行命令 void ExecuteCommand()
    • 源代码
        • 结语

[Linux - C语言] 自主Shell

逻辑策划 main()

先打印命令行,然后再获取用户字符串,并且判断字符串是否有效,在进行重定向检查,然后再切割字符串。将整行的命令切割为命令与命令选项,在判断是否为内键命令,如果是内建命令则不需要利用子进程替换,如果不是内建命令则利用子进程替换执行命令。
main函数代码如下:

int main()
{while(1){//命令行MakeCommandLineAndPrint();//获取用户字符串int n = GetUserCommand();if(n<=0) continue;//检查重定向CheckRedir();//切割字符SplitCommand();//内建命令n = CheckBuildin();if(n) continue;//执行命令ExecuteCommand();filename = NULL;}return 0;
}

打印命令行 void MakeCommandLineAndPrint()

在Linux系统中的命令行中,包括了用户名USER,主机名HOSTNAME,当前目录PWD,还有身份标识符,如下图所示:
在这里插入图片描述
下面就让我们来一一实现吧!

用户名 USER

获取用户名,我们需要从环境变量中获取USER,这里需要使用char* getenv(const char* name)函数,参数name代表我们要查询的变量,查询后,如果成功找到了返回该环境变量,否则返回NULL
代码参考如下:

const char* GetUserName()
{const char* UserName = getenv("USER");if(UserName == NULL) return "None";return UserName;
}
主机名 HOSTNAME

同理。

const char* GetHostName()
{const char* name = getenv("HOSTNAME");if(name==NULL) return "None";return name;
}
当前目录 PWD
const char* GetCwd()
{const char* cwd = getenv("PWD");if(cwd==NULL) return "None";return cwd;
}
SkipPath 切割目录

因为我们PWD获取的环境变量,是由用户的家目录到当前目录的绝对地址,而命令行中只是当前目录,所以我们需要对目录进行切割。
因为代码量较小,所以我们利用宏函数,用do - while结构对函数内部进行封装,利用尾指针向右移的方法切割。

#define SkipPath(p) do{ p+=strlen(p)-1,while(*p != '/') p++; }while(0)

例如:”/home/Ang/Test“ 切割后为 “/Test”

打印命令行

利用前面的函数再将其组合即可。
需要注意的是用户标识符,在bash进程的命令行中 ‘$’ 代表普通用户,'#'代表超级用户

我们这里为了和bash的命令行区分,我们就用 ‘>’ 代表普通用户,'->'代表超级用户
如果返回的name,为root就表示为超级用户,所以在这进行判断即可。

还需要注意的是如果我们当前位置为根目录,即honstname = "/"的时候,
如果直接使用cwd+1,就没有字符可打印了,而在根目录下命令行的此时这个位置为"/",所以此时直接打印"/"

void MakeCommandLineAndPrint()
{const char* name = GetUsrName();const char* hostname = GetHostName();const char* cwd = GetCwd();SkipPath(cwd);const char* ManageSign = "->";const char* CommonSign = ">";printf("[%s@%s %s]%s ",name,hostname,cwd[1]=='\0'?"/":cwd+1,!strcmp("root",name)?ManageSign:CommonSign);fflush(stdout);
}

获取用户字符串 int GetUserCommand()

在定义这个函数之前,我们现在全局定义一个字符数组,用于储存我们输入的命令,如下:

#define NUM 500
char* command[NUM];

然后利用fgets函数获取用户的行输入,如果fgets读取失败返回NULL,所以这里要进行判断。
还需要注意的是'\n'也会被读取的command中,而此时的’\n’会影响后续的命令执行,所以要将尾部的'\n'改为'\0',再返回有效地输入个数。
而在main函数中根据读取的状态来确定是否继续执行下一步。
代码如下:

int GetUserCommand()
{char* s = fgets(command,sizeof(command),stdin);if(s == NULL) return -1;int n = strlen(command);command[n - 1]='\0';return n - 1;
}

检查重定向 void CheckRedir()

重定向的本质:是在内核中改变文件描述符表特定下标的内容,如下:
在这里插入图片描述
在定义这个函数之前,我们现在全局定义几个常量来表示重定向的状态,再定义一个全局的字符串指针用来指向command中的目标文件,再定义一个int变量来表示当前命令重定向的状态,如下:

#define None_Redir 0 //没有重定向
#define In_Redir   1 //输入重定向
#define Out_Refir  2 //输出重定向
#define App_Redir  3 //追加重定向int redir_type = None_Redir;
char* filename = NULL;

在此之前,我们还需要写一个宏函数,功能是:当我们找到重定向符号后,filename指向后面的文件名称.

#define SkipSpace(cmd, pos) do{\while(1)\{\if(isspace(cmd[pos])) pos++;\else break;\}\
}while(0)

在实现了上述功能之后,这个函数的编写变得异常简单了。只需要再确认重定向符号后,filename指向正确的文件名,并且将redir_type变为正确的重定向状态即可,需要注意的是在检测到重定向符号后,要将其改为'\0',因为后边的内容已经不是指令了

void CheckRedir()
{int pos = 0;int n = strlen(command);while(pos < n){if(command[pos]=='>'){if(command[pos+1]=='>'){redir_type = App_Redir;command[pos++]='\0';pos++;SkipSpace(command,pos);filename = command + pos;}else {command[pos++]='\0';redir_type = Out_Redir;SkipSpace(command,pos);filename = command + pos;}}else if(command[pos]=='<'){command[pos++]='\0';redir_type = In_Redir;SkipSpace(command,pos);filename = command + pos;}else pos++;}
}

切割字符 void SplitCommand()

因为我们输入的一行指令中间是由空格所分开的,在切割字符的时候,利用strtok函数进行切割,所以我们需要定义一个全局变量表示只有一个空格的字符串。
我们在后续利用execvp进行程序替换,所以我们需要一个全局的可以存放指令的参数。

#define SEP " "
char* gArgv[NUM];

利用strtok进行切割即可。

void SplitCommand()
{int index = 0;gArgv[index++] = strtok(command, SEP);while(gArgv[index++] =strtok(NULL, SEP));
}

内建命令 bool CheckBuildin()

内建命令不需要子进程来执行,是由bash进程直接执行的命令,所以这一类的命令不能利用子进程替换,实现需要自己编写。

直接利用strcmp来判断是否为内建命令,如果是实现相应功能并返回1,表示为内建命令。
代码如下:

bool CheckBuildin()
{int yes = 0;if(strcmp("cd",gArgv[0]) == 0){yes = 1;Cd();}else if(!strcmp("echo",gArgv[0])&&!strcmp(gArgv[1],"$?")){yes=1;printf("%d",lastcode);lastcode=0;}return yes;
}
Cd

利用chird函数来变换当前目录,再定义一个temp来存放当前目录(此时目录已经变了),再利用snprintf重写cwd,使其符合putenv的参数标准,再让putenv感谢环境变量即可。
代码如下:

void Cd()
{const char* path = gArgv[1];if(path == NULL) path = GetHome();chdir(path);char temp[SIZE*2];getcwd(temp, sizeof(temp));snprintf(cwd,sizeof(cwd),"PWD=%s",temp);putenv(cwd);
}

执行命令 void ExecuteCommand()

利用fork创建子进程,利用返回值id判断进程,如果id<0,进程就可以直接去死了,如果id=0说明是子进程,如果id>0,说明是父进程。

在子进程中,我们先用filename判断命令是否使用了重定向,如果使用了则打开相关文件,利用dup2函数,更改相关文件操作符下标,然后再执行程序替换即可。如果替换失败,利用exit退出并报告错误。

在父进程中,我们需要用waitpid等待子进程。等待子进程结束后,利用waitpid的返回值判断等待是否成功,如果返回值为负数,说明waitpid调用失败,可能是子进程不存在。再利用输出型参数status。判断子进程的运行状态,用lastcode进行更新 ,如果lastcode不为零,说明子进程有异常,打印错误即可。

void ExecuteCommand()
{pid_t id = fork();if(id < 0) Die();if(id == 0){if(filename != NULL){if(redir_type == In_Redir){int fd = open(filename,O_RDONLY);dup2(fd,0);}else if(redir_type == Out_Redir){int fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fd,1);}else if(redir_type == App_Redir){int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);dup2(fd,1);}else {}}execvp(gArgv[0],gArgv);exit(errno);}else {int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0){printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);}}}
}

ps: lastcode是一个全局变量,用来记录子进程的退出状态,配合echo内建命令。

源代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdbool.h>
#include <ctype.h>#define NUM 1000
#define SIZE 64
#define SkipPath(p) do{ p+=strlen(p)-1; while(*p!='/') p--;  }while(0)
#define SkipSpace(cmd, pos) do {\while(1)\{\if(isspace(cmd[pos]))\pos++;\else break;\}\
}while(0)#define None_Redir 0
#define In_Redir   1
#define Out_Redir  2
#define App_Redir  3char cwd[SIZE*2];
char* gArgv[NUM];
char command[NUM];
char* filename = NULL; 
const char* SEP =" ";int redir_type = 0;
int lastcode = 0;const char* GetCwd()
{const char* cwd = getenv("PWD");if(cwd==NULL) return "None";return cwd;
}const char* GetUsrName()
{const char* name = getenv("USER");if(name==NULL) return "None";return name;
}const char* GetHostName()
{const char* name = getenv("HOSTNAME");if(name==NULL) return "None";return name;
}void MakeCommandLineAndPrint()
{const char* name = GetUsrName();const char* hostname = GetHostName();const char* cwd = GetCwd();SkipPath(cwd);const char* ManageSign = "->";const char* CommonSign = ">";printf("[%s@%s %s]%s ",name,hostname,cwd[1]=='\0'?"/":cwd+1,!strcmp("root",name)?ManageSign:CommonSign);fflush(stdout);
}int GetUserCommand()
{char* s = fgets(command,sizeof(command),stdin);if(s == NULL) return -1;int n = strlen(command);command[n - 1]='\0';return n - 1;
}void SplitCommand()
{int index = 0;gArgv[index++] = strtok(command,SEP);while(gArgv[index++] = strtok(NULL,SEP));
}void Die()
{exit(1);
}void ExecuteCommand()
{pid_t id = fork();if(id < 0) Die();if(id == 0){if(filename != NULL){if(redir_type == In_Redir){int fd = open(filename,O_RDONLY);dup2(fd,0);}else if(redir_type == Out_Redir){int fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fd,1);}else if(redir_type == App_Redir){int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);dup2(fd,1);}else {}}execvp(gArgv[0],gArgv);exit(errno);}else {int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0){printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);}}}
}const char* GetHome()
{const char* home = getenv("HOME");if(home== NULL) return "/";return home;
}void Cd()
{const char* path = gArgv[1];if(path == NULL) path = GetHome();chdir(path);char temp[SIZE*2];getcwd(temp, sizeof(temp));snprintf(cwd,sizeof(cwd),"PWD=%s",temp);putenv(cwd);}bool CheckBuildin()
{int yes = 0;if(strcmp("cd",gArgv[0]) == 0){yes = 1;Cd();}else if(!strcmp("echo",gArgv[0])&&!strcmp(gArgv[1],"$?")){yes=1;printf("%d",lastcode);lastcode=0;}return yes;
}void CheckRedir()
{int pos = 0;int n = strlen(command);while(pos < n){if(command[pos]=='>'){if(command[pos+1]=='>'){redir_type = App_Redir;command[pos++]='\0';pos++;SkipSpace(command,pos);filename = command + pos;}else {command[pos++]='\0';redir_type = Out_Redir;SkipSpace(command,pos);filename = command + pos;}}else if(command[pos]=='<'){command[pos++]='\0';redir_type = In_Redir;SkipSpace(command,pos);filename = command + pos;}else pos++;}
}int main()
{while(1){//命令行MakeCommandLineAndPrint();//获取用户字符串int n = GetUserCommand();if(n<=0) continue;//检查重定向CheckRedir();//切割字符SplitCommand();//内建命令n = CheckBuildin();if(n) continue;//执行命令ExecuteCommand();filename = NULL;}return 0;
}
结语

以上就是本期的全部内容了,喜欢就多多关注吧!!!
下期会继续完善的捏!
请添加图片描述

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

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

相关文章

基于Springboot的餐厅点餐系统

基于SpringbootVue的餐厅点餐系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 首页展示 菜品详情页 菜品信息 个人中心 后台管理 菜品信息管理 用户管理 菜…

小阳同学嵌入式学习日记-QFile读写文件

一、QFile简介 在Qt中&#xff0c;QFile是一个用于文件I/O操作的类。它提供了一种方便的方式来读取和写入文件内容&#xff0c;以及获取有关文件的信息。 QFile类提供了许多函数&#xff0c;用于打开、关闭、读取和写入文件。一些常用的QFile函数包括&#xff1a; open(): 打开…

潍微科技-水务信息管理平台 ChangePwd SQL注入漏洞复现(CNVD-2024-14945)

0x01 产品简介 水务信息管理平台主要帮助水务企业实现水质状态监测、管网运行监控、水厂安全保障、用水实时监控以及排放有效监管,确保居民安全稳定用水、环境有效保护,全面提升水务管理效率。由山东潍微科技股份有限公司研发,近年来,公司全力拓展提升水务、水利信息化业务…

小型燃气站3D可视化:打造安全高效的燃气新时代

随着科技的不断进步&#xff0c;越来越多的行业开始融入3D可视化技术&#xff0c;燃气行业也不例外。 小型燃气站作为城市燃气供应的重要节点&#xff0c;其安全性和运行效率至关重要。传统的燃气站管理方式往往依赖于人工巡检和纸质记录&#xff0c;这种方式不仅效率低下&…

linux查看网络连接数

目录 netstat top netstat 1.netstat查看当前主机上网络连接信息&#xff0c;端口号&#xff0c;pid,程序名等等 #直接查看 netstat -anp #一般使用的时候&#xff0c;可能要筛选 #比如8080端口是否被占用 netstat -anp | grep 8080 #minio服务占用了那些端口 netstat -anp …

CDC类下的画线函数

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系将于24小时内删除 目录 1.实验原理(后续再补写) 2.实验步骤 3.运行代码 4.运行结果 1.实验原理(后续再补写) MoveTo(); LineTo(); 2.实验步骤 2.1在对话框中添加如下布局控件 2.2绑定…

空间滤波器和频率域滤波器的对应

说实话&#xff0c;4.7.4的前面的图4.37我没看懂在说什么&#xff0c;但是例4.15这个我倒是看明白了。中心思想是:本来空间滤波器计算的时候需要不停地滑动模版的&#xff0c;这里把滑动用x的值代替了,但是现在不搞滑动了&#xff0c;利用傅里叶变换和卷积公式&#xff0c;只要…

飞驰云联入选金融信创生态实验室「金融信创优秀解决方案」

近日&#xff0c;由中国人民银行领导、中国金融电子化集团有限公司牵头组建的金融信创生态实验室发布了第三期金融信创优秀解决方案&#xff0c;Ftrans飞驰云联“文件数据传输解决方案”成功入选&#xff01; 本次金融信创优秀解决方案遴选经方案征集、方案初审、专家评审等多环…

基于STC12C5A60S2系列1T 8051单片机的带字库液晶显示器LCD12864数据传输并行模式显示64行点x64列点字模的功能

基于STC12C5A60S2系列1T 8051单片机的带字库液晶显示器LCD12864数据传输并行模式显示64行点x64列点字模的应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示…

嵌入式第四天:(C语言入门)

目录 什么是数组&#xff1f; 数组&#xff1a; 数组的使用&#xff1a; 数组的初始化&#xff1a; 数组名&#xff1a; 数组案例&#xff1a; 一维数组的最大值&#xff1a; 一维数组的逆置&#xff1a; 数组和指针&#xff1a; 通过指针操作数组元素&#xff1a; …

嵌入式4-16

tftpd #include <myhead.h> #define SER_IP "192.168.125.243" //服务器IP地址 #define SER_PORT 69 //服务器端口号 #define CLI_IP "192.168.125.244" //客户端IP地址 #define CLI_PORT 8889 //客户端端…

C++项目 -- 负载均衡OJ(一)comm

C项目 – 负载均衡OJ&#xff08;一&#xff09;comm 文章目录 C项目 -- 负载均衡OJ&#xff08;一&#xff09;comm一、项目宏观结构1.项目功能2.项目结构 二、comm公共模块1.util.hpp2.log.hpp 一、项目宏观结构 1.项目功能 本项目的功能为一个在线的OJ&#xff0c;实现类似…

springCloudAlibaba集成sentinel实战(超详细)

一、Sentinel介绍 1. 什么是Sentinel Sentinel是阿里开源的项目&#xff0c;提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。 分布式系统的流量防卫兵&#xff1a; 随着微服务的普及&#xff0c;服务调用的稳定性变得越来越重要。Sentinel以“流…

L2-3 完全二叉树的层序遍历

完全二叉树的层序遍历 一个二叉树&#xff0c;如果每一个层的结点数都达到最大值&#xff0c;则这个二叉树就是完美二叉树。对于深度为 D 的&#xff0c;有 N 个结点的二叉树&#xff0c;若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点&#xff0c;这样的树就是完全…

vue.js入门

vue是一个渐进js框架 渐进式:按需引入Vue.js的部分功能,不用把整个框架都导入 1. 传统开发方式 用vue.global.js <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"…

毕设选51还是stm32?51太简单?

如果你更倾向于挑战和深入学习&#xff0c;STM32可能是更好的选择。如果你希望更专注于底层硬件原理&#xff0c;51可能更适合。我这里有一套嵌入式入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习嵌入式&#xff0c;不妨点个关注&#xff…

企业级网络安全:入侵防御实时阻止,守护您的业务安全

随着互联网技术的快速发展&#xff0c;企业级网络安全问题日益凸显。在这个数字化时代&#xff0c;企业的业务安全不仅关系到企业的形象和声誉&#xff0c;还直接影响到企业的生存和发展。因此&#xff0c;加强企业级网络安全&#xff0c;预防和抵御各种网络攻击已成为企业的重…

两数相加(leetcode)

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

【kafka】安装

也是第二次安装&#xff0c;蛮记录一下 1.安装kafka之前需要先完成zookeeper的安装 【zookeeper】安装 2. Apache Kafka官网下载 3. 解压完成后修改server.properties配置文件 修改日志文件存放路径 查看与zookeeper连接的端口是否和zookeeper服务所在的端口一致 &#xf…

基于51单片机的温度控制恒温箱设计—数码管显示

基于51单片机的温度控制恒温箱 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.DS18B20温度传感器测温&#xff1b; 2.数码管实时显示温度&#xff1b; 3.按键设置温度上下限阈值&am…