文章目录
- 🦄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 32int 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
进程。