MyShell源代码公开
本篇是对之前知识的一个综合运用,也是检验你是否对前置知识有个较为透彻的理解的好时机
#include
#include
#include
#include
#include
#include
#include
#include
const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
// 全局的命令行参数表
char *gargv[argvnum];
int gargc = 0;
// 全局的变量
int lastcode = 0;
// 我的系统的环境变量
char *genv[envnum];
// 全局的当前shell工作路径
char pwd[basesize];
char pwdenv[basesize];
string GetUserName()
{
string name = getenv("USER");
return name.empty() ? "None" : name;
}
string GetHostName()
{
string hostname = getenv("HOSTNAME");
return hostname.empty() ? "None" : hostname;
}
string GetPwd()
{
if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";
snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);
putenv(pwdenv); // PWD=XXX
return pwd;
}
string LastDir()
{
string curr = GetPwd();
if(curr == "/" || curr == "None") return curr;
size_t pos = curr.rfind("/");
if(pos == std::string::npos) return curr;
return curr.substr(pos+1);
}
string MakeCommandLine()
{
char command_line[basesize];
snprintf(command_line, basesize, "[%s@%s %s]# ",\
GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());
return command_line;
}
void PrintCommandLine() // 1. 命令行提示符
{
printf("%s", MakeCommandLine().c_str());
fflush(stdout);
}
bool GetCommandLine(char command_buffer[], int size)
{
char *result = fgets(command_buffer, size, stdin);
if(!result)
{
return false;
}
// 因为 command_line 里有一个 \n,我们把它替换成 \0 即可
command_buffer[strlen(command_buffer)-1] = '\0';
if(strlen(command_buffer) == 0) return false;
return true;
}
获取用户输入后,我们需要将接收到的字符串拆分为命令及其参数。
将接收到的字符串拆开
通过 strtok 函数,我们可以将一个字符串按照特定的分隔符打散,依次返回子串
void ParseCommandLine(char command_buffer[], int len)
{
(void)len;
memset(gargv, 0, sizeof(gargv));
gargc = 0;
const char *sep = " ";
gargv[gargc++] = strtok(command_buffer, sep);
while((bool)(gargv[gargc++] = strtok(nullptr, sep)));
gargc--;
}
bool ExecuteCommand()
{
pid_t id = fork();
if(id < 0) return false;
if(id == 0)
{
execvpe(gargv[0], gargv, genv);
exit(1);
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
if(WIFEXITED(status))
{
lastcode = WEXITSTATUS(status);
}
else
{
lastcode = 100;
}
return true;
}
return false;
}
内建命令是指直接内置在操作系统内核中的一些命令,与普通的外部命令(外部程序文件)不同。这些内建命令是直接由shell解释器(如Bash、Zsh等)所处理,而不需要通过外部文件的方式来执行。这些内建命令通常在操作系统的shell环境中被频繁使用,并且执行速度更快,因为它们不需要创建新的进程来执行
在Unix和类Unix操作系统中,通常会有一些内建命令,比如cd、echo、exit等。这些命令不需要单独的可执行文件,而是直接由shell内核提供支持。当用户在shell中输入这些命令时,shell会直接处理它们,而不需要通过搜索系统路径来找到可执行文件
值得一提的是,某些shell也允许用户通过自定义的方式添加新的内建命令,这样用户可以根据自己的需求来扩展shell的内建功能
void AddEnv(const char *item)
{
int index = 0;
while(genv[index])
{
index++;
}
genv[index] = (char*)malloc(strlen(item)+1);
strncpy(genv[index], item, strlen(item)+1);
genv[++index] = nullptr;
}
bool CheckAndExecBuiltCommand()
{
if(strcmp(gargv[0], "cd") == 0)
{
if(gargc == 2)
{
chdir(gargv[1]);
lastcode = 0;
}
else
{
lastcode = 1;
}
return true;
}
else if(strcmp(gargv[0], "export") == 0)
{
if(gargc == 2)
{
AddEnv(gargv[1]);
lastcode = 0;
}
else
{
lastcode = 2;
}
return true;
}
else if(strcmp(gargv[0], "env") == 0)
{
for(int i = 0; genv[i]; i++)
{
printf("%s\n", genv[i]);
}
lastcode = 0;
return true;
}
else if(strcmp(gargv[0], "echo") == 0)
{
if(gargc == 2)
{
if(gargv[1][0] == '$')
{
if(gargv[1][1] == '?')
{
printf("%d\n", lastcode);
lastcode = 0;
}
}
else
{
printf("%s\n", gargv[1]);
lastcode = 0;
}
}
else
{
lastcode = 3;
}
return true;
}
return false;
}
支持的内建命令有:
void InitEnv()
{
extern char **environ;
int index = 0;
while(environ[index])
{
genv[index] = (char*)malloc(strlen(environ[index])+1);
strncpy(genv[index], environ[index], strlen(environ[index])+1);
index++;
}
genv[index] = nullptr;
}
从父进程复制环境变量
int main()
{
InitEnv();
char command_buffer[basesize];
while(true)
{
PrintCommandLine();
if( !GetCommandLine(command_buffer, basesize) )
{
continue;
}
ParseCommandLine(command_buffer, strlen(command_buffer));
if ( CheckAndExecBuiltCommand() )
{
continue;
}
ExecuteCommand();
}
return 0;
}
主循环流程:
感觉如何呢!