考虑下面这个与shell典型的互动:
[root@localhost myshell]# ls
makefile shell shell.c[root@localhost myshell]# ps
PID TTY TIME CMD
13702 pts/0 00:00:00 bash
17271 pts/0 00:00:00 shell
17285 pts/0 00:00:00 ps
- 用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左
向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结
束。
- 然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:- 获取命令行
- 解析命令行
- 建立一个子进程(fork)
- 替换子进程(execvp)
- 父进程等待子进程退出(wait)
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
char cmd_line[NUM];
char* g_argv[SIZE];
int main()
{
//shell 运行原理:通过让子进程执行命令,父进程等待&&解析指令
while(1)
{
//1.打印提示信息
printf("[root@localhost myshell]# ");
fflush(stdout);
memset(cmd_line, '\0', sizeof cmd_line);
//2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]
if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL) continue;
cmd_line[strlen(cmd_line)-1]='\0';
//3. 命令行解释
int index=1;
g_argv[0]=strtok(cmd_line, SEP);
while(g_argv[index++] = strtok(NULL, SEP));
// 4. TODO,内置命令
if(strcmp(g_argv[0], "cd") == 0)
{
if(g_argv[1] != NULL) chdir(g_argv[1]);
continue;
}
// 5. fork()
pid_t id = fork();
if(id == 0)
{//child
printf("子进程开始运行\n");
execvp(g_argv[0], g_argv);
exit(1);
}
//father
int status=0;
pid_t ret = waitpid(id, &status, 0);
if(ret>0) printf("exit code: %d\n", WEXITSTATUS(status));
}
return 0;
}
提醒:
内置命令,TODO, 让父进程(shell)自己执行的命令,我们叫做内置命令。本质其实就是shell中的一个函数调用
打印提示信息的时候注意冲刷缓存区,因为遇到\n或者点Enter键才出来。