模拟实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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];
// shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令
int main()
{
//0. 命令行解释器,一定是一个常驻内存的进程,不退出
while(1)
{
//1. 打印出提示信息 [whb@localhost myshell]#
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';
//"ls -a -l -i\n\0"
//printf("echo: %s\n", cmd_line);
//3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"
g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
int index = 1;
if(strcmp(g_argv[0], "ls") == 0)
{
g_argv[index++] = "--color=auto";
}
if(strcmp(g_argv[0], "ll") == 0)
{
g_argv[0] = "ls";
g_argv[index++] = "-l";
g_argv[index++] = "--color=auto";
}
//?
while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULL
//for debug
//for(index = 0; g_argv[index]; index++)
// printf("g_argv[%d]: %s\n", index, g_argv[index]);
//4. TODO,内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
//内建命令本质其实就是shell中的一个函数调用
if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute
{
if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..
continue;
}
//5. fork()
pid_t id = fork();
if(id == 0) //child
{
printf("下面功能让子进程进行的\n");
//cd cmd , current child path
execvp(g_argv[0], g_argv); // ls -a -l -i
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
}
}
TODO,内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
内建命令本质其实就是shell中的一个函数调用。比如cd
. shell执行的命令通常有两种
1.第三方提供的对应的在磁盘中有具体二进制文件的可执行程序文由子进程执行)
2. shell内部,自己实现的方法,由自己_(父进程)来进行执行 (shell 代表的是用户)
有些命令就是要影响shell本身的 例如cd,export
shell 的环境变量,又是从哪里来的呢??
环境变量,是写在配置文件中的,shell启动的时候,通过读取配置文件获得的起始环境变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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];
// 写一个环境变量的buffer,用来测试
char g_myval[64];
// shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令
int main()
{
extern char**environ;
//0. 命令行解释器,一定是一个常驻内存的进程,不退出
while(1)
{
//1. 打印出提示信息 [whb@localhost myshell]#
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';
//"ls -a -l -i\n\0"
//printf("echo: %s\n", cmd_line);
//3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"
// export myval=105
g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
int index = 1;
if(strcmp(g_argv[0], "ls") == 0)
{
g_argv[index++] = "--color=auto";
}
if(strcmp(g_argv[0], "ll") == 0)
{
g_argv[0] = "ls";
g_argv[index++] = "-l";
g_argv[index++] = "--color=auto";
}
//?
while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULL
if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
{
strcpy(g_myval, g_argv[1]);
int ret = putenv(g_myval);
if(ret == 0) printf("%s export success\n", g_argv[1]);
//for(int i = 0; environ[i]; i++)
// printf("%d: %s\n", i, environ[i]);
continue;
}
//for debug
//for(index = 0; g_argv[index]; index++)
// printf("g_argv[%d]: %s\n", index, g_argv[index]);
//4.内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
//内建命令本质其实就是shell中的一个函数调用
if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute
{
if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..
continue;
}
//5. fork()
pid_t id = fork();
if(id == 0) //child
{
printf("下面功能让子进程进行的\n");
printf("child, MYVAL: %s\n", getenv("MYVAL"));
printf("child, PATH: %s\n", getenv("PATH"));
//cd cmd , current child path
//execvpe(g_argv[0], g_argv, environ); // ls -a -l -i
//不是说好的程序替换会替换代码和数据吗??
//环境变量相关的数据,会被替换吗??没有!
execvp(g_argv[0], g_argv); // ls -a -l -i
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
}
}
这里出现了一个比较难的BUG。在环境变量这里,我们发现父进程可以获取到环境变量,但是呢,子进程(替换程序)却无法拿到环境变量???
问题在在哪呢??
首先我们明白的一点是环境变量是一个指针数据,而并不是把我们要增加的那个环境变量的字符串添加到那个字符串中,那在这个程序中我们的环境变量指针其实是指在cmd_line 里的那个环境变量字符串,当第一轮进程export 环境变量之后,我们的输入缓冲区会进行一次刷新,导致原来的那个指针变为空。导致环境变量无法传递。