[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.mzph.cn/news/823084.shtml

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

相关文章

数据加密、文档加密为什么都选择安企神软件

数据加密、文档加密为什么都选择安企神软件 免费试用安企神 在数据加密和文件加密领域&#xff0c;有众多优秀的软件&#xff0c;他们功能各异、价格不同、效果也大相径庭&#xff0c;经过对比使用、用户口碑和技术网站评判&#xff0c;安企神在各方面都稳坐第一把交易。其原…

新闻媒体行业邮件推广:精准推送,创造价值

在当今信息爆炸的时代&#xff0c;新闻行业如何在竞争激烈的市场中脱颖而出&#xff0c;吸引读者的目光&#xff0c;成为了每个新闻机构都需要认真思考的问题。许可式邮件营销成为了一种强大的工具&#xff0c;不仅能够向订阅者发送新闻期刊&#xff0c;还能够向广告商发送宣传…

【基础物理实验】【AFM虚拟实验】基于AFM的物质表面微观结构及力学性质表征仿真实验(下)【北京航空航天大学】

本次实验&#xff08;上&#xff09;见博客&#xff1a;【基础物理实验】【AFM虚拟实验】基于AFM的物质表面微观结构及力学性质表征仿真实验&#xff08;上&#xff09;【北京航空航天大学】 本次实验&#xff08;中&#xff09;见博客&#xff1a;【基础物理实验】【AFM虚拟实…

LLamaSharp加载llama.cpp转化好的模型

新建.net8控制台项目 安装依赖包 LLamaSharp和LLamaSharp.Backend.Cpu 准备好转化好的模型 没有的话参考这篇文章https://blog.csdn.net/qq_36437991/article/details/137248622 编写代码 using LLama; using LLama.Common; using LLama.Native;namespace llamasharpstu…

N皇后问题(DFS解决)

文章目录 一、题目分析二、对角线判断&#xff08;分两种&#xff09;三、代码演示 先赞后看&#xff0c;养成习惯&#xff01;&#xff01;&#xff01;^ _ ^<3 ❤️ ❤️ ❤️ 码字不易&#xff0c;大家的支持就是我坚持下去的动力。点赞后不要忘了关注我哦&#xff01; 一…

全球7大指纹浏览器排行榜:哪个最适合你?

在数字时代&#xff0c;我们每一次上网都会留下独特的数字足迹&#xff0c;被称为“浏览器指纹”。为了保护这些私人信息不被滥用&#xff0c;指纹浏览器成为了一个重要工具。但是&#xff0c;并非所有的指纹浏览器都是一样的&#xff0c;它们各有特点&#xff0c;适用于不同的…

数字乡村创新实践探索农业现代化路径:科技赋能农业产业升级、提升乡村治理效能与农民幸福感

随着信息技术的快速发展和数字化时代的到来&#xff0c;数字乡村建设正成为推动农业现代化、提升农业产业竞争力、优化乡村治理以及提高农民幸福感的重要途径。本文将围绕数字乡村创新实践&#xff0c;探讨其在农业现代化路径中的积极作用&#xff0c;以及如何通过科技赋能实现…

28. 找出字符串中第一个匹配项的下标(KMP)

class Solution {public int[] getNext(int[] next,String s){//j有两层含义&#xff1a;&#xff08;1&#xff09;最长公共前后缀的长度&#xff08;2&#xff09;前缀的末尾&#xff0c;是即将匹配的那个位置int j 0;//i含义&#xff1a;后缀的末尾&#xff0c;是即将匹配的…

Python疑难杂症(20)---介绍Python的pandas模块中将数据导入内存和导出数据的方法,以及一些参数的用法。

Python的pandas模块中数据框这种数据类型&#xff0c;可以通过文件导入函数&#xff0c;将磁盘上的csv、execl等类型的文件装入内存&#xff0c;并生成数据框的格式&#xff0c;以方便后续使用pandas的专有方法进行处理。 6、DataFrame数据输导入导出方法 Pandas常用的读取数…

安装ps提示MSVCP140.dll丢失怎么办,推荐几种有效的解决方法

在成功完成Adobe Photoshop&#xff08;简称PS&#xff09;软件的安装过程之后&#xff0c;当用户试图启动并运行该程序时&#xff0c;系统却弹出了一个令人困扰的错误提示信息&#xff0c;明确指出&#xff1a;“无法找到MSVCP140.dll”这一关键文件。这意味着尽管PS软件已经成…

redmibook 14 2020 安装 ubuntu

1. 参考博客 # Ubuntu20.10系统安装 -- 小米redmibook pro14 https://zhuanlan.zhihu.com/p/616543561# ubuntu18.04 wifi 问题 https://blog.csdn.net/u012748494/article/details/105421656/# 笔记本电脑安装了Ubuntu系统设置关盖/合盖不挂起/不睡眠 https://blog.csdn.net/…

权威Scrum敏捷开发企业级实训/敏捷开发培训课程

课程简介 Scrum是目前运用最为广泛的敏捷开发方法&#xff0c;是一个轻量级的项目管理和产品研发管理框架。 这是一个两天的实训课程&#xff0c;面向研发管理者、项目经理、产品经理、研发团队等&#xff0c;旨在帮助学员全面系统地学习Scrum和敏捷开发, 帮助企业快速启动敏…

【机器学习】科学库使用第5篇:Matplotlib,学习目标【附代码文档】

机器学习&#xff08;科学计算库&#xff09;完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;机器学习&#xff08;常用科学计算库的使用&#xff09;基础定位、目标&#xff0c;机器学习概述定位,目标,学习目标,学习目标,1 人工智能应用场景,2 人工智能小…

【网络设备巡检命令】--思科、华为、H3C、锐捷

【网络设备巡检命令】--思科、华为、H3C、锐捷 一、思科二、华为三、H3C四、锐捷 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 一、思科 1、查看系统信息&#xff1a; show version2、查看时间&#xff1a; show clock3、查看序列号&a…

Nginx内存池相关源码剖析(三)小块内存分配逻辑

在Nginx中&#xff0c;小块内存通常指的是那些大小相对较小、分配和释放频率较高的内存块。这些内存块由于数量众多、管理复杂&#xff0c;因此需要使用一种高效的内存管理机制来减少内存管理的开销和内存碎片的产生。 Nginx内存池通过一种预分配和复用的方式来管理小块内存。当…

觉飞、希亦、Daily neaty内衣洗衣机好用吗?爆款产品性能全面测评!

近几年来小家电产品中&#xff0c;内衣洗衣机的讨论热度无疑是最大的&#xff0c;功能多、操作方便&#xff0c;用内衣洗衣机来清洗内衣裤会更加卫生和安全&#xff0c;能满足了消费者的多种需求。不过尽管市面上的内衣洗衣机品牌很多、挑选空间大&#xff0c;也不是所有产品都…

RT-thread-线程间通讯3-事件集

事件集 事件集也是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。 一个线程和多个事件的关系可设置为: 其中任意一个事件唤醒 线程,或几个事件都到达后唤醒线程,多个事件集合可以用一个32bit无符号整型变量来表示,…

4.8-4.12算法刷题笔记

刷题 堆1. 堆排序2. 模拟堆 哈希表3. 模拟散列表4. 字符串哈希 DFS5. 排列数字6. n-皇后问题 2. BFS&#xff08;队列&#xff09;7. 字母迷宫8. 滑动谜题 3. 树与图的dfs9. 树的重心 4. 树与图的bfs(最短路)10. 图中点的层次( 无权最短路 ) 5. 拓扑排序11. 课程表 6. 朴素dijk…

docker (CentOS,ubuntu)安装及常用命令

Docker和虚拟机一样&#xff0c;都拥有环境隔离的能力&#xff0c;但它比虚拟机更加轻量级&#xff0c;可以使资源更大化地得到应用 Client&#xff08;Docker客户端&#xff09;&#xff1a;是Docker的用户界面&#xff0c;可以接受用户命令&#xff08;docker build&#xff…

记录Python的pandas库详解

如何生成一个pd import pandas as pd df pd.DataFrame([[1,2,3],[4,5,6]],index[A,B],columns[C1,C2,C3])df ---------------------------------------------------------------------------C1 C2 C3 A 1 2 3 B 4 5 6df.T -------------------------------------------------…