【Linux】第二个小程序--简易shell

请看上面的shell,其本质就是一个字符串,我们知道bash本质上就是一个进程,只不过命令行就是一个输出的字符串,

我们输入的命令“ls -a -l”实际上是我们在输入行输入的字符串,所以,如果我们想要做一个简易的shell的时候,首先要输出“[ghs@hecs-406886 myshell]$”这样的字符串,然后再接收并解析写入的命令字符串,然后一条命令就可以执行了。在“[ghs@hecs-406886 myshell]$”这样一行字符串中,ghs表示用户名,hecs-406886表示当前主机的主机名,myshell表示当前所处的路径,[ @ ]$为提示符。

所以,我们也需要构建一个类似的命令行,首先,创建一个myshell.c,

在main函数中,第一步我们需要自己输出一个命令行:命令行包括用户名、主机名、当前路径, 这些内容可以从环境变量中获取,

可以通过getenv函数获取环境变量内容,我们自定义三个函数来获取上面三个环境变量:

const char* getusername()
{
    const char* name = getenv("USER");
    if(name == NULL) return "None";
    return name;
}
const char* gethostname()
{
    const char* hostname = getenv("HOSTNAME");
    if(hostname == NULL) return "None";
    return hostname;
}
const char* getcwd()
{
    const char* cwd=getenv("PWD");
    if(cwd == NULL)return "None";
    return cwd;
}

先介绍一个函数snprintf:把指定参数按照特定格式写到指定长度的内存里,

然后综合上面三个函数,封装出MakeCommandLineAndPrint()函数,制作并打印命令行:

void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char* username = GetUserName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();
    snprintf(line,sizeof(line),"[%s@%s %s]>",username,hostname,cwd);
    printf("%s",line);
    fflush(stdout);
}

第二步就是获取用户命令字符串,输入的指令(“ls -l -a”)站在开发者的角度本质是一个字符串,我们想一下,可以用scanf获取指令吗?不可以!因为指令选项个数不定。其实,我们想按行来获取字符串,在C语言中,我们可以使用fgets函数,它可以按行从特定的文件流中获取指定内容,获取内容指向由指针s指向的缓冲区,

char *fgets(char *s, int size, FILE *stream);

定义usercommand数组用于存储字符串,然后封装GetUserCommand()函数用于获取用户命令字符串,

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

第三步就是命令行字符串分割,使用‘ ’(空格)作为分隔符,我们想要得到一个数组,这个数组叫做char *argv[],对于这样“ls -l -a”一个字符串,定义指针指向第一个‘l’,再定义另一个指针也指向第一个‘l’,往后遍历,遇到第一个空格把其置为‘\0’,把指向第一个‘l’的指针放到agrv数组的第一个位置,然后指针往后走,指向‘-’,再定义一个指针向后走,遇到第一个空格把其置为‘\0’,把指向第一个‘-’的指针放到agrv数组的第二个位置,依次类推。为了实现这个功能,我们使用strtok函数:

#define NUM 32
char* gArgv[NUM];
void SplitCommand(char command[],size_t n)
{
    gArgv[0] = strtok(command,SEP);
    int index = 1;
    while((gArgv[index++] = strtok(NULL,SEP)));//故意写成=,表示先赋值再判断,分割之后,strtok 
                                               //会返回NULL,刚好让gArgv最后一个元素是NULL, 
                                               //并且while判断结束
}

下一步就要执行命令,我们需要创建子进程去执行,在子进程中我们要选择使用哪一个程序替换函数,第一,由于上面分割出来的命令是不带路径的,我要执行的命令全是系统的默认路径,应该让它在环境变量中找,所以选择的函数一定带p,第二,由于我提供的是一个命令数组argv,所以一定要带v,所以,选择execvp函数,这个函数在失败时返回-1,同时会设置错误码errno。

pid_t id = fork();
if(id < 0) Die();
else if(id == 0)
{
    //child
    execvp(gArgv[0],gArgv);
    exit(errno);
}

但是,上面这个代码只能执行一次,为了能一直执行下去,我们创建一个循环:

int main()
{
    int quit = 0;
    while(!quit)
    {
         //1.我们需要自己输出一个命令行
         MakeCommandLineAndPrint();

         //2.获取用户命令字符串
         char usercommand[SIZE];
         int n = GetUserCommand(usercommand,sizeof(usercommand));
         if(n <= 0) return 1;

         //3.命令行字符串分割
         SplitCommand(usercommand,sizeof(usercommand));
         
         //n.执行命令
         ExecuteCommand();
    }
    return 0;
}

上面我们完成一个粗犷的shell,但是有一点:

我们无法使用cd ..完成路径回退,这是为什么呢?因为我们上面的程序是在子进程中进行的,cd ..是在子进程中进行的,是把子进程的路径进行了回退,但是和父进程无关,父进程没有回退,cd这样的命令应该让父进程进程回退,而不应该让子进程回退,因此,需要对这些内建命令进行单独处理,在执行命令之前,要检查命令是否是内建命令

//3.命令行字符串分割
SplitCommand(usercommand,sizeof(usercommand));
//4.检查命令是否是内建命令
n = CheckBuilding();
if(n) continue; 
void Cd()
{
   const char* path = gArgv[1];
   if(path == NULL) path = GetHome();
   //path一定存在
   chdir(path);
}

上面代码的意思是,如果“cd”,那么回退到用户家目录,否则把命令行里的路径更改为当前路径。 

int CheckBuilding()
{
    int yes = 0;
    const char* enter_cmd = gArgv[0];
    if(strcmp("cd",enter_cmd) == 0)
    {
        yes = 1;
        Cd();
    }

    return yes;
}

当时,运行上面代码后,发现命令行的当前路径提示一直不变,只有pwd里的路径才改变,导致这样的原因是没有对环境变量进行更新,需要导入环境变量,将当前的路径放到temp中,然后将cwd导入环境变量中。

char cwd[SIZE*2];
void Cd()
{
   const char* path = gArgv[1];
   if(path == NULL) path = GetHome();
   //path一定存在
   chdir(path);
   //刷新环境变量
   char temp[SIZE*2];
   getcwd(temp,sizeof(temp));
   snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
   putenv(cwd);
}

执行结果虽然正确了,但是我们只想让最后一个路径显示出来(和正常的shell一样),因此,我定义了一个宏函数:

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

这个宏将穿进去的路径字符串指向最后一个/,

最后的效果如上图。

但是还有一个问题,当我们回退到根目录后,命令行不显示路径了,需要做一下特殊处理:

此外,当进程退出时,我们也想用echo看一下退出码,由于echo也是一种内建命令,因此也需要在第四步特殊判断一下:

int CheckBuilding()
{
    int yes = 0;
    const char* enter_cmd = gArgv[0];
    if(strcmp("cd",enter_cmd) == 0)
    {
        yes = 1;
        Cd();
    }
    else if(strcmp(enter_cmd,"echo") == 0 && strcmp("$?",gArgv[1]) == 0)
    {
        yes = 1;
        printf("%d\n",lastcode);
        lastcode = 0;
    }

    return yes;
}

至此,简易的shell完成。

下面附上完整代码:

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>

#define ZERO '\0'
#define SIZE 512
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p += strlen(p)-1; while(*p != '/') p--; }while(0)

char cwd[SIZE*2];
char* gArgv[NUM];
int lastcode = 0;

const char* GetHome()
{
    const char* home = getenv("HOME");
    if(home == NULL) return "/";
    return home;
}

const char* GetUserName()
{
    const char* name = getenv("USER");
    if(name == NULL) return "None";
    return name;
}

const char* GetHostName()
{
    const char* hostname = getenv("HOSTNAME");
    if(hostname == NULL) return "None";
    return hostname;
}

//临时
const char* GetCwd()
{
    const char* cwd=getenv("PWD");
    if(cwd == NULL)return "None";
    return cwd;
}

void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char* username = GetUserName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();

    SkipPath(cwd);
    snprintf(line,sizeof(line),"[%s@%s %s]>",username,hostname,strlen(cwd)==1 ? "/":cwd+1);
    printf("%s",line);
    fflush(stdout);
}
int GetUserCommand(char command[],size_t n)
{

    char* s = fgets(command, n ,stdin);
    if(s == NULL) return -1;
    command[strlen(command)-1] = ZERO;
    return strlen(command);
}
void SplitCommand(char command[],size_t n)
{
    gArgv[0] = strtok(command,SEP);
    int index = 1;
    while((gArgv[index++] = strtok(NULL,SEP)));//故意写成=,表示先赋值再判断,分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL,并且while判断结束
}
void Die()
{
    exit(-1);
}

void ExecuteCommand()
{
    pid_t id = fork();
    if(id < 0) Die();
    else if(id == 0)
    {
        //child
        execvp(gArgv[0],gArgv);
        exit(errno);
    }
    else 
    {
        //father
        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);
        }
    }
}
void Cd()
{
   const char* path = gArgv[1];
   if(path == NULL) path = GetHome();
   //path一定存在
   chdir(path);
   //刷新环境变量
   char temp[SIZE*2];
   getcwd(temp,sizeof(temp));
   snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
   putenv(cwd);
}
int CheckBuilding()
{
    int yes = 0;
    const char* enter_cmd = gArgv[0];
    if(strcmp("cd",enter_cmd) == 0)
    {
        yes = 1;
        Cd();
    }
    else if(strcmp(enter_cmd,"echo") == 0 && strcmp("$?",gArgv[1]) == 0)
    {
        yes = 1;
        printf("%d\n",lastcode);
        lastcode = 0;
    }

    return yes;
}

int main()
{
    int quit = 0;
    while(!quit)
    {
         //1.我们需要自己输出一个命令行
         MakeCommandLineAndPrint();

         //2.获取用户命令字符串
         char usercommand[SIZE];
         int n = GetUserCommand(usercommand,sizeof(usercommand));
         if(n <= 0) return 1;

         //3.命令行字符串分割
         SplitCommand(usercommand,sizeof(usercommand));
         //4.检查命令是否是内建命令
         n = CheckBuilding();
         if(n) continue; 
         //5.执行命令
         ExecuteCommand();
    }
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

通用开发技能系列:SQL基础学习

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 通用开发技能系列 文章&#xff0c;主要对编程通用技能 SQL基础 进行学习 1.数据库简介 1.1.数据库中的一些名称 DataBase&#xff1a;数据库 程序员只负责怎么维护存取数据&#xff0c;不管数据库是什么 DBA…

c#程序报错引用无效解决办法之一:检查引用的文件路径

直接右键然后打开本地 打开这个.csproj文件&#xff0c;直接对着路径看看里面的路径对不对。 一般是很多人一起开发&#xff0c;然后这个文件路径被推送上来的问题

考研经验与科目学习建议

前言 24考研刚刚结束&#xff0c;成功上岸&#xff0c;回想起刚开始的时候的迷茫&#xff0c;加上因为迷茫而被卖书的坑的几百块钱。感慨万千&#xff0c;所以决定写下这篇文章。回想当时&#xff0c;因为笔者零基础&#xff0c;加上作为一名专升本的学生&#xff0c;惶恐因为…

第十四届省赛大学B组(C/C++)子串简写

原题链接&#xff1a;子串简写 程序猿圈子里正在流行一种很新的简写方法&#xff1a; 对于一个字符串&#xff0c;只保留首尾字符&#xff0c;将首尾字符之间的所有字符用这部分的长度代替。 例如 internationalization 简写成 i18n&#xff0c;Kubernetes 简写成 K8s&#…

目标检测——车牌数据集

一、重要性及意义 交通安全与管理&#xff1a;车牌检测和识别技术有助于交通管理部门快速、准确地获取车辆信息&#xff0c;从而更有效地进行交通监控和执法。例如&#xff0c;在违规停车、超速行驶等交通违法行为中&#xff0c;该技术可以帮助交警迅速锁定违规车辆&#xff0…

机器学习模型:决策树笔记

第一章&#xff1a;决策树原理 1-决策树算法概述_哔哩哔哩_bilibili 根节点的选择应该用哪个特征&#xff1f;接下来选什么&#xff1f;如何切分&#xff1f; 决策树判断顺序比较重要。可以使用信息增益、信息增益率、 在划分数据集前后信息发生的变化称为信息增益&#xff0c…

MySQL 主从复制架构搭建及其原理

前言 系统的性能瓶颈一般出现在数据库上&#xff0c;以 mysql 为例&#xff0c;如果存在高并发的写请求&#xff0c;势必会有锁表&#xff0c;锁数据行的情况发生&#xff0c;这时候如果有读请求刚好访问到被锁的数据&#xff0c;那么读请求会阻塞&#xff0c;直到写请求处理完…

【C++ STL迭代器】iterator

文章目录 【 1. 迭代器的属性 】【 2. 不同容器支持的迭代器 】【 3. 迭代器的定义方式 】【 4. 实例 】4.1 定义方式&#xff1a;正向迭代器和反向迭代器4.2 迭代器属性&#xff1a;前向迭代、双向迭代、随机迭代4.2 迭代器的遍历方法4.3 auto关键字 自动指定迭代器定义类型 背…

C语言进阶课程学习记录-第21课 - 宏定义与使用分析

C语言进阶课程学习记录-第21课 - 宏定义与使用分析 宏定义的本质实验-字面量比较宏定义表达式实验-表达式有效性宏的作用域实验-作用域分析内置宏内置宏演示小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学…

蓝桥杯python组真题练习1

目录 1.单词分析 2.成绩统计 3.门牌制作 4.卡片 5.跑步训练 6.蛇形填数 7.时间显示 1.单词分析 1.单词分析 - 蓝桥云课 (lanqiao.cn) s list(input()) maxx 0 for i in s:num s.count(i)if num>maxx:sm imaxx numif num maxx:if ord(sm)>ord(i):sm i print…

ctypes 封装c语言

一&#xff0c;python与C类型对应关系 最左边一列的ctypes type&#xff0c;以替代C库中的各种类型。 二&#xff0c;不带参数的调用 1&#xff0c;target.c #include <stdio.h>void hello_world(){printf("hello downey!!\r\n"); } 2&#xff0c;执行命令…

基于Java,SSM,html,Vue在线视频播放管理系统网站设计

摘要 基于Java, SSM, HTML, Vue的在线视频播放管理系统网站设计是一个利用Spring框架、SpringMVC、MyBatis&#xff08;SSM&#xff09;和前端技术HTML与Vue.js实现的多功能Web应用。这个系统旨在为用户提供一个便捷、高效的平台来上传、管理和观看视频内容&#xff0c;同时兼…

吴恩达2022机器学习专项课程(一) 5.1 多元特征回归

问题预览/关键词 多元特征回归是什么&#xff1f;多元特征回归的符号多元特征回归的函数是&#xff1f;函数简化版 笔记 1.多元特征回归 从单一的输入特征&#xff08;如房子的大小&#xff09;扩展到多个输入特征&#xff08;如卧室数量、楼层、房龄&#xff09;。 2.多元…

Android java基础_类的继承_android 类继承(2)

运行结果&#xff1a;rootubuntu:/home/topeet/guyilian# javac Ext2.java rootubuntu:/home/topeet/guyilian# java Ext2 10 ustc **子类继承父类的方法和属性都可以进行覆写&#xff0c;我们在子类覆写父类的printInfo方法。**class Person { private int age; public void …

图像拼接——最小割准则提取拼接缝

一、最大流问题与Ford-Fulkerson算法介绍 二、最大流与最小割 显然,我们有对任意一个割,穿过该割的净流量上界就是该割的容量,即不可能超过割的容量。所以网络的最大流必然无法超过网络的最小割。最小割是指割的容量最小,最大流是指网络当中最大的净流量,简单的例子s是水龙…

深入浅出 -- 系统架构之微服务架构选型参考图

技术选型架构图 是一个用于展示项目中所采用的各种技术和组件之间关系的图表。 它通常包括以下几个部分&#xff1a; 1. 项目名称和描述&#xff1a;简要介绍项目的背景和目标。 2. 技术栈&#xff1a;列出项目中使用的主要技术和工具&#xff0c;如编程语言、框架、数据库…

47.函数基本概念和用法

目录 一.什么是函数 二.定义函数 三.函数调用 四.函数的声明 五.函数的分类 六.视频教程 一.什么是函数 每个C语言程序至少有一个函数&#xff0c;即主函数man函数。除了主函数以外&#xff0c;我们也可以自定义其他函数。 函数可以看作是某个功能的封装&#xff0c;而且…

[AutoSar]BSW_Memory_Stack_004 创建一个简单NV block并调试

目录 关键词平台说明背景一、需求二、配置2.1 NvMBlockDescriptors2.2 NvMFeeRef2.3 FeeBlockConfigurations 三、code3.1 声明和定义3.2 调试 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 项目ValueOSautosar OSautosar厂商vector &#xff0c; EB芯片厂商TI 英飞凌编…

useBlocker 防止页面跳转导致的表单丢失

关于useBlocker The hook allows you to prevent the user from navigating away from the current location, and present them with a custom UI to allow them to confirm the navigation 在react-router的v6版本之前&#xff0c;我们会使用<Prompt />组件来拦截路由的…

9、鸿蒙学习-开发及引用静态共享包(API 9)

HAR&#xff08;Harmony Archive&#xff09;是静态共享包&#xff0c;可以包含代码、C库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。HAR不同于HAP&#xff0c;不能独立安装运行在设备上&#xff0c;只能作为应用模块的依赖项被引用。…