文章目录
- 🦄0. shell
- 🐮1. 交互及获取命令行
- 🐷2. 解析命令行
- 🐯3. 执行命令行
- 🐅3.1 普通命令
- 🐅3.2 内建命令
- 🦁4. 主函数逻辑及演示
本章代码gitee仓库:简易shell
🦄0. shell
shell
是操作系统外的一层外壳程序,负责将用户的指令执行,将指令获取到之后再交给操作系统,操作系统将指令执行完毕之后的结果通过shell
交给用户。shell
/bash
也是一个进程,本质上也是通过创建子进程来执行这些指令。
🐮1. 交互及获取命令行
我们先来开一下交互及我们输入的命令行格式
先来做交互的页面,这其实就是一个while
循环,一直等着我们输入指令,我们可以通过获取环境变量来获取这些信息。
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
const char *getUserName()
{
return getenv("USER");
}
const char *getHostName()
{
return getenv("HOSTNAME");
}
void getPwd()
{
getcwd(pwd,sizeof(pwd));
}
void interact(char *cline, int size)
{
getPwd();
printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getUserName(),getHostName(),pwd);
char *s = fgets(cline,size,stdin); //获取指令
assert(s);
(void)s; //防止后面不使用s变量报警告,假装用一下
cline[strlen(cline)-1] = '\0'; //将回车抵消
//printf("%s\n",s);
}
我们可以输出我们获取的命令测试一下
🐷2. 解析命令行
获取到命令之后,我们就要解析这个命令,这个解析的本质上,就是将获取的字符串进行分割,分割各个部分:要执行的命令
、所带的命令行参数
例如ls -a -l
,我们就需要解析成:
- 所需执行的命令:
ls
- 命令行参数:
-a
、-l
#define DELIM " \t"
int splitString(char cline[], char *_argv[])
{
int i = 0;
_argv[i++] = strtok(cline,DELIM);
while(_argv[i++] = strtok(NULL,DELIM));
return i-1;
}
🐯3. 执行命令行
获取到所需执行的命令和参数之后,其实就是创建子进程,然后程序替换来执行这个命令,但是这些命令分为普通命令和内建命令:
- 普通命令:创建子进程直接程序替换
- 内建命令:父进程自己执行
🐅3.1 普通命令
这里没有什么高科技,就是简单的创建子进程、程序替换和进程等待
#define EXIT_CODE 11
void normalExcute(char *_argv[])
{
pid_t id = fork();
if(id < 0)
{
perror("fork fail");
return;
}
else if(id == 0)
{
//子进程执行命令
//execvpe(_argv[0],_argv,environ); //直接程序替换
execvp(_argv[0],_argv); //直接替换程序
exit(EXIT_CODE); //替换失败的退出码
}
else
{
//父进程等待子进程退出
int status = 0;
pid_t rid = waitpid(id,&status,0); //阻塞等待
if(rid == id)
{
lastcode = WEXITSTATUS(status);
}
}
}
🐅3.2 内建命令
对应内建命令,需要我们自己去一个一个添加然后判断,这里做一个简单的演示
int buildCommand(char *_argv[], int _argc)
{
if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
{
chdir(_argv[1]);
getPwd();
sprintf(getenv("PWD"),pwd);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0],"export") == 0) //导环境变量
{
putenv((char*)_argv[1]);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
{
if(strcmp(_argv[1],"$?")==0)
{
printf("%d\n",lastcode);
lastcode = 0;
}
else if(*_argv[1] == '$')
{
char*val = getenv(_argv[1]+1);
if(val) printf("%s\n",val);
}
else printf("%s\n",_argv[1]);
return 1;
}
//将ls命令显示颜色
if(strcmp(_argv[0],"ls") == 0)
{
_argv[_argc++] = "--color";
_argv[_argc] = NULL;
}
return 0;
}
🦁4. 主函数逻辑及演示
这里我们全部都封装起来了,各个模块解耦,想要修改的话,也很方便,完整的代码可以去仓库里面查看。
#define LINE_SIZE 1024
#define ARGC_SIZE 32
int main()
{
while(!quit)
{
//交互 获取命令行
interact(commandline,sizeof(commandline));
//解析命令行
int argc = splitString(commandline,argv);
if(argc == 0) continue;
//for(int i=0;argv[i];i++) printf("%s\n",argv[i]);
//printf("%s\n",argv);
//普通命令执行
int flag = buildCommand(argv,argc);
if(!flag) normalExcute(argv);
}
return 0;
}
所以我们每次登录的时候,界面会显示这些信息,其实就是因为系统启动了一个shell
进程。