一、前言
来制作一个简易的 [Shell 命令]行解释器。
首先这是与 Shell 的互动::

用下图的[时间轴]来表示事件的发生次序。其中时间从> > 左向右。shell 由标识为 sh 的方块代表,它随着时间的流逝从左向右移动。shell 从用户读入字符串 "ls"。shell 建立一个新的进程,然后在那个进程中运行 ls 程序并等待那个进程结束。

然后 shell 读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。所以要写一个 shell,需要循环以下过程:
1. 获取命令行
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)
5. 父进程等待子进程退出(wait)
二、准备工作
1.输出提示符

这里的提示字符为用户名 @主机名 当前路径# 直接打印出来作为提示所用
printf("用户名@主机名当前路径#");
这里没有 n,会有缓冲区的问题,类似于我们之前所说的进度条所遇到的问题,可以用 fflush(stdout) 刷新缓冲区。
2. 输入和获取命令
输入
我们需要输入一连串命令,其中可能出现空格,所以不能使用 gets 函数,需要用到 fgets 函数,同时,可以定义一个 lineCommand[NUM] 数组
#defineNUM1024 charlineCommand[NUM]; char*s=fgets(lineCommand,sizeof(lineCommand)-1,stdin); assert(s!=NULL);
但是打印的时候却多换了一行,这是我们把 n 也读取到了,直接进行处理即可, 清除最后一个 n
lineCommand[strlen(lineCommand)-1]=0;
可以通过打印看看效果和测试是否有 BUG
printf("test:%s
",lineCommand);

获取
输入之后,我们自然需要去进行获取,我们需要分割命令行,这个地方用 strtok。把字符串切割成若干个子串:
strtok: 第一次直接传递参数,第二次则必须传 NULL。且在最终 strtok 会返回 NULL。


3.shell 运行原理
同时,在理解一下 shell 的运行原理:shell 内部提取命令行做分析,然后调用 exec. shell 执行命令必须通过创建子进程,如果不创建子进程会把我们所有的 shell 全部替换,所以执行命令时一般磁盘上的程序必须创建子进程。
4. 内建命令
我们在运行自己写的 shell 的时候,发现输入 cd … 输入 cd path 等命令时发现路径并没有改变!

没有发生改变是因为自己写的 shell 执行很多命令都要 fork() 创建子进程,让子进程执行的 cd,子进程有自己的工作目录,所以更改的子进程的目录,子进程执行完毕,继续用的是父进程,既 shell,并没有影响父进程,所以并没有改变。
对于 cd, 我们可以采用内建命令:不需要创建子进程执行,让 shell 自己执行命令,称为内建命令。本质就是执行系统接口,我们可以调用一个系统接口 chdir,可解决上述问题:


5. 替换
采用 execvp 进行替换进程
pid_tid=fork(); assert(id!=-1); if(id==0) { execvp(myargv[0],myargv); exit(1); }
三、整体代码
#include
#include
#include
#include
#include
#include
#include
#defineNUM1024
#defineOPT_NUM64
charlineCommand[NUM];
char*myargv[OPT_NUM];//指针数组
intlastcode=0;
intlastsig=0;
intmain()
{
while(1)
{
//1.输出提示符
printf("lj@VM-8-2-centos当前路径#");
fflush(stdout);
//2.获取用户输入的命令,输入的时候,用户最后还输入了
char*s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
assert(s!=NULL);
(void)s;//避免Linux认为s变量未使用,导致警告
//清除最后一个
;例如:abcd
lineCommand[strlen(lineCommand)-1]=0;
//printf("test:%s
",lineCommand);
//"ls-a-l-i"-->字符串分割-->"ls""-a""-l""-i"
myargv[0]=strtok(lineCommand,"");
inti=1;
if(myargv[0]!=NULL&&(strcmp(myargv[0],"ls")==0))
{
myargv[i++]=(char*)"--color=auto";
}
//如果没有子串了,strtok会返回NULL,即myargv[end]=NULL
while(myargv[i++]=strtok(NULL,""));
//如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
//像这种不需要让我们的子进程来执行,而是让shell自己执行的命令—内建命令
//其中echo是一个自建命令
if(myargv[0]!=NULL&&(strcmp(myargv[0],"cd")==0))
{
if(myargv[1]!=NULL)chdir(myargv[1]);
continue;
}
if(myargv[0]!=NULL&&myargv[1]!=NULL&&(strcmp(myargv[0],"echo")==0))
{
if(strcmp(myargv[1],"$?")==0)
{
printf("%d,%d
",lastcode,lastsig);
}
else
{
printf("%s
",myargv[i]);
}
continue;
}
//利用条件编译测试是否成功
#ifdefDEBUG
for(inti=0;myargv[i];++i)
{
printf("myargv[%d]:%s
",i,myargv[i]);
}
#endif
//执行命令
pid_tid=fork();
assert(id!=-1);
if(id==0)
{
execvp(myargv[0],myargv);
exit(1);
}
intstatus=0;
pid_tret=waitpid(id,&status,0);
assert(ret>0);
(void)ret;
lastcode=(status>>8)&0xFF;
lastsig=status&0x7F;
}
return0;
}

审核编辑:汤梓红
-
Linux
+关注
关注
88文章
11628浏览量
217946 -
命令行
+关注
关注
0文章
81浏览量
10697 -
Shell
+关注
关注
1文章
373浏览量
25169 -
进程
+关注
关注
0文章
208浏览量
14478 -
解释器
+关注
关注
0文章
103浏览量
6935
原文标题:Linux 实现简易的 Shell 命令行解释器
文章出处:【微信号:良许Linux,微信公众号:良许Linux】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
在STM32实现命令行
Linux图形界面的原理与构成和Linux命令行和vi编辑器的使用手册
Linux桌面系统初级教程之Shell命令行操作的资料概述
Linux 命令行教程好书推荐
mini shell命令行调试工具(单片机、c语言)

Linux实现简易的Shell命令行解释器
评论