考虑下面这个与shell典型的互动:
用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个进程结束。所以要写一个shell,需要循环以下过程:
1.打印命令提示符[用户名@主机名 当前目录] 提示符(对于普通用户$,对于root用户显示#)
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)
5. 父进程等待子进程退出(wait)
根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define SIZE 256
#define NUM 16
int main()
{
//命令行的获取
char cmd[SIZE];
//这里是可以通过查看系统调用接口来获取这里的用户名、主机名、和当前目录的,但是这里省略了,在后续需要补充
const char* cmd_line = "[temp@VM-8-3-centos lesson_15]# ";
while(1){
cmd[0] = 0; //对于字符串来说以'\0'结束,把第一个位置设置为'\0',其余的就都是了
printf("%s",cmd_line);
//char *fgets(char *s,int size,FILE * stream);//获得一个字符串,放到缓冲区;这个缓冲区有多大;从哪里流方式获得
fgets(cmd,SIZE,stdin);
cmd[strlen(cmd)-1] = '\0';//因为你的fgets在获取字符串的时候,最后将回车敲下之后补了'\0'结束,所以你的字符串的最后是以n\0结束的
//命令行解析 strtok() 把命令行拆解为多个字符串,然后把每个字符串放在一个对于的字符指针数组里面,那么访问这个数组的下标就可以拿到对应的命令行内容
char *args[NUM];
args[0] = strtok(cmd," ");
int i = 1;
do{
args[i] = strtok(NULL," ");
if(args[i] == NULL){
break;
}
++i;
}while(1);
//创建子进程
pid_t id = fork();
if(id < 0){
perror("fork error\n");
continue;
}
if(id == 0){
//child
execvp(args[0],args);
//只要子进程替换失败,就直接让它退出
exit(1);
}
//parent
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret > 0 ){
printf("status exit code : %d\n",(status>>8)&0xff);
}
}
return 0;
}
系统调用获取用户名 、主机名、和当前目录
可以查阅CSDN的centos7如何得到“系统调用获取用户名 、主机名、和当前目录”,目前这里没有找到很好的链接。
需要补充的知识点:
char *fgets(char *s,int size,FILE * stream);
获得一个字符串,放到缓冲区;这个缓冲区有多大;从哪种流方式获得
①fgets()函数
②cmd[strlen(cmd)-1] = '\0';
这句,对于你的命令行来说,你按下回车键作为了结束,但是你的命令行将他识别为了’\n’,所以你的cmd中存储的字符串最终是‘\n\0’结尾的,所以这里你在调用的时候就需要用ls ‘- -’(两个’-‘来执行命令),所以为了避免这样,就把最后一个’\n’的地方替换为’\0’就好了
③strtok字符串函数
字符串详解链接: link.
④continue:再循环体中会跳过剩下的语句,而直接回到最初重新进行程序的判断,break是直接跳出循环体,不再执行