【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;
}
创作不易,点个赞呗,蟹蟹啦~