[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/804143.shtml

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

相关文章

JVM字节码与类的加载——类的加载过程详解

文章目录 1、概述2、加载(Loading)阶段2.1、加载完成的操作2.2、二进制流的获取方式2.3、类模型与Class实例的位置2.4、数组类的加载 3、链接(Linking)阶段3.1、链接阶段之验证(Verification)3.1.1、格式检查3.1.2、字节码的语义检查3.1.3、字节码验证3.1.4、符号引用验证 3.2、…

Harmony鸿蒙南向驱动开发-I3C

I3C&#xff08;Improved Inter Integrated Circuit&#xff09;总线是由MIPI Alliance开发的一种简单、低成本的双向二线制同步串行总线。 I3C是两线双向串行总线&#xff0c;针对多个传感器从设备进行了优化&#xff0c;并且一次只能由一个I3C主设备控制。相比于I2C&#xf…

langchain LCEL,prompt模块,outputparse输出模块

目录 基本代码 prompt模块 prompt模版控制长度 outputparse格式化输出 LangChain表达式语言&#xff0c;或者LCEL&#xff0c;是一种声明式的方式&#xff0c;可以轻松地将链条组合在一起 langchian 可以使用 通义千问&#xff0c;我们用通义千问&#xff0c;用法也要申请…

基于ros的相机内参标定过程

基于ros的相机内参标定过程 1. 安装还对应相机的驱动2. 启动相机节点发布主题3. 下载camera_calibartion4. 将红框的文件夹复制在自己的工作空间里边&#xff0c;编译5. 标定完成以后&#xff0c;生成内参参数文件camera.yaml。将文件放在对应的路径下&#xff0c;修改config文…

ArcGIS Server 10发布要素服务时遇到的数据库注册问题总结(一)

工作环境&#xff1a; Windows 7 64 位旗舰版 ArcGIS Server 10.1 ArcGIS Desktop 10.1 IIS 7.0 开始的时候以为10.1发布要素服务和10.0一样&#xff0c;需要安装ArcSDE&#xff0c;后来查阅资料发现不需要&#xff0c;数据库直连方式就可以了。 首先我来说一下发布要素服…

stm32开发之threadx+netxduo(tcp 服务端使用记录)

前言 本篇需要用到threadx之动态内存的实现记录 里面的动态内存分配管理代码.开发环境使用的stm32cubemxclion组合芯片使用的是stm32f407zgt6,网口使用的是lan8720&#xff0c;使用cubemx提供的lan8742也可以驱动&#xff0c;注意实际的网口与芯片的引脚 示例代码 tcp 服务端…

Excel文本内容抽取工具[Python]

#创作灵感# 一堆Excel文件&#xff0c;每个打开看太累了。写个脚本直接显示里面的内容多好。最好这些内容可以直接复制到剪切板&#xff0c;方便以后编辑修改。只需要将文件拖动到全屏置顶的文本框内&#xff0c;就能弹出Excel里的内容。支持一次选取多个文件。 开干&#xff…

计算机视觉——引导APSF和梯度自适应卷积增强夜间雾霾图像的可见性算法与模型部署(C++/python)

摘要 在夜间雾霾场景中&#xff0c;可见性经常受到低光照、强烈光晕、光散射以及多色光源等多种因素的影响而降低。现有的夜间除雾方法常常难以处理光晕或低光照条件&#xff0c;导致视觉效果过暗或光晕效应无法被有效抑制。本文通过抑制光晕和增强低光区域来提升单张夜间雾霾…

N1922A是德科技N1922A功率传感器

181/2461/8938产品概述&#xff1a; N192XA 传感器是首款通过将直流参考源和开关电路集成到功率传感器中来提供内部调零和校准的传感器。此功能消除了与使用外部校准源相关的多个连接&#xff0c;从而最大限度地减少了连接器磨损、测试时间和测量不确定性。 连接到 DUT 时进行…

InsectMamba:基于状态空间模型的害虫分类

InsectMamba&#xff1a;基于状态空间模型的害虫分类 摘要IntroductionRelated WorkImage ClassificationInsect Pest Classification PreliminariesInsectMambaOverall Architecture InsectMamba: Insect Pest Classification with State Space Model 摘要 害虫分类是农业技术…

Excel从零基础到高手【办公】

第1课 - 快速制作目录【上篇】第1课 - 快速制作目录【下篇】第2课 - 快速定位到工作表的天涯海角第3课 - 如何最大化显示工作表的界面第4课 - 给你的表格做个瘦身第5课 - 快速定位目标区域所在位置第6课 - 快速批量填充序号第7课 - 按自定义的序列排序第8课 - 快速删除空白行第…

C++数据结构与算法——贪心算法难题

C第二阶段——数据结构和算法&#xff0c;之前学过一点点数据结构&#xff0c;当时是基于Python来学习的&#xff0c;现在基于C查漏补缺&#xff0c;尤其是树的部分。这一部分计划一个月&#xff0c;主要利用代码随想录来学习&#xff0c;刷题使用力扣网站&#xff0c;不定时更…

计算机视觉异常检测——PatchCore面向全召回率的工业异常检测

1. 概述 异常检测问题在工业图像数据分析中扮演着至关重要的角色&#xff0c;其目的是从大量正常数据中识别出异常行为或模式。这一任务的挑战在于&#xff0c;正常数据的样本相对容易获取&#xff0c;而异常情况却因其稀有性和多样性而难以收集。为了解决这一问题&#xff0c…

跟TED演讲学英文:Why AI will spark exponential economic growth by Cathie Wood

TED英文文稿 文章目录 TED英文文稿Why AI will spark exponential economic growthIntroductionVocabularyTranscriptSummary Why AI will spark exponential economic growth Link: https://www.ted.com/talks/cathie_wood_why_ai_will_spark_exponential_economic_growth? …

家庭网络防御系统搭建-将NDR系统的zeek日志集成到securit yonion

在前面的文章中安装了zeek,这里&#xff0c;安装了securityonion&#xff0c;这里&#xff0c;本文讲述如何将zeek生成的日志发送到siem security onion之中。 所有日志集成的步骤分为如下几步&#xff1a; 日志收集配置日志发送接收日志解析配置日志展示配置 ZEEK日志收集配…

大型语言模型如何助力推荐系统:综述研究

论文地址&#xff1a;https://arxiv.org/pdf/2306.05817.pdf 这篇论文主要探讨了推荐系统&#xff08;RS&#xff09;如何从大型语言模型&#xff08;LLM&#xff09;中获益。论文首先指出&#xff0c;随着在线服务和网络应用的快速发展&#xff0c;推荐系统已成为缓解信息过载…

路由器如何端口映射到外网?

随着互联网的发展和普及&#xff0c;远程访问已经成为了现代社会的一个重要需求。在复杂的网络环境下&#xff0c;特别是涉及异地组网的情况下&#xff0c;实现远程访问变得更加困难。本文将介绍一种名为【天联】的组网产品&#xff0c;它可以解决复杂网络环境下的远程连接问题…

搜维尔科技:Patchwork 3D工业仿真实时渲染,将CAD 数据转换成真实感的3D模型以用于工业用途

Patchwork 3D工业仿真 实时渲染点击跳转官网 从实时渲染到真实照片 根据工作阶段所需的逼真度&#xff0c;您可以使用三个渲染引擎&#xff0c;从最快的&#xff08;OpenGL&#xff0c;交互式&#xff09;到最逼真的&#xff08;光线跟踪&#xff0c;Iray物理逼真&#xff09;…

vue中使用axios获取不到响应头Content-Disposition的解决办法

项目中&#xff0c;后端返回的文件流; 前端需要拿到响应头里的Content-Disposition字段的值&#xff0c;从中获取文件名 在控制台Headers中可以看到相关的字段和文件名&#xff0c;但是在axios里面却获取不到 如果想要让客户端访问到相关信息&#xff0c;服务器不仅要在head…