1. 实现思路
考虑下面这个与shell
典型的互动:
[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps
用下面的时间轴来表示事件的发生次序,从左向右,shell
从用户读入字符串"ls"
,shell
建立一个新的进程,然后在那个进程中运行ls
程序并等待那个进程结束.
然后shell
读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个进程结束,所以就是一下过程的循环:
- 获取命令行
- 解析命令行
- 建立一个子进程(
fork
) - 替换子进程(
execvp
) - 父进程等待子进程退出
2. 代码实现
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#define NUM 1024
#define SIZE 128
#define SEP " "
char command_line[NUM];
char* command_args[SIZE];
char env_buffer[NUM];
int ChangeDir(const char* new_path)
{
chdir(new_path);
return 0; //调用成功
}
void PutEnvInMyShell(char* new_env)
{
putenv(new_env);
}
int main()
{
//shell 本质就是一个死循环
while(1)
{
//1. 显示提示符
printf("[smf@我的主机名 当前目录]# ");
fflush(stdout);
//2. 获取用户输入
memset(command_line, '\0', sizeof(command_line));
fgets(command_line, NUM, stdin); //从stdin中读NUM长度的字符串到command_line中
command_line[strlen(command_line) - 1] = '\0'; //清空'\n'
//3. "la -a -l" -> "ls" "-a" "-l"字符串切分
command_args[0] = strtok(command_line, SEP);
int index = 1;
//给ls命令添加颜色
if(strcmp(command_args[0], "ls") == 0)
command_args[index++] = (char*)"--color=auto";
while(command_args[index++] = strtok(NULL, SEP));
//4. 执行、内建命令
if(strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL)
{
ChangeDir(command_args[1]); //让父进程进行路径切换
continue;
}
if(strcmp(command_args[0], "export") == 0 && command_args[1] != NULL)
{
strcpy(env_buffer, command_args[1]);
PutEnvInMyShell(env_buffer);
continue;
}
//5. 创建进程、执行
pid_t id = fork();
if(id == 0)
{
//child 进行程序替换
execvp(command_args[0], command_args);
exit(1);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf("等待子进程成功, sig: %d, code: %d\n", status&0x7F, (status>>8)&0xFF);
}
}
}