hello,各位小伙伴,本篇文章跟大家一起学习《Linux:shell原理以及模拟实现》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !
如果本篇文章对你有帮助,还请各位点点赞!!!
话不多说,开始正题:
文章目录
模拟实现shell
对于模拟实现shell
分这几步走:
由于shell
不断读取命令,也就是说可以利用死循环来实现
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
while(true)
{
PrintCommandLine(); // 1. 命令行提示符
GetCommandLine(); // 2. 获取用户命令
ParseCommandLine(); // 3. 分析命令
ExecuteCommand(); // 4. 执行命令
}
return 0;
}
怎么退出shell
,我的做法是直接Ctrl + C
!!!
PrintCommandLine()
对于获取这些信息可以使用系统的接口,其实也可以用环境变量来进行获取getenv()
:
#include <cstdlib>
#include <cstring>
#include <string>
string GetUserName()
{
const char* name = getenv("USER");
return (name && name[0] != '\0') ? name : "None"; // 检查是否为nullptr并确保非空
}
string GetHostName()
{
const char* hostname = getenv("HOSTNAME");
return (hostname && hostname[0] != '\0') ? hostname : "None";
}
string GetPwd()
{
const char* pwd = getenv("PWD");
return (pwd && pwd[0] != '\0') ? pwd : "None";
}
输出命令行:
const int basesize = 1024;
string MakeCommandLine()
{
char line[basesize];
snprintf(line, basesize, "[%s@%s %s]# ",\
GetUserName().c_str(), GetHostName().c_str(), GetPwd().c_str());
}
对于snprintf
是将一个字符串格式化输出到另一个字符串中
为了区别和shell
原本的命令行,在这里使用#
来替换掉$
以上就将PrintCommandLine差不多写完了:
由于命令行上不带\n
,所以要刷新缓冲区就可以使用fflush(stdout)
来对显示器进行刷新
void PrintCommandLine()
{
printf("%s", MakeCommandLine().c_str());
fflush(stdout);
}
int main()
{
while(true)
{
PrintCommandLine(); // 1. 命令行提示符
}
return 0;
}
可以test一下:
while(true)
{
PrintCommandLine(); // 1. 命令行提示符
printf("%s\n",buffer);
sleep(1);
}
GetCommandLine()
要获取用户输入的命令,可以用一个字符串来记录buffer[]
:
char buffer[basesize];
由于我们并不清楚用户会输入多少个命令选项(ls -l -a……),所以我们并不能使用cin
来获取字符串,要把整个用户输入的当作一个字符串,这里我使用fgets()
:
从一个文件流里获取字符串:
char *result = fgets(buffer, size, stdin);
因为当用户输入指令错误时会返回打印命令行提示符,所以我们要检测用户是否成功输入,采用bool
,因为用户输入完指令之后会按回车,字符串中就会多一个\n
,我们要去掉:
bool GetCommandLine(char buffer[], int size) // 2. 获取用户命令
{
char *result = fgets(buffer, size, stdin);
if(!result)
{
return false;
}
buffer[strlen(buffer) - 1] = 0; // 将 \n 去掉
return true;
}
test一下:
int main()
{
char buffer[basesize];
while(true)
{
PrintCommandLine(); // 1. 命令行提示符
if(!GetCommandLine(buffer, basesize)) // 2. 获取用户命令
{
continue;
}
printf("%s\n", buffer);
// ParseCommandLine(); // 3. 分析命令
// ExecuteCommand(); // 4. 执行命令
}
ParseCommandLine()
对于解析命令行,不就是将一个字符串拆成一个一个字符,有多少个字符可以使用整形int
来存储,字符可以放在char *
数组来存储,这不就是main
的参数吗?int argc, char *argv
:
我在这里创建2个全局变量int gargc = 0; char *gargv[argvnum];
gargc
用来记录多少个字符,gargv
用来记录分割好的字符指令
传参:
ParseCommandLine(buffer, strlen(buffer)); // 3. 分析命令
函数实现:
void ParseCommandLine(char buffer[], int len) // 3. 分析命令
{
(void)len; // 解除报警
gargc = 0;
memset(gargv, 0, sizeof((char*)buffer)); // 为了保证安全,清空历史字符串
const char *sep = " "; // 作为分割符
gargv[gargc++] = strtok(buffer, sep);
while(gargv[gargc++] = strtok(nullptr, sep));
gargc--;
}
开始前将gargc,gargv
重置是为了保证安全
对于strtok
可自行了解,实际上就是找到分隔符并将其设置为\0
,strtok
有个设定,你若想从上次记录的位置继续分割,第一个参数必须是nullptr或者NULL
由于gargc
的后置++
会导致多++
一次,所以后续进行--
一次
对ParseCommandLine进行test:
void debug()
{
printf("gargc: %d\n", gargc);
for(int i = 0; i