shell
- 功能描述
- 思路介绍
- 1.实现常驻进程功能
- 2.实现命令读取功能
- 3. 实现命令解析功能
- 4.实现子进程执行命令功能
- 5.完善功能
- 补充内容
- 让父进程运行内置命令
- 实现子进程能够获得父进程的环境变量功能(export命令)
- shell实现重定向功能
- 全部代码如下:
功能描述
实现一个类似于shell的命令行解释器。通过让子进程执行命令,父进程等待等待并解析命令,从而可以执行类似于“ls”,“ls -a -l -i”,'pwd"等linux指令。
思路介绍
1.实现常驻进程功能
在这里将要实现一个死循环,并且打印出提示信息。
while(1)
{//命令行解释器一定是一个常驻内存的进程,不退出//打印出提示信息[]23 printf("[xty@localhost myshell]#] ");24 fflush(stdout);
}
运行如图:
2.实现命令读取功能
使用fgets函数读取输入的内容,注意要注意把回车给删除(因为fgets会把回车也读取进来)
#define NUM 1024//保存完整的命令字符串char cmd_line[NUM];//2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]memset(cmd_line, '\0', sizeof(cmd_line));if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1] = '\0';//发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个//printf("echo:%s \n", cmd_line);
3. 实现命令解析功能
需要将我们输入的命令行字符串,变成shell能理解的语言。
将命令行选项使用strtok分开,把 "ls -a -l"变成 “ls”, “-a”, “-l”, “NULL”。
为后面的execvp作准备。
#define SIZE 32
//保存打散之后的命令行字符串
char *g_argv[SIZE];//3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"
g_argv[0] = strtok(cmd_line, " ");
int index = 1;
while(g_argv[index++] = strtok(NULL, " "));//检查一下g_argv对不对
for(index = 0; g_argv[index];index++)
{//虽然存入的是地址,但是%s,会将它看成字符串打印出来printf("g_argv[%d] = %s\n", index, g_argv[index]);
}
结果如下:
4.实现子进程执行命令功能
子进程执行命令,父进程等待子进程返回。
//4.让子进程执行命令,执行完后给父进程返回值pid_t id = fork();if(id==0){//子进程,执行命令 printf("子进程开始执行任务\n"); execvp(g_argv[0], g_argv);exit(1);}//fatherint status = 0;pid_t ret = waitpid(id, &status, 0);if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));
5.完善功能
让"ls"有颜色,并且让shell认识"ls"命令。
完整代码如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/wait.h>
6 #include<sys/types.h>
7
8 #define NUM 1024
9 #define SIZE 32
10 //保存完整的命令字符串
11 char cmd_line[NUM];
12 //保存打散之后的命令行字符串
13 char *g_argv[SIZE];
14
15
16 //shell运行原理:通过让子进程执行命令,父进程等待&&解析命令
17 int main()
18 {
19 //0.命令行解释器一定是一个常驻内存的进程,不退出
20 while(1)
21 {
22 //1.打印出提示信息[]
23 printf("[xty@localhost myshell]#] ");
24 fflush(stdout);
25
26 //2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]
27 memset(cmd_line, '\0', sizeof(cmd_line));
28 if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
29 {
30 continue;
31 }
32 if(cmd_line[0] == '\n')
33 {
34 continue;
35 }
36 cmd_line[strlen(cmd_line)-1] = '\0';
37 //发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个
38 //printf("echo:%s \n", cmd_line);
39
40 //3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"
41 g_argv[0] = strtok(cmd_line, " ");
42 int index = 1;
43
44 if(strcmp(g_argv[0], "ls")==0)
45 {
46 g_argv[index++]="--color=auto";
47 }
48 //还可以让编译器支"ll"
49 if(strcmp(g_argv[0],"ll")==0)
50 {
51 g_argv[0] = "ls";
52 g_argv[index++] = "-l";
53 g_argv[index++] = "--color=auto";
54 }
55
56 //让ls命令有颜色
57 while(g_argv[index++] = strtok(NULL, " "));
58
59 //检查一下g_argv对不对
60 for(index = 0; g_argv[index];index++)
61 {
62 //虽然存入的是地址,但是%s,会将它看成字符串打印出来
63 printf("g_argv[%d] = %s\n", index, g_argv[index]);
64 }
65
66 //4.让子进程执行命令,执行完后给父进程返回值
67 pid_t id = fork();
68 if(id==0)
69 {
70 //子进程,执行命令
71 printf("子进程开始执行任务\n");
72 execvp(g_argv[0], g_argv);
73 exit(1);
74 }
75
76 //father
77 int status = 0;
78 pid_t ret = waitpid(id, &status, 0);
79 if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));
80 }
81 return 0;
82 }
这样我们一个简易的shell就完成了。
补充内容
shell执行的命令,通常有两种:
- 第三方提供的对应在磁盘中有具体二进制文件的可执行程序(由子进程执行)
- shell内部,自己实现的方法,由(父进程)来执行,有些命令会影响到shell本身,比如:“cd”,"export"命令等。
让父进程运行内置命令
当我们执行cd命令时,发现程序并没有改变目录。如下图:
原因是:cd命令被子进程执行了,子进程的当前目录被修改了,但是子进程立马就退出了。但是并没有影响到父进程的当前目录,所以第二次再次运行的时候,子进程还是在父进程的目录创建的,因此没有改变。
使用chdir函数改变父进程的工作目录。
代码如下:
66 //让父进程执行cd 命令67 if(strcmp(g_argv[0], "cd")==0)68 {69 if(g_argv[1]!=NULL) chdir(g_argv[1]); // cd .., cd path70 continue; 71 }
结果如下图:
实现子进程能够获得父进程的环境变量功能(export命令)
因export和其他的命令行功能不一样,所以需要再判断一下该功能。并且需要创建新数组保存一下环境变量,因为cmd_line再第二次读取时会清空,会导致getenv时得不到被清空的环境变量(因为存的是指针地址,清空后变成了野指针),因此需要创建数组存储一下!
代码如下:
//myshell.c//写一个环境变量的bufer,用来存储临时的环境变量,防止cmd_line被清空char my_num[64];// export MYNUM=111222333if(strcmp(g_argv[0], "export")==0 && g_argv[1]!=NULL){strcpy(my_num, g_argv[1]);int ret = putenv(my_num);if (ret == 0) printf("%s export sucess\n", my_num); //查看一下导入的是什么continue;}//env_test.c1 #include<stdio.h>2 #include<stdlib.h> 3 4 int main()5 {6 printf("我是测试环境变量是否成功导入进程\n");7 printf("MYNUM= %s \n",getenv("MYNUM"));8 9 }
shell实现重定向功能
先检查是否有重定向的功能,然后再执行命令。
//检查命令模块//定义标志位的含义 22 #define INPUT_REDIR 123 #define OUTPUT_REDIR 224 #define APPEND_REDIR 325 #define NONE_REDIR 026 int redir_status = NONE_REDIR;27 28 char *CheckRedir(char *start)29 {30 assert(start);31 char *end = start + strlen(start) - 1;// ls -a -l32 33 //从后往前找34 while(end >= start)35 {36 if(*end == '>')37 {38 if(*(end - 1) == '>')39 {40 redir_status = APPEND_REDIR;41 *(end - 1) = '\0';42 end++; //重定向后字符串的首地址43 break;44 }45 redir_status = OUTPUT_REDIR;46 *end = '\0';47 end++; // ls -a>myfile.txt ->ls -a\0myflie.txt48 break;49 }50 else if(*end == '<')51 {52 redir_status = INPUT_REDIR;53 *end = '\0';54 end++;55 break;56 }57 else{58 end--;59 }60 }61 if(end>=start)62 {63 return end; //要打开的文件名64 }65 else{66 return NULL; //没有重定向功能67 }68 } //main函数内部,子进程执行命令前,多一个重定向打开文件的逻辑:142 //4.让子进程执行命令,执行完后给父进程返回值143 pid_t id = fork();144 if(id==0)145 {146 //子进程,执行命令 147 printf("子进程开始执行任务\n");148 if(sep!=NULL)149 {150 int fd = -1;151 //有重定向152 switch(redir_status)153 {154 case INPUT_REDIR:155 fd = open(sep, O_RDONLY);156 dup2(fd, 0);157 break;158 case OUTPUT_REDIR:159 fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);160 dup2(fd, 1);161 break;162 case APPEND_REDIR:163 fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);164 dup2(fd, 1);165 break;166 default:167 printf("erro????????\n");168 break;169 }170 }171 172 execvp(g_argv[0], g_argv);173 exit(1);174 }175
全部代码如下:
1 #include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>4 #include<string.h>5 #include<assert.h>6 #include<fcntl.h>7 #include<sys/wait.h>8 #include<sys/stat.h>9 #include<sys/types.h>10 11 #define NUM 102412 #define SIZE 3213 //保存完整的命令字符串14 char cmd_line[NUM];15 //保存打散之后的命令行字符串16 char *g_argv[SIZE];17 18 //写一个环境变量的bufer,用来存储临时的环境变量,防止cmd_line被清空19 char my_num[64];20 21 //定义标志位的含义22 #define INPUT_REDIR 123 #define OUTPUT_REDIR 224 #define APPEND_REDIR 325 #define NONE_REDIR 026 int redir_status = NONE_REDIR;27 28 char *CheckRedir(char *start)29 {30 assert(start);31 char *end = start + strlen(start) - 1;// ls -a -l32 33 //从后往前找34 while(end >= start)35 {36 if(*end == '>')37 {38 if(*(end - 1) == '>')39 {40 redir_status = APPEND_REDIR; 41 *(end - 1) = '\0';42 end++; //重定向后字符串的首地址43 break;44 }45 redir_status = OUTPUT_REDIR;46 *end = '\0';47 end++; // ls -a>myfile.txt ->ls -a\0myflie.txt48 break;49 }50 else if(*end == '<')51 {52 redir_status = INPUT_REDIR;53 *end = '\0';54 end++;55 break;56 }57 else{58 end--;59 }60 }61 if(end>=start)62 {63 return end; //要打开的文件名64 }65 else{66 return NULL; //没有重定向功能67 }68 } 69 70 //shell运行原理:通过让子进程执行命令,父进程等待&&解析命令71 int main()72 {73 //0.命令行解释器一定是一个常驻内存的进程,不退出74 while(1)75 {76 //1.打印出提示信息[]77 printf("[xty@localhost myshell]#] ");78 fflush(stdout);79 80 //2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]81 memset(cmd_line, '\0', sizeof(cmd_line));82 if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)83 {84 continue;85 }86 if(cmd_line[0] == '\n')87 {88 continue;89 }90 cmd_line[strlen(cmd_line)-1] = '\0';91 //发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个92 //printf("echo:%s \n", cmd_line);93 94 95 //在分析指令之前就检查有没有重定向96 char* sep = CheckRedir(cmd_line);97 98 //3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"99 g_argv[0] = strtok(cmd_line, " ");100 int index = 1;101 102 if(strcmp(g_argv[0], "ls")==0)103 {
W>104 g_argv[index++]="--color=auto";105 }106 //还可以让编译器支"ll"107 if(strcmp(g_argv[0],"ll")==0)108 {
W>109 g_argv[0] = "ls";
W>110 g_argv[index++] = "-l";
W>111 g_argv[index++] = "--color=auto";112 }113 114 //让ls命令有颜色
W>115 while(g_argv[index++] = strtok(NULL, " "));116 117 检查一下g_argv对不对118 //for(index = 0; g_argv[index];index++)119 //{120 // //虽然存入的是地址,但是%s,会将它看成字符串打印出来121 // printf("g_argv[%d] = %s\n", index, g_argv[index]);122 //}123 124 125 // export MYNUM=111222333126 if(strcmp(g_argv[0], "export")==0 && g_argv[1]!=NULL)127 {128 strcpy(my_num, g_argv[1]);129 int ret = putenv(my_num);130 if (ret == 0) printf("%s export sucess\n", my_num); //查看一下导入的是什么131 continue;132 }133 134 135 136 //让父进程执行cd 命令137 if(strcmp(g_argv[0], "cd")==0)138 {139 if(g_argv[1]!=NULL) chdir(g_argv[1]); // cd .., cd path140 continue;141 }142 //4.让子进程执行命令,执行完后给父进程返回值143 pid_t id = fork();144 if(id==0)145 {146 //子进程,执行命令 147 printf("子进程开始执行任务\n");148 if(sep!=NULL)149 {150 int fd = -1;151 //有重定向152 switch(redir_status)153 {154 case INPUT_REDIR:155 fd = open(sep, O_RDONLY);156 dup2(fd, 0);157 break;158 case OUTPUT_REDIR:159 fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);160 dup2(fd, 1);161 break;162 case APPEND_REDIR:163 fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);164 dup2(fd, 1);165 break;166 default:167 printf("erro????????\n");168 break;169 }170 }171 172 execvp(g_argv[0], g_argv);173 exit(1);174 }175 176 //father177 int status = 0;178 pid_t ret = waitpid(id, &status, 0);179 if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));180 }181 return 0;182 }