1.实验目的
探索、理解并掌握操作系统命令解释器的设计原理和实现机制,基于 Linux 内核进行相
应命令解释程序的设计和实现,并在 Linux 操作系统平台上加以测试验证。
2.实验内容
分析、设计与实现基于Linux 内核的命令解释程序(Shell),主要包括系统环境变量的设置和初始化、系统命令提示符显示、命令辨别解析(区分内部命令与外部命令及不同内部命令)、典型内部命令(譬如显示指定目录下文件列表、显示文本文件内容、文件拷贝、文件删除、空文件创建、日期设置/显示)处理等功能,并在 Linux 操作系统上测试验证。
3.实验要求
Linux 命令解释程序功能设计要求:
(1)选取和设计实现一组内部命令(五条以上);
(2)外部命令执行采用直接调用 exec 系统调用的方式来实现;
(3)至少一条内部命令采用直接调用相应系统调用的方式来实现;
(4)系统环境变量(至少包括用户主目录 HOME 和可执行程序搜索路径目录 PATH)支持;
(5)在 Linux操作系统上启用(或替换原命令解释程序 Shell)并测试验证。
4.实验环境
开发环境 | Ubuntu、Visual Studio Code |
运行环境 | GCC、linux shell |
测试环境 | 终端(笔记本) |
5.实验设计与实现
5.1实验总体设计
5.1.1命令设计
本次实验设计了一组有8条内部命令并能执行外部命令的的命令解释程序,其中5条内部命令为系统直接调用。
其中5条系统直接调用的内部命令为:
命令 | 功能 |
my_cd | 切换目录 |
my_pwd | 打印当前目录 |
my_mkdir | 创建目录 |
my_rmdir | 删除目录 |
my_env | 显示系统环境变量 |
其余3条内部命令为:
命令 | 功能 |
my_echo | 打印用户输入 |
my_cat | 读取并打印文件 |
my_write | 写入或改写文件内容 |
5.1.2程序总体设计
执行外部命令的程序设计如下:
程序总体设计如下:
5.1.3函数设计
- main()
功能:程序的主入口,处理用户输入和命令执行。
说明:此函数使用循环来不断接收用户输入并调用 execute_command 函数处理命令。当用户输入 "exit" 时,程序退出。
int main(){
print_menu();
char input[256];
while(1){
// 获取用户输入
fgets(input, 256, stdin);
input[strcspn(input, "\n")] = 0; // 去除换行符
// 输入exit,退出
if(strcmp(input, "exit") == 0){
break;
}
execute_command(input); // 解析并执行命令
print_line();
}
return 0;
}
- print_menu()
功能:打印程序支持的命令列表。
说明:此函数不涉及系统调用,它仅使用 printf 函数来显示菜单。
// 打印菜单
void print_menu(){
printf("-------------------my_shell------------------\n");
printf("输入“my_cd”命令:切换目录\n");
printf("输入“my_pwd”命令:打印当前目录\n");
printf("输入“my_echo”命令:打印用户输入\n");
printf("输入“my_mkdir”命令:创建目录\n");
printf("输入“my_rmdir”命令:删除目录\n");
printf("输入“my_cat”命令:打开并读取文件内容\n");
printf("输入“my_write”命令:写入文件内容\n");
printf("输入“my_env”命令:显示系统环境变量\n");
printf("---------------------------------------------\n");
}
- handle_cd(char *args[])
功能:处理目录切换命令。
说明:
系统调用:chdir() 用于改变当前工作目录。
参数:
args[]:命令行参数数组。args[1] 应包含要切换到的目录路径。
1.// my_cd内部命令,切换目录
2.void handle_cd(char *args[]){
3. if(args[1] == NULL){
4. //
5. chdir(getenv("HOME")); // 切换到 HOME 目录
6. } else if(chdir(args[1]) == 0){
7. printf("目录切换成功,当前在 %s\n", args[1]);
8. } else {
9. perror("你输入的路径错误!\n");
10. }
11.}
- handle_pwd()
功能:打印当前工作目录。
说明:
系统调用:getcwd() 用于获取当前工作目录的路径。
参数:
cwd:字符数组,用于存储当前工作目录的路径。
sizeof(cwd):cwd 数组的大小。
1.// my_pwd命令:打印当前目录
2.void handle_pwd(){
3. char cwd[1024];
4. if(getcwd(cwd, sizeof(cwd)) != NULL){
5. printf("%s\n", cwd);
6. } else {
7. perror("获取目录失败!");
8. }
}
- handle_echo(char *args[])
功能:回显用户输入的命令。
说明:此函数不涉及系统调用,使用循环和 printf 函数输出所有参数。
1.// my_echo命令:打印用户输入(可打印空格)
2.void handle_echo(char *args[]){
3. for(int i = 1; args[i] != NULL; i++){
4. printf("%s ", args[i]);
5. }
6. printf("\n");
7.}
- handle_mkdir(char *args[])
功能:创建新目录。
说明:
系统调用:mkdir() 用于创建一个新目录。
参数:
args[1]:要创建的目录名。
0777:设置新目录的权限。
1.// my_mkdir命令:创建目录
2.void handle_mkdir(char *args[]){
3. if(args[1] == NULL){
4. fprintf(stderr, "my_mkdir: missing operand\n");
5. } else if(mkdir(args[1], 0777) == 0){
6. printf("创建成功!\n");
7. } else {
8. perror("创建失败!");
9. }
10.}
- handle_rmdir(char *args[])
功能:删除空目录。
说明:
系统调用:rmdir() 用于删除一个空目录。
参数:
args[1]:要删除的目录名。
1.// my_mkdir命令:创建目录
2.void handle_mkdir(char *args[]){
3. if(args[1] == NULL){
4. fprintf(stderr, "my_mkdir: missing operand\n");
5. } else if(mkdir(args[1], 0777) == 0){
6. printf("创建成功!\n");
7. } else {
8. perror("创建失败!");
9. }
10.}
- handle_cat(char *args[])
功能:读取并显示文件内容。
说明:
系统调用:fopen() 和 fgets() 用于打开文件并逐行读取内容。
参数:
args[1]:要读取的文件名。
line:用于存储从文件中读取的每一行数据。
1.// my_cat 命令:读取并打印文件内容
2.void handle_cat(char *args[]) {
3. if (args[1] == NULL) {
4. fprintf(stderr, "my_cat: missing operand\n");
5. return;
6. }
7.
8. FILE *file = fopen(args[1], "r");
9. if (file == NULL) {
10. perror("无法打开文件");
11. return;
12. }
13.
14. char line[256];
15. while (fgets(line, sizeof(line), file) != NULL) {
16. printf("%s", line);
17. }
18.
19. fclose(file);
20.}
- handle_write(char *args[])
功能:写入或改写文件内容。
说明:
参数:
args[1]:文件名。
O_WRONLY | O_CREAT | O_TRUNC:打开文件的标志,表示写入、创建和截断。
0644:文件权限。
1.// my_write 命令:写入或改写文件内容
2.void handle_write(char *args[]) {
3. if (args[1] == NULL) {
4. fprintf(stderr, "my_write: missing operand (文件名)\n");
5. return;
6. }
7.
8. int fd = open(args[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
9. if (fd < 0) {
10. perror("无法打开文件");
11. return;
12. }
13.
14. printf("输入要写入的内容(输入 'done' 结束):\n");
15. char buffer[256];
16. while (1) {
17. fgets(buffer, sizeof(buffer), stdin);
18. buffer[strcspn(buffer, "\n")] = 0; // 去除换行符
19.
20. if (strcmp(buffer, "done") == 0) {
21. break;
22. }
23.
24. write(fd, buffer, strlen(buffer));
25. write(fd, "\n", 1); // 添加换行符
26. }
27.
28. close(fd);
29.}
- handle_env() (进行system调用)
功能:显示系统环境变量 HOME 和 PATH。
说明:
此函数使用 system() 系统调用来执行 shell 命令,从而获取并打印环境变量 HOME 和 PATH 的值。
system() 是一个 C 标准库函数,它允许程序执行外部命令。
系统调用:system()
功能:执行指定的命令。
1.// my_env 命令:显示系统环境变量
2.// 使用 system() 函数获取并打印 HOME 和 PATH 环境变量
3.void handle_env() {
4. // 调用 system() 执行 shell 命令来打印 HOME 环境变量
5. printf("HOME: ");
6. system("echo $HOME");
7.
8. // 调用 system() 执行 shell 命令来打印 PATH 环境变量
9. printf("PATH: ");
10. system("echo $PATH");
11.}
- execute_command
功能:解析并执行命令。
说明:
系统调用:fork(), execvp(), wait() 用于创建子进程、执行外部命令和等待子进程结束。
参数:
input:用户输入的命令字符串。
strtok():用于分割命令字符串。
args[]:存储分割后的命令参数。
1.// 解析并执行内部命令
2.void execute_command(char *input){
3. char *args[64];
4.
5. // 使用strtok函数进行字符串分割
6. char *token = strtok(input, " ");
7. int i = 0;
8.
9. while(token != NULL){
10. args[i++] = token;
11. token = strtok(NULL, " ");
12. }
13. args[i] = NULL;
14.
15. // 处理内部命令
16. if (strcmp(args[0], "my_cd") == 0) {
17. handle_cd(args);
18. } else if (strcmp(args[0], "my_pwd") == 0) {
19. handle_pwd();
20. } else if (strcmp(args[0], "my_echo") == 0) {
21. handle_echo(args);
22. } else if (strcmp(args[0], "my_mkdir") == 0) {
23. handle_mkdir(args);
24. } else if (strcmp(args[0], "my_rmdir") == 0) {
25. handle_rmdir(args);
26. } else if (strcmp(args[0], "my_cat") == 0) {
27. handle_cat(args);
28. } else if (strcmp(args[0], "my_write") == 0) {
29. handle_write(args);
30. } else if (strcmp(args[0], "my_env") == 0) {
31. handle_env(); // 显示环境变量
32. } else {
33. // 外部命令处理
34. printf("此条命令为外部命令\n");
35.
36. pid_t pid = fork();
37.
38. if(pid == 0){
39. execvp(args[0], args); // 使用PATH查找并执行外部命令
40. perror("execvp failed");
41. exit(EXIT_FAILURE);
42. } else if(pid > 0){
43. wait(NULL); // 等待子进程结束
44. } else {
45. perror("调用子进程失败!\n");
46. }
47. }
48.}
完整代码如下:
21.#include <stdio.h>
22.#include <string.h>
23.#include <unistd.h>
24.#include <stdlib.h>
25.#include <sys/wait.h>
26.#include <sys/stat.h>
27.#include <fcntl.h>
28.
29.// 打印菜单
30.void print_menu(){
31. printf("-------------------my_shell------------------\n");
32. printf("输入“my_cd”命令:切换目录\n");
33. printf("输入“my_pwd”命令:打印当前目录\n");
34. printf("输入“my_echo”命令:打印用户输入\n");
35. printf("输入“my_mkdir”命令:创建目录\n");
36. printf("输入“my_rmdir”命令:删除目录\n");
37. printf("输入“my_cat”命令:打开并读取文件内容\n");
38. printf("输入“my_write”命令:写入文件内容\n");
39. printf("输入“my_env”命令:显示系统环境变量\n");
40. printf("---------------------------------------------\n");
41.}
42.
43.// 打印分割线
44.void print_line(){
45. printf("---------------------------------------------\n");
46.}
47.
48.// my_cd内部命令,切换目录
49.void handle_cd(char *args[]){
50. if(args[1] == NULL){
51. //
52. chdir(getenv("HOME")); // 切换到 HOME 目录
53. } else if(chdir(args[1]) == 0){
54. printf("目录切换成功,当前在 %s\n", args[1]);
55. } else {
56. perror("你输入的路径错误!\n");
57. }
58.}
59.
60.// my_pwd命令:打印当前目录
61.void handle_pwd(){
62. char cwd[1024];
63. if(getcwd(cwd, sizeof(cwd)) != NULL){
64. printf("%s\n", cwd);
65. } else {
66. perror("获取目录失败!");
67. }
68.}
69.
70.// my_echo命令:打印用户输入(可打印空格)
71.void handle_echo(char *args[]){
72. for(int i = 1; args[i] != NULL; i++){
73. printf("%s ", args[i]);
74. }
75. printf("\n");
76.}
77.
78.// my_mkdir命令:创建目录
79.void handle_mkdir(char *args[]){
80. if(args[1] == NULL){
81. fprintf(stderr, "my_mkdir: missing operand\n");
82. } else if(mkdir(args[1], 0777) == 0){
83. printf("创建成功!\n");
84. } else {
85. perror("创建失败!");
86. }
87.}
88.
89.// my_rmdir 命令:删除目录
90.void handle_rmdir(char *args[]) {
91. if(args[1] == NULL) {
92. fprintf(stderr, "缺少操作数!\n");
93. } else if(rmdir(args[1]) == 0) {
94. printf("删除成功!\n");
95. } else {
96. perror("删除失败");
97. }
98.}
99.
100.// my_cat 命令:读取并打印文件内容
101.void handle_cat(char *args[]) {
102. if (args[1] == NULL) {
103. fprintf(stderr, "my_cat: missing operand\n");
104. return;
105. }
106.
107. FILE *file = fopen(args[1], "r");
108. if (file == NULL) {
109. perror("无法打开文件");
110. return;
111. }
112.
113. char line[256];
114. while (fgets(line, sizeof(line), file) != NULL) {
115. printf("%s", line);
116. }
117.
118. fclose(file);
119.}
120.
121.// my_write 命令:写入或改写文件内容
122.void handle_write(char *args[]) {
123. if (args[1] == NULL) {
124. fprintf(stderr, "my_write: missing operand (文件名)\n");
125. return;
126. }
127.
128. int fd = open(args[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
129. if (fd < 0) {
130. perror("无法打开文件");
131. return;
132. }
133.
134. printf("输入要写入的内容(输入 'done' 结束):\n");
135. char buffer[256];
136. while (1) {
137. fgets(buffer, sizeof(buffer), stdin);
138. buffer[strcspn(buffer, "\n")] = 0; // 去除换行符
139.
140. if (strcmp(buffer, "done") == 0) {
141. break;
142. }
143.
144. write(fd, buffer, strlen(buffer));
145. write(fd, "\n", 1); // 添加换行符
146. }
147.
148. close(fd);
149.}
150.
151.// my_env 命令:显示系统环境变量
152.// 使用 system() 函数获取并打印 HOME 和 PATH 环境变量
153.void handle_env() {
154. // 调用 system() 执行 shell 命令来打印 HOME 环境变量
155. printf("HOME: ");
156. system("echo $HOME");
157.
158. // 调用 system() 执行 shell 命令来打印 PATH 环境变量
159. printf("PATH: ");
160. system("echo $PATH");
161.}
162.// 解析并执行内部命令
163.void execute_command(char *input){
164. char *args[64];
165.
166. // 使用strtok函数进行字符串分割
167. char *token = strtok(input, " ");
168. int i = 0;
169.
170. while(token != NULL){
171. args[i++] = token;
172. token = strtok(NULL, " ");
173. }
174. args[i] = NULL;
175.
176. // 处理内部命令
177. if (strcmp(args[0], "my_cd") == 0) {
178. handle_cd(args);
179. } else if (strcmp(args[0], "my_pwd") == 0) {
180. handle_pwd();
181. } else if (strcmp(args[0], "my_echo") == 0) {
182. handle_echo(args);
183. } else if (strcmp(args[0], "my_mkdir") == 0) {
184. handle_mkdir(args);
185. } else if (strcmp(args[0], "my_rmdir") == 0) {
186. handle_rmdir(args);
187. } else if (strcmp(args[0], "my_cat") == 0) {
188. handle_cat(args);
189. } else if (strcmp(args[0], "my_write") == 0) {
190. handle_write(args);
191. } else if (strcmp(args[0], "my_env") == 0) {
192. handle_env(); // 显示环境变量
193. } else {
194. // 外部命令处理
195. printf("此条命令为外部命令\n");
196.
197. pid_t pid = fork();
198.
199. if(pid == 0){
200. execvp(args[0], args); // 使用PATH查找并执行外部命令
201. perror("execvp failed");
202. exit(EXIT_FAILURE);
203. } else if(pid > 0){
204. wait(NULL); // 等待子进程结束
205. } else {
206. perror("调用子进程失败!\n");
207. }
208. }
209.}
210.
211.int main(){
212. print_menu();
213.
214. char input[256];
215.
216. while(1){
217. // 获取用户输入
218. fgets(input, 256, stdin);
219. input[strcspn(input, "\n")] = 0; // 去除换行符
220.
221. // 输入exit,退出
222. if(strcmp(input, "exit") == 0){
223. break;
224. }
225.
226. execute_command(input); // 解析并执行命令
227. print_line();
228. }
229. return 0;
230.}