shell的模拟实现
我们知道shell是一个永不退出的程序,所以他应该是一个死循环,并且shell为了防止影响到自己,我们在命令行上输入的所有命令都是由shell的子进程来执行的,所以它应该要有创建子进程的相关函数,当然也会有进程替换的相关函数,因为我们直接创建子进程,父子进程是共享代码的,如果没有进程替换,shell根本无法让子进程执行特定的命令。
总的思路:
1.打印提示符 && 获取用户命令字符串
2.分割字符串
//“ls -a -l” —> “ls” “-a” “-l”
3.check buildin command 检查内键命令
4.执行命令
1.打印提示符 && 获取用户命令字符串
对于getUserCommand函数:
先介绍fgets函数:
#define NUM 1024
#define SIZE 64int getUserCommand(char *command,int num)
{printf("[%s@%s %s]",getUsername(),getHostname(),getCwd());//不用scanf,遇到空格会停,不好用char *r = fgets(command,num,stdin);//最终还是会输入\nif(r == NULL) return -1;//abcd\ncommand[strlen(command) - 1] = '\0';//给最后加上\0return strlen(command);
}
getUsername(),getHostname(),getCwd() 是为了读取用户名@主机名 当前所在文件夹
2.分割字符串
比如我要执行ls命令,ls命令后还可以携带选项,ls -a -l,shell需要获取指令与选项,并将它们分割(shell接收到的是一串字符串"ls -a -l",shell需要将字符串分割为更小的字符串"ls" “-a” “-l”)。
定义char usercommand[NUM];接收一整行的指令,该数组中的元素保存的是字符,char *argv[SIZE];//存储命令拆分后结果,该数组中的元素要保存的是分割后的字符串。
介绍strtok:
void commandSplit(char *in,char *out[])
{int argc = 0;out[argc++] = strtok(in," ");//分隔并放入out指针数组while(out[argc++] = strtok(NULL," "));
}
3.check buildin command 检查内键命令
内建命令:
开头说到,shell的命令分为内部命令和其他命令,何为内建命令? 内建命令是一个需要shell自己执行的命令,即shell不创建子进程,自己亲自执行的命令。
shell对于一些命令是必须要亲自执行的:比如cd更改工作路径,将shell的工作路径修改后,由于子进程的工作路径与父进程相同,更改分进程的工作路径后,父进程创建出的子进程的工作路径也是被修改过的,体现给用户的感觉就是当前的工作路径改变了。
对于内建命令,shell是怎么实现的? shell中有许多内建命令,当shell接收到指令并解析后,需要判断用户输入的命令是否为内建命令,如果是就执行拦截操作,使子进程不再被创建,自己执行该指令。如果不是内建命令,则创建子进程执行该命令。
内建命令太多,代码里面只写了cd和export内建命令。
char cwd[1024];//全局变量
char enval[1024];//for test char *homepath()
{char *home = getenv("HOME");if(home) return home;else return (char*)".";
}void cd(const char *path)
{chdir(path);//chdir(),用户将当前的工作目录改变成以参数路径所指的目录char tmp[1024];getcwd(tmp,sizeof(tmp));//getcwd()会将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,参数size为buf的空间大小。sprintf(cwd,"PWD=%s",tmp); //打印到字符串中putenv(cwd);//putenv 函数会将cwd 直接填写到环境表中
}// 什么叫做内键命令: 内建命令就是bash自己执行的,类似于自己内部的一个函数!
// 1->yes, 0->no, -1->err
int doBuildin(char* argv[])
{if(strcmp(argv[0],"cd") == 0) //cd ...{char *path = NULL;if(argv[1] == NULL) path = homepath();//纯cd,回到HOMEelse path = argv[1];cd(path);//进入要进入的路径return 1;}else if(strcmp(argv[0],"export") == 0){if(argv[1] == NULL) return 1;strcpy(enval,argv[1]);//拷贝putenv(enval);//直接填写到环境表中return 1;}return 0;
}
对于void cd(const char *path),用 chdir() 函数来将 用户当前的工作目录改变成以参数路径所指的目录,
用 getcwd() 函数将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,
用 sprintf() 函数将 “PWD=工作目录” 字符串打印给cwd,
用putenv 将cwd 直接填写到环境表中。
4.执行命令
int execute(char *argv[])
{pid_t id = fork();if(id < 0) return -1;else if(id == 0) //child{//exec commandexecvp(argv[0],argv);//execvp()会从环境变量所指的目录中查找符合参数 file 的文件名, 找到后执行该文件, 然后将第二个参数argv 传给该执行的文件。exit(1);}else //father{int status = 0;pid_t res = waitpid(-1, &status, 0);//阻塞式等待if(res > 0)//等待成功{printf("exit code: %d \n", WEXITSTATUS(status));}}return 0;
}
用子进程进行进程替换,执行命令:
在进行进程替换时,使用ececvp()函数:
效果动态图
代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>#define NUM 1024
#define SIZE 64char cwd[1024];
char enval[1024];//for test char *homepath()
{char *home = getenv("HOME");if(home) return home;else return (char*)".";
}const char *getUsername()
{const char *name = getenv("USER");if(name) return name;else return "none";
}const char *getHostname()
{const char *hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "none";
}const char *getCwd()
{const char *cwd = getenv("PWD");if(cwd) return cwd;else return "none";
}int getUserCommand(char *command,int num)
{printf("[%s@%s %s]",getUsername(),getHostname(),getCwd());//不用scanf,遇到空格会停,不好用char *r = fgets(command,num,stdin);//最终还是会输入\nif(r == NULL) return -1;//abcd\ncommand[strlen(command) - 1] = '\0';return strlen(command);
}void commandSplit(char *in,char *out[])
{int argc = 0;out[argc++] = strtok(in," ");//分隔并放入out指针数组while(out[argc++] = strtok(NULL," "));
}void cd(const char *path)
{chdir(path);//chdir(),用户将当前的工作目录改变成以参数路径所指的目录char tmp[1024];getcwd(tmp,sizeof(tmp));//getcwd()会将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,参数size为buf的空间大小。sprintf(cwd,"PWD=%s",tmp); //打印到字符串中putenv(cwd);//putenv 函数会将c wd 直接填写到环境表中
}// 什么叫做内键命令: 内建命令就是bash自己执行的,类似于自己内部的一个函数!
// 1->yes, 0->no, -1->err
int doBuildin(char* argv[])
{if(strcmp(argv[0],"cd") == 0) //cd ...{char *path = NULL;if(argv[1] == NULL) path = homepath();else path = argv[1];cd(path);return 1;}else if(strcmp(argv[0],"export") == 0){if(argv[1] == NULL) return 1;strcpy(enval,argv[1]);putenv(enval);return 1;}return 0;
}int execute(char *argv[])
{pid_t id = fork();if(id < 0) return -1;else if(id == 0) //child{//exec commandexecvp(argv[0],argv);//execvp()会从环境变量所指的目录中查找符合参数 file 的文件名, 找到后执行该文件, 然后将第二个参数argv 传给该执行的文件。exit(1);}else //father{int status = 0;pid_t res = waitpid(-1, &status, 0);//阻塞式等待if(res > 0)//等待成功{printf("exit code: %d \n", WEXITSTATUS(status));}}return 0;
}int main()
{while(1){char usercommand[NUM];//存储命令char *argv[SIZE];//存储命令拆分后结果//1.打印提示符 && 获取用户命令字符串int n = getUserCommand(usercommand,sizeof(usercommand));if(n <= 0) continue;//输入空也可以重新输入//2.分割字符串//"ls -a -l" ---> "ls" "-a" "-l"commandSplit(usercommand,argv);//3.check buildin command 检查内键命令n = doBuildin(argv);if(n) continue;//4.执行命令execute(argv);}
}