【Linux】自定义简易shell

news/2024/11/29 6:32:01/

【Linux】自定义简易shell

 

🥕个人主页:开敲🍉

🔥所属专栏:Linux🍊

🌼文章目录🌼

1. 实现思路

2. 实现代码

    2.1 输出命令行提示符

    2.2 获取用户输入信息

    2.3 解析用户输入命令

    2.4 执行用户输入命令

    2.5 小优化(可写可不写)

3. 实现源码

在学习完 Linux 进程控制之后,我们就可以用已有的知识来自己实现一个简易的 shell程序。

1. 实现思路

  仔细留意我们在 shell 中输入命令时,在每一行的最开始会打印这么一串信息:

  其中包含了:用户名、主机名、当前路径。这三个信息实际上分别对应的是环境变量的:USER、HOSTNAME、PWD。这就说明我们在实现自己的 shell 时需要获取这三个环境变量并打印。

  接着,在打印这一串信息后,光标就会卡在当前行的末尾:

  这是在等在用户输入信息,因此我们还需要获取用户输入的信息,在用户输入信息之前光标卡死不动,这里我们可以用 fgets 接口来帮助我们实现。

  随后,在我们输入完一串信息后,shell程序并不会直接退出,而是继续重复上面的过程:打印命令行提示符:用户名、主机名、当前路径;等待用户输入信息。

  因此我们可以把上面的过程写成一个死循环,只要程序不退出,就一直运行。

  最后当用户输入完命令后,我们就要执行用户所输入的命令,这里就用到了我们 进程控制 中所学的 程序替换

  综上,我们实现 shell 的大体思路就出来了,后面还有很多细节我们实现时作优化。

2. 实现代码
    2.1 输出命令行提示符

  输出命令行提示符:用户名、主机名、当前路径

snprintf:

第一个参数:要写入的空间的地址

第二个参数:写入的格式,类似 printf:%d %s 等格式

最后的 ...:可变参数,第二个参数中需要的参数格式及个数是什么这里就传什么

实现代码参考:

    2.2 获取用户输入信息

  使用 fgets 获取用户输入信息:

  获取用户输入信息就这一个函数,非常简单,但是我们光获取到了用户输入的信息还没法用于后面的执行:因为当前获取到的只是一串字符串 如:"ls -a -l"。我们需要对这个字符串进行解析操作,将 "ls"  "-a"  "-l" 全部剥离出来。

参考实现代码:

​ #include <iostream>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>using namespace std;#define FORMAT "%s@%s %s # "//命令行提示符
const char* GetUserName()
{const char* un = getenv("USER");return un == nullptr ? "None" : un;
}const char* GetHostName(){const char* hn = getenv("HOSTNAME");return hn == nullptr ? "None" : hn;}const char* GetPwd(){const char* pwd = getenv("PWD");return pwd == nullptr ? "None" : pwd;}//输出命令行提示符void CmdPrompt(){char prompt[GETSIZE];snprintf(prompt,sizeof(prompt),FORMAT,GetUserName(),GetHostName(),GetPwd()));printf("%s",prompt);}

  下面就是解析用户输入信息。

    2.3 解析用户输入命令

  我们使用 strtok 函数来帮助我们解析:

第一个参数:需要解析的字符串

第二个参数:解析的格式,比如:传 "/",则会以 "/" 作为分隔符,将字符串一个个拆分

  因为我们解析完后需要执行解析后的命令,因此我们这里将解析出来的一个个字符串放入一个新的数组中:argv。看到这个名称相信你就能够恍然大悟:这不就是参数表吗?同时我们还需要一个参数表示 argv 中的参数个数:argc。看到这个名称也会让你再次恍然大悟。

  以空格为分隔符,将用户输入信息拆分存入 argv 中,argc 表示的就是参数个数。最后 argc-- 是因为我们最后还写入了一个 nullptr,这也会算进参数中,因此我们需要 --。因为 argc 是全局变量,因此为了保证我们每次输入命令能够正确拆分,每次调用都将 argc 置为0。

参考实现代码:

#define MAXSIZE 128
char* argv[MAXSIZE]
int argc = 0;//解析用户输入的命令void ParseCommand(char* command){#define SEP " "argc = 0;argv[argc++] = strtok(command,SEP);while(argv[argc++] = strtok(nullptr,SEP));argc--;}
    2.4 执行用户输入命令

  这里就要用到我们 进程控制 中学的 exec 系列函数,来进行进程替换。但是这里我们不能直接替换当前进程,我们创建一个子进程,让子进程执行进程替换,替换子进程不影响父进程。

参考实现代码:

pid_t id = fork();
if(id==0)
{execvp(argv[0],argv);exit(1);
}pid_t pid = waitpid(-1,nullptr,0);
    2.5 小优化(可写可不写)

  实现完前面的功能,我们的自定义简易shell就已经成型了,不过还有个小问题:我们在 输出命令行提示符 里输出当前路径时直接用的是 PWD,这会直接输出当前路径,非常长,而系统的 shell 输出的只是当前目录,因此我们这里可以对路径的打印做个小优化。

参考实现代码:

//优化命令行路径string OptimizePrompt(const char* pwd){#define SLASH "/"string s = pwd;if(s==SLASH) return "/";auto pos = s.rfind(SLASH);if(pos==string::npos) return "BUG?";return s.substr(pos+1);}//输出命令行提示符void CmdPrompt(){char prompt[GETSIZE];snprintf(prompt,sizeof(prompt),FORMAT,GetUserName(),GetHostName(),OptimizePrompt(GetPwd()).c_str());printf("%s",prompt);}
3. 实现源码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;#define GETSIZE 1024
#define FORMAT "%s@%s %s # "
#define MAXSIZE 128char* argv[MAXSIZE];
int argc = 0;//输出命令行提示符
const char* GetUserName()
{const char* un = getenv("USER");return un==nullptr?"None":un;
}const char* GetHostName()
{const char* hn = getenv("HOSTNAME");return hn==nullptr?"None":hn;
}const char* GetPwd()
{const char* pwd = getenv("PWD");return pwd==nullptr?"None":pwd;
}//优化命令行路径
string OptimizePrompt(const char* pwd)
{
#define SLASH "/"string s = pwd;if(s==SLASH) return "/";auto pos = s.rfind(SLASH);if(pos==string::npos) return "BUG?";return s.substr(pos+1);
}//输出命令行提示符
void CmdPrompt()
{char prompt[GETSIZE];snprintf(prompt,sizeof(prompt),FORMAT,GetUserName(),GetHostName(),OptimizePrompt(GetPwd()).c_str());printf("%s",prompt);
}//获取用户输入的命令
bool GetCommand(char* command,int size)
{char* ret = fgets(command,size,stdin);if(!ret) return false;command[strlen(command)-1] = 0;if(!strlen(command)) return false;return true;
}//解析用户输入的命令
void ParseCommand(char* command)
{
#define SEP " "argc = 0;argv[argc++] = strtok(command,SEP);while(argv[argc++] = strtok(nullptr,SEP));argc--;
}
int main()
{while(1){//1. 输出命令行提示符CmdPrompt();//2. 获取用户输入命令char command[GETSIZE];if(!GetCommand(command,sizeof(command)))continue;//3. 解析用户命令ParseCommand(command);//DeBugPC();//4. 执行用户命令pid_t id = fork();if(id==0){execvp(argv[0],argv);exit(1);}pid_t pid = waitpid(-1,nullptr,0);}return 0;
}

                                                 创作不易,点个赞呗,蟹蟹啦~ 


http://www.ppmy.cn/news/1550832.html

相关文章

鸿蒙开发App 如何通过抓包查看 http 网络请求?

通过借助第三方工具 Charles https://www.charlesproxy.com/ https://www.zzzmode.com/mytools/charles/https://www.zzzmode.com/mytools/charles/ Charles 激活码计算器 相关博客日志&#xff1a;https://zhuanlan.zhihu.com/p/281126584 MAC上的使用方法&#xff1a; ch…

c++类模板成员函数的特化

是的&#xff0c;类成员函数可以是模板函数。在C中&#xff0c;类模板和非模板类都可以包含模板成员函数。这种设计允许类在某些成员函数中具有泛型行为&#xff0c;而不需要将整个类设计为模板。 本文将详细介绍类成员函数作为模板函数的概念、声明和定义方法&#xff0c;以及…

算法竞赛(Python)-链表

文章目录 一 链表简介1.1链表定义1.2 双向链表1.3 循环链表 二、链表的基本操作2.1 链表的结构定义2.2 建立一个线性链表2.3 求线性链表的长度2.4 查找元素2.5 插入元素2.5.1 链表头部插入元素2.5.2 链表尾部插入元素2.5.3 链表中间插入元素 2.6 改变元素2.7 删除元素2.7.1 链表…

Linux环境下配置neo4j图数据库

1.下载安装包 openjdk-11.0.1_linux-x64_bin.tar.gz neo4j-community-4.2.19-unix.tar.gz 2.之前配置好的配置文件 neo4j.conf 3.安装 3.1-jdk11的安装&#xff08;jdk1.8不够用&#xff09; 解压缩 tar -zxvf openjdk-11.0.1_linux-x64_bin.tar.gz修改系统环境变量 打开pro…

vue3项目搭建-4-正式启动项目,git管理

安装插件&#xff1a; npm install vue router npm install eslint 完成目录&#xff1a; 需要添置文件夹&#xff1a; apis -> api接口 composables -> 组合函数 directives -> 全局指令 styles -> 全局样式 utils -> 工具函数 git 管理&#xff1a; …

Francek Chen 的365天创作纪念日

文章目录 Francek Chen 的365天创作纪念日机缘收获日常成就憧憬 Francek Chen 的365天创作纪念日 Francek Chen 的个人主页 机缘 不知不觉的加入 CSDN 已有三年时间了&#xff0c;最初我第一次接触 CSDN 技术社区是在 2022 年 4 月的时候&#xff0c;通过学长给我们推荐了几个 …

从 EXCEL 小白到 EXCEL 高手的成长之路

在职场与日常生活中&#xff0c;Excel 作为一款强大的数据处理与分析工具&#xff0c;扮演着不可或缺的角色。无论是初学者还是资深职场人士&#xff0c;掌握 Excel 技能都能极大地提高工作效率。那么&#xff0c;从一个 Excel 小白蜕变成为 Excel 高手&#xff0c;究竟需要多久…

电子应用设计方案-27:智能淋浴系统方案设计

智能淋浴系统方案设计 一、系统概述 本智能淋浴系统旨在为用户提供舒适、便捷、个性化的淋浴体验&#xff0c;通过集成多种智能技术&#xff0c;实现水温、水流、淋浴模式的精准控制以及与其他智能家居设备的联动。 二、系统组成 1. 喷头及淋浴杆 - 采用可调节角度和高度的设计…