欢迎来到博主的专栏:从0开始Linux
博主ID:代码小豪
文章目录
- bash
- myshell
- 源码
bash
什么?我写bash?bash作为一个大型的shell程序,甚至已经成为一种语言。博主当然没能力复刻。
博主这里写了一个仿bash的shell程序。主要目的是仿制bash的命令行是如何执行命令的,但是bash的功能远超于此。相当于是博主在班门弄斧了。这里先带大家了解一下bash。
bash是linux系统下的默认shell程序。比如我们登录linux系统后,显示出来的命令行,其实就是bash程序的命令行模式
我们在命令行中写的指令,其实都是交给bash解析的。而且我们在命令行中启动的程序,其父进程都是bash。因此,我们打开linux系统所见的那个黑乎乎的命令行,其实就是bash,换句话说,我们在使用linux的时候,其实一直都在使用bash程序,只是我们对其不了解罢了。
myshell
博主将这个自己写的shell程序,命名为myshell,既然myshell要仿bash的命令行模式,首先我们要搞清楚bash的功能有什么。
首先是命令行,我们要先提示用户,当前我们处于命令行模式,请用户输入指令。因此,我们要先写出myshell的命令行提示符。为了和bash做出区分,因此命令行设计也要和bash不同才行。
其具体格式为[用户名@主机名 当前工作文件]:
,而这些信息都在环境变量当中,因此需要用到getenv()函数获取这些数据。
//博主在文章末尾会附上完整代码,因此这里只展示部分,以提供思路参考
std::string getusr()//获取用户名
{
std::string name=getenv("USER");
return name.empty()?"None":name;
}
void PrintCommandLine()//打印命令行提示符
{
char buf[BASE_SIZE]={0};
snprintf(buf,BASE_SIZE,"[%s@%s %s]:"\
,getusr().c_str(),gethost().c_str(),getcwd().c_str());//获取这些环境变量,并且将其输出到buf中
fputs(buf,stdout);
fflush(stdout);
}
接下来,命令行可以接收用户输入的命令,我们将其保存在一个字符数组当中。
int main()
{
char commandbuf[BASE_SIZE];//保存用户命令
EnvInit();//初始化环境变量
while(true)
{
//1. 命令行提示符
PrintCommandLine();
//2. 获取用户命令
if(GetUsrCommand(commandbuf)==false)
{
continue;
}
//3. 解析用户命令
ParseUsrCommand(commandbuf);
//4. 执行用户命令
ExecuteUsrCommand();
}
return 0;
}
第三步是解析用户命令行参数,比如ls -a -l --color
,我们应该将每个单独的字符串(即空格隔开的字符串),按照顺序保存在一个字符型指针数组当中。
第四步是执行用户命令,在前面的进程章节中,博主提到,在命令行执行的程序,实际上都是bash创建的子进程,但是bash是如何创建的呢?其实原理很简单,bash使用fork函数,创建一个子进程,接着用execvpe函数,切换到用户想要启动的进程,比如bash执行ls指令,本质上就是bash先fork出一个子进程,接着execvpe("ls",gargv,environ)
的方式创建的。具体的细节大家可以去看看博主前面写的,与进程相关的文章,实际上博主写这个myshell的本质是想让大家将进程系列的知识串联起来。
void ExecuteUsrCommand()//执行用户命令
{
pid_t id=fork();//创建一个bash子进程
if(id==0)
{
execvpe(gargv[0],gargv,env);//切换到用户的命令进程
exit(1);
}
else if(id>0)
{
int status=0;
waitpid(id,&status,0);//回收创建的子进程,避免产生僵尸进程
lastcode=WEXITSTATUS(status);
}
}
但是有些命令,是不能通过创建子进程的方式执行的,比如cd指令,我使用这个指令是想让bash切换工作路径,而是让子进程切换路劲,因此如果解析出用户使用cd指令,我们应该让bash自己去执行,而非创建子进程执行,这种命令,我们称其为内建命令。bash的内建命令比较多,博主只写了其中几个。
bool CheckAndExecuteBulitCommand()//判断一下用户输入的是否是内建命令
{
if(gargc==2&&strcmp(gargv[0],"cd")==0)//cd是内建命令
{
if(chdir(gargv[1])==-1)//切换myshell的当前工作路径
{
printf("No such file of dirtory");
lastcode=ERROR;
return true;
}
lastcode=0;
return true;
}
else if(strcmp(gargv[0],"export")==0)
{
addenv(gargv[1]);
return true;
}
else if(strcmp(gargv[0],"env")==0)//显示myshell当前的环境变量
{
for(int i=0;env[i]!=nullptr;i++)
{
printf("env[%d]:%s\n",i,env[i]);
}
return true;
}
else if(gargc==2&&strcmp(gargv[0],"echo")==0)
{
if(strcmp(gargv[1],"$?")==0)
{
printf("%d\n",lastcode);
}
else
{
printf("%s\n",gargv[1]);
}
lastcode=0;
return true;
}
else if(strcmp(gargv[0],"exit")==0)
{
printf("thank you for your using myshell\n");
exit(0);
}
return false;
}
到此,一个简单的shell程序myshell就已经写好了,下面是实际使用的演示。
myshell
源码
点击下面链接获取源码与程序
简单的仿bash的shell程序
或:
#include<cstdio>
#include<string>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/wait.h>
#define BASE_SIZE 256
//命令行参数与环境变量
char *gargv[BASE_SIZE];
int gargc;
char* env[BASE_SIZE];
extern char** environ;
//最近一次进程的运行结果
int lastcode=0;
#define ERROR 1
//当前文件路径
char cwd[BASE_SIZE];
char cwdenv[BASE_SIZE];
void EnvInit()
{
int index=0;
while(environ[index]!=nullptr)
{
env[index]=(char*)malloc(strlen(environ[index])+1);
strcpy(env[index],environ[index]);
index++;
}
env[index]=nullptr;
}
void addenv(char* item)//增加环境变量
{
int index=0;
while(env[index]!=nullptr)
{
index++;
}
env[index]=(char*)malloc(strlen(item)+1);
strcpy(env[index],item);
env[index+1]=nullptr;
}
std::string getusr()//获取用户名
{
std::string name=getenv("USER");
return name.empty()?"None":name;
}
std::string gethost()//获取主机名
{
std::string hostname=getenv("HOSTNAME");
return hostname.empty()?"None":hostname;
}
std::string getcwd()//获取当前工作文件
{
if( getcwd(cwd,BASE_SIZE)==nullptr)
{
strcpy(cwd,"Node");
}
snprintf(cwdenv,BASE_SIZE,"PWD=%s",cwd);
putenv(cwdenv);
return cwd;
}
void PrintCommandLine()//打印命令行提示符
{
char buf[BASE_SIZE]={0};
snprintf(buf,BASE_SIZE,"[%s@%s %s]:"\
,getusr().c_str(),gethost().c_str(),getcwd().c_str());//获取这些环境变量,并且将其输出到buf中
fputs(buf,stdout);
fflush(stdout);
}
bool GetUsrCommand(char commandbuf[])
{
if(fgets(commandbuf,BASE_SIZE,stdin)==nullptr)
{
return false;
}
commandbuf[strlen(commandbuf)-1]='\0';
return true;
}
void ParseUsrCommand(char* commandbuf)
{
memset(gargv,0,BASE_SIZE*sizeof(commandbuf[0]));
gargc=0;
gargv[gargc++]=strtok(commandbuf," ");
while(bool(gargv[gargc]=strtok(nullptr," ")))
{
gargc++;
}
}
bool CheckAndExecuteBulitCommand()//判断一下用户输入的是否是内建命令
{
if(gargc==2&&strcmp(gargv[0],"cd")==0)//cd是内建命令
{
if(chdir(gargv[1])==-1)//切换myshell的当前工作路径
{
printf("No such file of dirtory");
lastcode=ERROR;
return true;
}
lastcode=0;
return true;
}
else if(strcmp(gargv[0],"export")==0)
{
addenv(gargv[1]);
return true;
}
else if(strcmp(gargv[0],"env")==0)//显示myshell当前的环境变量
{
for(int i=0;env[i]!=nullptr;i++)
{
printf("env[%d]:%s\n",i,env[i]);
}
return true;
}
else if(gargc==2&&strcmp(gargv[0],"echo")==0)
{
if(strcmp(gargv[1],"$?")==0)
{
printf("%d\n",lastcode);
}
else
{
printf("%s\n",gargv[1]);
}
lastcode=0;
return true;
}
else if(strcmp(gargv[0],"exit")==0)
{
printf("thank you for your using myshell\n");
exit(0);
}
return false;
}
void ExecuteUsrCommand()//执行用户命令
{
pid_t id=fork();//创建一个bash子进程
if(id==0)
{
execvpe(gargv[0],gargv,env);//切换到用户的命令进程
exit(1);
}
else if(id>0)
{
int status=0;
waitpid(id,&status,0);//回收创建的子进程,避免产生僵尸进程
lastcode=WEXITSTATUS(status);
}
}
void debug()
{
int i=0;
while(gargv[i])
{
printf("%s\n",gargv[i]);
i++;
}
printf("%d\n",gargc);
}
int main()
{
char commandbuf[BASE_SIZE];
EnvInit();//初始化环境变量
while(true)
{
//1. 命令行提示符
PrintCommandLine();
//2. 获取用户命令
if(GetUsrCommand(commandbuf)==false)
{
continue;
}
//3. 解析用户命令
ParseUsrCommand(commandbuf);
//4. 执行用户命令
if(CheckAndExecuteBulitCommand())//判断一下用户输入的是否是内建命令
{
continue;
}
ExecuteUsrCommand();
}
return 0;
}