shell的字面翻译是壳,所以在Linux中有时也被称为外壳程序,主要功能是用来进行用户与内核之间的交互,他接收用户的命令送给内核去执行。
今天来模拟实现一下Linux下的命令行解释器shell的部分功能,在此运用到主要的知识点有创建子进程,进程等待和进程替换,还有main函数的命令行参数等。
完整代码大家可以参考这个:模拟实现Shell完整代码
既然是命令解释器,那么当然要给我们一个输入命令的地方,这个地方要满足我们日常敲出的命令的长度,所以我们来申请一块缓冲区。有了缓冲区,还要考虑命令行解释器不会只输一次命令,多次输入自然是需要一个大循环来进行控制。并且在真正的shell下,按下回车才会执行,否则不执行。大体框架大概就是这个样子,我们来看看具体的代码:
#define IN 1
#define OUT 0
int main()
{
char buf[100];
while(1)
{
printf("冰可乐>");
memset(buf, 0x00, sizeof(buf));
scanf("%[^\n]%*c",buf);
if( strncmp(buf, "exit", 4) == 0)
exit(0);
do_prase(buf);
}
return 0;
}
把所有输入的字符读入缓冲区之后,我们需要一个能够区分出输入几条命令选项的函数,这也就是我们的第二块功能函数了。在上面我们定义了两个宏,用这两个宏和isspace
函数我们可以做到分开每一条命令。isspace
函数的功能是读到空格类字符(空格,制表符(Tab),回车,换行符等)返回非零值,读到非空格类返回零值。
void do_prase(char *buf)
{
char* argv[8];
int argc = 0;
int flag = OUT;
int i = 0;
for(i = 0; buf[i] !='\0'; i++)
{
if(!isspace(buf[i]) && flag == OUT){
flag = IN;
argv[argc++] = &buf[i];
}else if(isspace(buf[i])){
flag = OUT;
buf[i] = '\0';
}
}
argv[argc] = NULL;
do_exec(argc, argv);
}
最后一个功能模块就是我们的创建子进程和进程替换了,因为shell在执行本命令后需要继续在界面检测等待下一条命令,所以shell本身并不执行命令的功能,而是创建子进程去执行,创建出子进程后,直接执行execvp函数进行替换。而exec一族的函数,可以将当前进程中的数据,代码,堆栈全部替换,但是进程ID不变。exec一族一般情况下所传参数是一个可执行文件的相关参数,也可以是Linux下的任何脚本文件,而此处 我们就用了第二种功能,执行Linux命令池中本身就有的脚本文件。
void do_exec(int argc, char* argv[])
{
pid_t id;
if( (id=fork()) == 0)
{
execvp(argv[0],argv);
printf("command %s not found\n",argv[0]);
exit(1);
}
int s;
waitpid(id, &s, 0);
}