1. 思路
在学习了linux进程概念和进程控制以后,简单的来实现一个minishell。
- 启动一个父进程,用以从标准输入里面读取用户输入的命令行内容
- 由父进程解析用户输入的内容中,哪些是命令,哪些是命令行参数,存放进argv数组中
- 父进程fork一个子进程,让子进程进行程序替换(execvp),执行用户输入的命令,对命令参数为NULL,或替换失败的子进程进行退出。
- 父进程阻塞的等待子进程退出(wait),待子进程退出后,再去循环获取用户输入
思路图示:
2. 实现代码
2.1 头文件及宏定义部分
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#define MAX_CMD 1024
char command[MAX_CMD];
2.2 获取用户输入部分
2.2.1 用fgets()获取
int get_command()
{
memset(command,0,MAX_CMD);//初始化数组
printf("[minishell@localhost ~]");
fflush(stdout); //刷新缓冲区
if(fgets(command,sizeof(command)-1,stdin) == NULL) //stdin 标准输入
{
return -1; //没有读取到内容则返回-1
}
return 0;
}
2.2.2 用scanf()获取
int get_command() // 读取用户输入
{
memset(command,0,MAX_CMD);
printf("[minishell@localhost ~]");
fflush(stdout);
/*
* %[^\n] 表示读入一行字符串,遇到\n结束
* %*c 表示清理输入缓冲区第一个字符,也就是上次遗留下的\n
*/
if(scanf("%[^\n]%*c",command) == 0) //scanf读取输入,如果没有读取到输入就返回-1
{
getchar();
return -1;
}
return 0;
}
2.3 解析命令行部分
char **do_parse(char *buff) // 处理输入的字符数组
{
int argc = 0;
static char *argv[32]; // 创建一个指针数组
char *ptr = buff;// 定义ptr指向buff字符数组
while(*ptr != '\0')
{
if(!isspace(*ptr))
{
argv[argc++] = ptr;//如果*ptr不为空格,则将ptr这个指针保存到argv数组中
while(!isspace(*ptr) && *ptr != '\0')
{
ptr++; // 直到遇到空格停下
}
}else{
while(isspace(*ptr)) // 将字符数组中空格字符赋值为'\0'
{
*ptr = '\0';
ptr++;
}
}
}
argv[argc] = NULL; // 因为最后要传给exec函数的可变参数数组argv要以NULL结尾,所以给所存数据的后一个位置赋值NULL
return argv; //返回该指针数组
}
上面的while循环也可以这样写,作用一样:
while(*ptr != '\0')
{
while(!isspace(*ptr) && *ptr != '\0')
{
argv[argc++] = ptr;
while(!isspace(*ptr) && *ptr != '\0')
{
ptr++;
}
*ptr = '\0';
}
ptr++;
}
2.4 创建子进程并进行替换部分
int do_myexec(char *buff)
{
char **argv = do_parse(buff); // 定义一个二级指针指向存放命令及参数的argv
pid_t pid = fork(); // 创建一个子进程
if(pid < 0 )
{
perror("fork");
}else if(pid == 0){
//child 进行程序替换
if(argv[0] == NULL) // 如果传入的参数为空,子进程退出
{
exit(-1);
}
int ret = execvp(argv[0],argv); // 数组传参,带p表示会从环境变量找,传入程序名称即可,argv的第一个变量即为命令名称
if(ret < 0) //返回值小于0,这说明替换失败了
{
printf("bash: %s: 未找到命令...\n",argv[0]);
exit(0); // 让替换失败的子进程退出
}
}else{
// parent
waitpid(pid,NULL,0);// 父进程阻塞等待子进程退出
//wait(NULL); //用这个与waitpid效果一致
}
return 0;
}
2.5 main函数部分
int main()
{
while(1)
{
if(get_command() < 0)
{
continue; // 如果没有读取到输入,则跳过继续读取
}
do_myexec(command); // 进行替换
}
return 0;
}
3. 运行结果
如果父进程不阻塞等待子进程:
父进程阻塞等待子进程:
传入的可执行程序所在路径不再环境变量中时: