shell是一个命令行解释器,将用户命令解析为操作系统所能理解的指令,实现用户与操作系统的交互。
实现一个简单地shell,需要循环执行以下过程
1.获得命令行,2.解析命令行,3.建立一个子进程,4.替换掉子进程,5.父进程等待子进程退出
实现过程:
包含程序需要的头文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#incuude<wait.h>
#include<assert.h>
首先::shell命令提示符采用以下的格式:用户名+主机名+目录,通过函数getenv()获得所需要的参数。
int main()
{
printf("[%s@%s Path]$ ",getenv("LOGNAME"),getenv("HOSTNAME")); //获得环境变量参数
fflush(stdout); //刷新缓冲区
return 0;
}
其次:我们需要获取需要指向的指令并解析,我们将从键盘获取的指令存放到lineCommand[]里,将解析后的字符串放到myargv[]里。
(像我们输入 ls -a 时,我们分解成"ls","-a"分别存放到myargv[]里)
#define OPT_NUM 64
#define NUM 1024
char lineCommand[NUM] //存放用户输入时的指令
char *myargv[OPT_NUM] //将指令分解成一个个字符串,存放
int main()
{
char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);获得输入的时候会会对到用户输入的\n;
assert(s!=NULL) //判断不为空串
lineCommand[lineCommand-1]=0; //清除掉最后一个\0;
myargv[0]=strtok(linCommand," "); //分割字符串存到myargv里
int i=1;
while(myargv[i++]=strtok(NULL," "); //strtok失败会返回NULL
return 0;
}
最后:我们替换掉子进程,让父进程等待子进程退出
int lastCode=0;
int lastSig=0;
int main()
{
pid_t id=fork();
if(id==0) //子进程
{
execvp(myargvp[0],myargv); //调用execvp
}
int status=0;
pid_t ret=waitpid(id,&status,0);
assert(ret>0)
lastCode=((status>>8)& oxff); //拿到退出码
lastSig=(status & ox7f); //拿到退出信号
return 0
}
cd 指令:其中,我们会发现使用cd命令是并不会改变路径的,因为实现是是通过execvp函数替换掉子进程,子进程结束退出后,并不会影响到父进程所在的工作目录,所以我们在执行类似的命令时,并不需要创建子进程,而是应该让shell自己指向对应的指令。
if(myargv[0]!=NULL && strcmp(myargv[0],"cd")==0)
{
if(myargv[1]!=NULL)
chdir(myargv[1])//改变到指定路径
continue;//跳出当前循环
}
echo 指令:和cd 类似,并不需要创建子进程,通过定义的退出码,我们可以通过echo $? 命令获得到程序退出码
if(myargv[0]!=NULL && myargb[1]==NULL && strcmp(myargv[0],"echo"))
{
if(strcmp(myargv[1],"$?")==0)
{
printf("lastCode:%d lastSig:%d\n",lastCode,lastSig);
}
else
{
printf("%s\n",myargv[1]);
}
continue;
}
全部代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#incuude<wait.h>
#include<assert.h>
#define OPT_NUM 64
#define NUM 1024
char lineCommand[NUM] //存放用户输入时的指令
char *myargv[OPT_NUM] //将指令分解成一个个字符串,存放
int lastCode=0;
int lastSig=0;
int main()
{
while(1)
{
printf("[%s@%s Path]$ ",getenv("LOGNAME"),getenv("HOSTNAME")); //获得环境变量参数
fflush(stdout); //刷新缓冲区
char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);获得输入的时候会会对到用户输入的\n;
assert(s!=NULL) //判断不为空串
(void)s;
lineCommand[lineCommand-1]=0; //清除掉最后一个\0;
myargv[0]=strtok(linCommand," "); //分割字符串存到myargv里
int i=1;
if(myargv[0]!=NULL && strcmp(myargv[0],"cd")==0)
{
if(myargv[1]!=NULL)
chdir(myargv[1])//改变到指定路径
continue;//跳出当前循环
}
if(myargv[0]!=NULL && myargb[1]==NULL && strcmp(myargv[0],"echo"))
{
if(strcmp(myargv[1],"$?")==0)
{
printf("lastCode:%d lastSig:%d\n",lastCode,lastSig);
}
else
{
printf("%s\n",myargv[1]);
}
continue;
}
if(myargv[0]!=NULL && strcmp(myargv[0],"ls")==0)
{
//实现使用ls是带上颜色
myargv[i++]=(char*)"--color=auto";
}
while(myargv[i++]=strtok(NULL," "); //strtok失败会返回NULL
}
printf("[%s@%s Path]$ ",getenv("LOGNAME"),getenv("HOSTNAME")); //获得环境变量参数
fflush(stdout); //刷新缓冲区
fgets(lineCommand,sizeof(lineCommand)-1,stdin);获得输入的时候会会对到用户输入的\n;
assert(s!=NULL) //判断不为空串
lineCommand[lineCommand-1]=0; //清除掉最后一个\0;
myargv[0]=strtok(linCommand," "); //分割字符串存到myargv里
int i=1;
while(myargv[i++]=strtok(NULL," "); //strtok失败会返回NULL
pid_t id=fork();
if(id==0) //子进程
{
execvp(myargvp[0],myargv); //调用execvp
}
int status=0;
pid_t ret=waitpid(id,&status,0);
assert(ret>0)
lastCode=((status>>8)& oxff); //拿到退出码
lastSig=(status & ox7f); //拿到退出信号
}