五、进程
12. 自定义shell
根据我们之前学的知识,这里来编写一个 shell 程序。
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #define SIZE 1024 #define MAX_ARGC 64 // 用户名 const char* UserName() { char* username = getenv("USER"); if (username) return username; else return "None"; } // 主机名 const char* HostName() { char* hostname = getenv("HOSTNAME"); if (hostname) return hostname; else return "None"; } // 路径 const char* CurrentWorkDir() { char* currentworkdir = getenv("PWD"); if (currentworkdir) return currentworkdir; else return "None"; }
上面代码可以通过环境变量接口获取指定信息。我们先输出一下命令行提示符试试看。
封装输出命令行提示符的信息和接收用户输入成一个函数。
我们获取到了用户一行输入的整个字符串,现在需要对这个字符串进行切割。
// 分隔符是空格 #define SEP " " void Split(char in[]) { int i = 0; argv[i++] = strtok(in, SEP); while (argv[i++] = strtok(NULL, SEP)); }
当做完以上操作后就可以执行命令了,我们创建一个子进程,让子进程来程序替换为我们要执行的命令。我们封装一下执行命令。
我们不想自己写的shell程序只跑一遍,所以我们要在主函数里加上死循环。
运行比较成功,但是我们发现,使用 cd … 命令却没有反应,这是怎么回事?因为执行cd命令的是子进程而不是我们的shell 。像这种不让子进程来执行的命令,而是让我们的shell执行的命令叫做 内建命令 。我们需要特殊处理一下。
// 获取环境变量 char *Home() { return getenv("HOME"); } int BuildinCmd() { // 是内建命令则返回1,不是则返回0 int ret = 0; if (strcmp("cd", argv[0]) == 0) { ret = 1; char *target = argv[1]; // cd 空表示进入家目录 if (!target) target = Home(); chdir(target); } return ret; }
我们再来测试一下:
这里已经完成的差不多了,再来处理一下细节问题,比如环境变量没有更新。
// 记录路径字符 char pwd[SIZE]; int BuildinCmd() { int ret = 0; if(strcmp("cd", argv[0]) == 0) { ret = 1; char *target = argv[1]; if(!target) target = Home(); chdir(target); char temp[1024]; // 获取当前路径 getcwd(temp, 1024); // 写入数组 snprintf(pwd, SIZE, "PWD=%s", temp); // 更新 putenv(pwd); } }
再来测试一下:
比较完善了。但是实际上内建命令绝对不止 cd 命令,至少还包含 export 和 echo 等命令。就不再一一赘述了。感兴趣的可以自己再去完善,也可以使ls命令带上颜色等等。
完整程序
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #define SIZE 1024 #define MAX_ARGC 64 #define SEP " " char *argv[MAX_ARGC]; char pwd[SIZE]; // 用户名 const char* UserName() { char* username = getenv("USER"); if (username) return username; else return "None"; } // 主机名 const char* HostName() { char* hostname = getenv("HOSTNAME"); if (hostname) return hostname; else return "None"; } // 路径 const char* CurrentWorkDir() { char* currentworkdir = getenv("PWD"); if (currentworkdir) return currentworkdir; else return "None"; } // 家目录 char* Home() { return getenv("HOME"); } int Interactive(char out[], int size) { // 输出命令行提示符 printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir()); // 接收用户输入 fgets(out, size, stdin); // 去掉最后一个 回车符 out[strlen(out) - 1] = '\0'; return strlen(out); } void Split(char in[]) { int i = 0; argv[i++] = strtok(in, SEP); while (argv[i++] = strtok(NULL, SEP)); } void Execute() { pid_t id = fork(); if(id == 0) { // 让子进程执行命令 文件名 + 选项数组 execvp(argv[0], argv); exit(1); } waitpid(id, NULL, 0); } int BuildinCmd() { // 是内建命令则返回1,不是则返回0 int ret = 0; if (strcmp("cd", argv[0]) == 0) { ret = 1; char *target = argv[1]; // cd 空表示进入家目录 if (!target) target = Home(); chdir(target); char temp[SIZE]; getcwd(temp, SIZE); snprintf(pwd, SIZE, "PWD=%s", temp); putenv(pwd); } return ret; } int main() { while(1) { char commandline[SIZE]; int n = Interactive(commandline, SIZE); // 空串跳出 if (n == 0) continue; Split(commandline); n = BuildinCmd(); if (n) continue; Execute(); } return 0; }