🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
一、shell的简单实现-C
shell运行的原理:子进程执行命令,父进程等待&&解析命令
分为五步进行执行
1.打印$前面的信息
2.读取用户输入命令
3.分割命令进行解析
4.判断TODO(比如cd)
5.创建子进程进行进程程序替换来执行命令,父进程接收
打印命令行提示符的缓冲区问题
printf("[zhupi@localhost myshell]# ");
fflush(stdout);
memset(cmd_line,0,sizeof cmd_line);
直接打印没有\n,会先放在缓冲区,缓冲区存放到一定大小才会刷新
可以通过fflush(stdout);来刷新缓冲区
命令行字符解析
思路1:写一个指针数组,空格置为\0就可以了,多个指针指向子字符串起始位置
思路2:C语言的strtok函数(string token字符串分割)
char*strtok(char*str,const char*delim)//delim是需要的分隔符
比如分割"ls -a -l -i"
#define SEP " "//分隔符
char*g_argv[NUM];//#define NUM 32
g_argv[0]=strtok(cmd_line,SEP);
int index=1;
while(g_argv[index++]=strtok(NULL,SEP));
//strtok如果解析的是相同字符串,那么后面第一个参数是NULL,不然又重新开始了
//解析到最后返回的是NULL
子进程使用exec系列函数的选择
首先,不知道命令的位置,所以需要去PATH环境变量找,其次,g_argv已经有了命令分割出来的指针数组,所以综合看来选择execvp
p表示环境变量,v可以看做需要传一个数组
cd切换路径的问题
系统调用接口chdir(change working director)
if(strcmp(g_argv[0],"cd")==0)
{
if(g_argv[1] chdir(argv[1]));
//argv[0]是命令,argv[1]是路径,因为它是cd
}
TODO
shell执行的命令有两种
1.第三方提供的在磁盘中的二进制可执行程序,比如ls,top等等,只需要 程序替换来调用即可执行
2.shell内部的命令,这些需要由父进程来执行,因为有些命令是要影响shell本身的,比如cd,export等等
这些命令就叫做TODO,内置命令,内建命令
因为子进程是不能够影响父进程的,如果cd由子进程来切换路径,那么只有他自己能看到修改后的路径,如果export由子进程来添加环境变量,那么只有它自己能看到添加的环境变量
#include <stdlib.h>
char*getenv(const char*name);//根据环境变量名获取环境变量
int putenv(char*string);//修改环境变量 MYVAL=101
所以说,可以 在分割完命令之后,进行TODO的判断,让父进程进行执行
if(strcmp(g_argv[0],"export")==0&&g_argv[1]!=NULL)
{
strcpy(g_myval/*存储设置的环境变量,因为cmd_line会被清理带哦*/,g_argv[1]);
putenv(g_myval);
continue;//continue之后,下一次输入命令就会清空掉cmd_line,所以需要先保存
}
因为myshell是shell的子进程,所以是不能影响shell的环境变量的,所以用shell来看看不到,其次,进程程序替换是会替换掉代码和数据,但是有特例,比如环境变量就不会被替换(与系统相关的东西)
二、完整代码-C
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define NUM 1024
#define SISE 32
#defien SEP " "//分隔符
char cmd_line[NUM];//命令读取
char*g_argv[SIZE];//命令分割指针数组
char g_myval[64];//用于保存设置的环境变量
int main()
{
//extern char** environ;
while(1)
{
printf("[zhupi@localhost myshell]# ");
fflush(stdout);
memset(cmd_line,0,sizeof cmd_line);//sizeof(cmd_line) sizeof cmd_line
//等待输入命令
if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
{
//fgets输入少了没关系,输入多了会受限制
continue;
}
cmd_line[strlen(cmd_line)-1]=0;//去掉最后的回车
//解析 strtok
g_argv[0]=strtok(cmd_line,SEP);
int index=1;
if(strcmp(g_argc[0],"ls")==0)
{
g_argv[index++]="-color=auto";//如果是ls,添加自动配色
}
while(g_argv[index++]=strtok(NULL,SEP));
if(strcmp(g_argv[0],"export")==0&&g_argv[1]!=NULL)
{
strcpy(g_myval/*存储设置的环境变量,因为cmd_line会被清理带哦*/,g_argv[1]);
putenv(g_myval);
continue;//continue之后,下一次输入命令就会清空掉cmd_line,所以需要先保存
}
if(strcmp(g_argv[0],"cd")==0)
{
if(g_argv[1]!=NULL) chdir(g_argv[1]);//g_argv[1]是path
//chdir进行路径切换
}
pid_t id=fork();
if(id==0)
{
printf("下面的功能让子进程进行");
//printf("child:%s\n",getenv["环境变量名"]);
execvp(g_argv[0],g_argv);//进行进程程序替换
//p表示在环境变量中查找
exit(1);//替换成功将不执行,失败则进程退出,返回1,表示执行出错
}
//子进程 需要程序替换,所以父进程不用else来判断
int status=0;
pid_t ret =waitpid(id,&status,0);
if(ret) printf("exit code:%d\n",WEXITSTATUS(status));//宏
}
}