一、实验目的
l 用 C/C++构造一个简单的 shell;
l 理解 shell 程序的功能;
l 学会 shell 的使用;
l 掌握基本的 makefile 方法。
二、实验内容
基本任务 1:用 C/C++编写一个简单的 shell 程序,实现以下基本的命令。
1) 浏览目录和文件的各种属性 ls(可以不支持参数)
2) 回显命令 echo
3) 显示文件内容 cat
4) 创建目录 mkdir
5) 删除文件 rm
6) 切换目录 cd
7) 显示当前目录 pwd
8) 文字统计 wc
基本任务 2:每一条命令单独对应一个源程序文件,不允许所有命令一个源文件。
基本任务 3:写一个 makefile 来管理这些源文件。
基本任务 4:并写清楚 make 编译和运行的过程。
l
选做任务
Advanced shell 功能,例如:
1) 输入输出重定向,例如 ./a.out > output.dat
2) 支持多管道命令,例如 ./a.out | ./b.out | ./c.out
3) 支持后台运行命令,例如 ls | wc &
4) 其它:参考 GNU bash 提供的功能。
提供友好的人机界面:例如提供命令列表、命令查询、命令帮助等;
任何你想实现的其它命令;
l
参考程序:
man 手册:例如 man 2 stat//获取文件数据结构;man readdir //获取目录数据结构
网上资源
三、实验步骤
(1)编写每一条命令对应的源程序文件
a.sh_ls.c
这个文件用来实现linux下的ls命令。
本系统支持的命令格式:ls [FILE]...
1.#include <stdio.h>
2.#include <sys/types.h>
3.#include <dirent.h>
4.
5.int sh_ls(int argc, char *argv[]){
6. if(argc == 1){
7. DIR *pdir = opendir("./");
8. struct dirent *pdirent = NULL;
9. if(pdir == NULL){
10. printf("failed to open directory\n");
11. return -1;
12. }
13. while((pdirent = readdir(pdir)) != NULL){
14. if(pdirent -> d_name[0] == '.') continue;//jump over the hide file
15. printf("%s ",pdirent->d_name);
16. }
17. printf("\n");
18. closedir(pdir);
19. }
20. else{
21. for(int i = 1; i <= argc - 1; i++){
22. printf("%s:\n", argv[i]);
23. DIR *pdir = opendir(argv[i]);
24. struct dirent *pdirent = NULL;
25. if(pdir == NULL){
26. printf("failed to open directory\n");
27. return -1;
28. }
29. while((pdirent = readdir(pdir)) != NULL){
30. if(pdirent -> d_name[0] == '.') continue;//jump over the hide file
31. printf("%s ",pdirent->d_name);
32. }
33. printf("\n");
34. closedir(pdir);
35. }
36. }
37. return 0;
}
b.sh_echo.c
这个文件用来实现linux下的echo命令。
本系统支持的命令格式:echo [STRING]...
1.#include <stdio.h>
2.
3.int sh_echo(int argc, char *argv[]){
4. for(int i = 1; i < argc; i++){
5. printf("%s%s", argv[i], (i == argc - 1)?"":" ");
6. }
7. printf("\n");
8. return 0;
}
c.sh_cat.c
这个文件用来实现linux下的cat命令。
本系统支持的命令格式:cat [FILE]...
1.#include <stdio.h>
2.
3.int sh_cat(int argc, char *argv[]){
4. if(argc == 1){
5. printf("please input source file!\n");
6. return -1;
7. }
8.
9. for(int i = 1; i < argc; i++){
10. FILE *fp = fopen(argv[i], "r");
11. if(fp == NULL){
12. printf("%s: no such file or directory!\n", argv[i]);
13. return -1;
14. }
15.
16. int read_ret;
17. while(1){
18. read_ret = fgetc(fp);
19. if(feof(fp)){//file end
20. break;
21. }
22. fputc(read_ret, stdout);
23. }
24. }
}
d.sh_mkdir.c
这个文件用来实现linux下的mkdir命令。
本系统支持的命令格式:mkdir [DIRECTORY]...
1.#include <stdio.h>
2.#include <stdlib.h>
3.#include <sys/types.h>
4.#include <sys/stat.h>
5.#include <unistd.h>
6.#include <libgen.h>
7.
8.int sh_mkdir(int argc, char* argv[]){
9. if(argc == 1){
10. printf("please input file!\n");
11. return -1;
12. }
13.
14. for(int i = 1; i < argc; i++){
15. if(mkdir(argv[i],0770) != 0) printf("mkdir %s failed\n",argv[i]);
16. }
17. return 0;
}
e.sh_rm.c
这个文件用来实现linux下的rm命令。
本系统支持的命令格式:rm [FILE]...
1.#include <sys/stat.h>
2.#include <dirent.h>
3.#include <fcntl.h>
4.#include <stdio.h>
5.#include <unistd.h>
6.#include <string.h>
7.
8.//using namespace std;
9.
10.int remove_dir(const char *dir){
11. char cur_dir[] = ".";
12. char up_dir[] = "..";
13. char dir_name[128];
14. DIR *pdir;
15. struct dirent *pd;
16. struct stat dir_stat;
17.
18. if(0 != access(dir, F_OK)){
19. printf("%s: no such file or directory\n", dir);
20. return -1;
21. }
22.
23. if(0 > stat(dir, &dir_stat)){
24. perror("get directory stat error");
25. return -1;
26. }
27.
28. if(S_ISREG(dir_stat.st_mode)){//normal file
29. remove(dir);
30. }
31. else if(S_ISDIR(dir_stat.st_mode)){//directory
32. char flag[50] = {0};
33. printf("%s is a directory, do u want to continue? (y/n)", dir);
34. scanf("%s", flag);
35. //puts(flag);
36. if(strcmp(flag,"y") == 0){
37.
38. pdir = opendir(dir);
39. while((pd = readdir(pdir)) != NULL){
40. if((0 == strcmp(cur_dir, pd->d_name)) || (0 == strcmp(up_dir, pd->d_name))){
41. continue;//do not delete ./ and ../
42. }
43.
44. sprintf(dir_name, "%s/%s", dir, pd->d_name);
45. remove_dir(dir_name);//di gui
46. }
47. closedir(pdir);
48. rmdir(dir);
49.
50. }
51. }
52. else{
53. perror("unknow file type");
54. }
55.
56. return 0;
57.}
58.
59.int sh_rm(int argc, char *argv[]){
60. int ret;
61. for(int i = 1; i < argc; i++){
62. const char *dir = argv[i];
63. ret = remove_dir(dir);
64. }
65. return ret;
}
f.sh_cd.c
这个文件用来实现linux下的cd命令。
本系统支持的命令格式:cd [DIRECTORY]
1.#include <stdio.h>
2.#include <string.h>
3.#include <unistd.h>
4.
5.int sh_cd(int argc, char *argv[]){
6. char buf[80];
7. if(argc == 2){
8. getcwd(buf,sizeof(buf));
9. printf("current working directory: %s\n", buf);
10.
11. if(strcmp(argv[1], "~") == 0){
12. chdir("/home/stu2394");
13. }
14. else{
15. if(chdir(argv[1]) == 0){
16.
17. }
18. else{
19. printf("invalid directory\n");
20. return -1;
21. }
22. }
23. }
24. else{
25. printf("invalid directory\n");
26. return -1;
27. }
28.
29. getcwd(buf,sizeof(buf));
30. printf("change to directory: %s\n", buf);
31.
32. return 0;
}
g.sh_pwd.c
这个文件用来实现linux下的pwd命令。
本系统支持的命令格式:pwd
1.#include <stdio.h>
2.#include <unistd.h>
3.
4.int sh_pwd()
5.{
6. char path[80];
7. puts(getcwd(path, sizeof(path)));
8. return 0;
}
h.sh_wc.c
这个文件用来实现linux下的wc命令。
本系统支持的命令格式:wc [FILE]...
1.#include<stdio.h>
2.#include<unistd.h>
3.#include<sys/stat.h>
4.#include<stdlib.h>
5.#include<string.h>
6.
7.struct information{//not provide parameter function
8. int lines;//-l, --lines
9. int words;//-w, --words
10. int bytes;//-c, --bytes
11. int chars;//-m, --chars
12. int max_line_length;//-L, --max-line-length
13.}file_info;
14.
15.int init(char filename[]){
16. struct stat get_information = {};
17. FILE *fp;
18. int length = 0;//记录行的长度
19. file_info.lines = 0;
20. file_info.words = 0;
21. file_info.bytes = 0;
22. file_info.chars = 0;
23. file_info.max_line_length = 0;
24.
25. int ret_stat = stat(filename,&get_information);//读取filenmae文件的信息,并将结果写到get_message结构体中
26. if(ret_stat == -1){
27. printf("Error: failed to get the file's information\n");
28. return -1;
29. }
30. //判断是否是目录
31. mode_t mode = get_information.st_mode;//.st_mode: 文件对应的模式,文件,目录等
32. if(S_ISDIR(mode)){//是目录
33. printf("Error: %s is a directory\n", filename);
34. return -1;
35. }
36. else{//不是目录
37. fp = fopen(filename, "r");//以只读方式打开指定文件
38. if(fp == NULL){
39. printf("Error: failed to open the file!\n");
40. return -1;
41. }
42. else{
43. file_info.bytes = get_information.st_size;//st_size: 以字节为单位的文件容量
44.
45. //if(file_info.bytes != 0) file_info.words++;
46.
47. char ch;
48. int flag = 0;
49.
50. while((ch = fgetc(fp)) != EOF){//一直读到文件尾
51. file_info.chars++;
52.
53. if(ch != '\n'){
54. length++;
55. }
56. if(ch == '\n'){
57. file_info.lines++;
58. if(length > file_info.max_line_length)
59. file_info.max_line_length = length;
60. length = 0;
61. }
62.
63. if(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'){
64. if(flag == 1){
65. file_info.words++;
66. }
67. flag = 0;
68. }
69. else{
70. flag = 1;
71. }
72. }
73. fclose(fp);
74. return 0;
75. }
76. }
77.}
78.
79.int sh_wc(int argc, char *argv[]){
80. int ret;
81. if(argc == 2){
82. ret = init(argv[1]);
83. if(ret == 0){
84. printf("%d %d %d %s\n",file_info.lines,file_info.words,file_info.bytes,argv[1]);
85. return 0;
86. }
87. else{
88. return -1;
89. }
90. }
91. else if(argc == 1){
92. printf("Error: invalid input\n");
93. return -1;
94. }
95. else if(argc >= 3){
96. int total_lines = 0, total_words = 0, total_bytes = 0;
97. for(int i = 1; i <= argc - 1; i++){
98. ret = init(argv[i]);
99. if(ret == 0){
100. printf("%d %d %d %s\n",file_info.lines,file_info.words,file_info.bytes,argv[i]);
101. total_lines += file_info.lines;
102. total_words += file_info.words;
103. total_bytes += file_info.bytes;
104. }
105. else{
106. return -1;
107. }
108. }
109. printf("%d %d %d total\n",total_lines,total_words,total_bytes);
110. }
111. return 0;
112.}
(2)实现的其它命令:sh_cp.c
这个文件用来实现linux下的cp命令。
本系统支持的命令格式: cp SOURCE DEST
cp SOURCE DIRECTORY
1.//实现cp功能
2.#include <stdio.h>
3.#include <sys/types.h>
4.#include <sys/stat.h>
5.#include <fcntl.h>
6.#include <errno.h>
7.#include <unistd.h>
8.#include <string.h>
9.#include <stdlib.h>
10.#include <dirent.h>
11.
12.static char *get_name(const char *path)
13.{
14. char tmp_path[1024] = {0};
15. char * tmp_p = malloc(1024);
16. char * p = NULL;
17.
18. //字符串处理
19. strcpy(tmp_path , path);
20. p = strtok(tmp_path , "/");
21. strcpy(tmp_p , p);
22. while(1)
23. {
24. p = strtok(NULL , "/");
25. if(p == NULL)
26. {
27. break;
28. }
29. else
30. {
31. strcpy(tmp_p , p);
32. }
33. }
34.
35. return tmp_p;
36.}
37.
38.int sh_cp(int argc, char *argv[]){
39. struct stat get_information = {};
40. if(argc != 3){
41. printf("Error: invalid input.\n");
42. return -1;
43. }
44. int ret_stat = stat(argv[argc - 1],&get_information);//读取filenmae文件的信息,并将结果写到get_message结构体中
45. //判断是否是目录
46. mode_t mode = get_information.st_mode;//.st_mode: 文件对应的模式,文件,目录等
47. if(S_ISDIR(mode)){//是目录
48. FILE *fps = NULL;
49. FILE *fpc = NULL;
50. fps = fopen(argv[1], "r");//以只读方式打开指定文件
51. if(fps == NULL){
52. printf("Error: failed to open the file!\n");
53. return -1;
54. }
55. else{
56. char cp_path[1024] = {0};
57. chdir(argv[argc - 1]);
58. getcwd(cp_path, 1024);
59. sprintf(cp_path, "%s/%s", cp_path, get_name(argv[1]));
60. fpc = fopen(cp_path, "w");
61. if(fpc == NULL){
62. printf("Error: failed to open the file!\n");
63. return -1;
64. }
65. else{
66. int ch = 0;//ch获取文件字符
67. while(1){
68. ch = fgetc(fps);//获取fds中字符
69. if(ch == EOF)
70. break;
71. fputc(ch, fpc);//将ch中获取的字符写入到fdc指向的文件中
72. }
73. }
74. }
75. fclose(fpc);
76. fclose(fps);
77. }
78. else{//不是目录
79. FILE *fps = NULL;
80. FILE *fpc = NULL;
81. fps = fopen(argv[1], "r");//以只读方式打开指定文件
82. if(fps == NULL){
83. printf("Error: failed to open the file!\n");
84. return -1;
85. }
86. else{
87. fpc = fopen(argv[2], "w");
88. if(fpc == NULL){
89. printf("Error: failed to open the file!\n");
90. return -1;
91. }
92. else{
93. int ch = 0;//ch获取文件字符
94. while(1){
95. ch = fgetc(fps);//获取fds中字符
96. if(ch == EOF)
97. break;
98. fputc(ch, fpc);//将ch中获取的字符写入到fdc指向的文件中
99. }
100. fclose(fpc);
101. }
102. fclose(fps);
103. }
104. }
105. return 0;
106.}
(3)Advanced shell 功能
本系统提供了history历史命令查询功能、!!执行最近一条命令功能、!n根据历史命令编号执行命令功能、exit退出系统功能,具体实现代码在主程序2394_mysh.c中:
1.//主程序的process_command()函数部分代码:
2.if(strcmp(args[0], "!!") == 0 && args[1] == NULL) {
3. delete_last_elem();//删掉保存好的'!!'命令
4. if(cmd_cnt == 0) {
5. printf("No commands in history!\n");
6. return 0;
7. }
8. else{
9. int indx = (tail - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
10. strcpy(command_buffer, h_lst[indx].command_line);
11. return process_command(command_buffer, args);
12. }
13. }
14. // N should [cmd_cnt - MAX_CMDS_CNT + 1, cmd_cnt] and > 0.
15. if(args[0][0] == '!' && args[1] == NULL) {
16. delete_last_elem();
17. int is_int = 1;
18. for(int j = 1; args[0][j]; ++j) {//判断参数是否为数字
19. if(!isdigit(args[0][j])) {
20. is_int = 0;
21. break;
22. }
23. }
24. if(is_int) {
25. int ret_n = atoi(&args[0][1]);//把字符串nptr转换为int
26. int min_cmd_ind = 1 > cmd_cnt - MAX_CMDS_CNT + 1 ? 1 : cmd_cnt - MAX_CMDS_CNT + 1;
27. int max_cmd_ind = cmd_cnt;
28. if( ! (ret_n >= min_cmd_ind && ret_n <= max_cmd_ind) ) {
29. printf("No such command in history.\n");
30. return 0;
31. }
32. int offset = (cmd_cnt - ret_n);
33. int indx = (tail - 1 - offset + MAX_CMDS_CNT) % MAX_CMDS_CNT;
34. strcpy(command_buffer, h_lst[indx].command_line);
35. return process_command(command_buffer, args);
36. }
37. }
38. if (strcmp(args[0], "exit") == 0 && args[1] == NULL) {
39. should_run = 0;
40. }
41. else if (strcmp(args[0], "history") == 0 && args[1] == NULL) {
42. delete_last_elem();
43. }
44.
45.//主程序的execute_command()函数部分代码:
46.if (strcmp(args[0], "history") == 0 && args[1] == NULL) {
47. display_commands_history();
48. return 1;
49. }
50. else if(strcmp(args[0], "exit") == 0 && args[1] == NULL){
51. should_run = 0;
}
(4)友好的人机界面
a.sh_help.c
这个文件用来这个文件用来提供命令列表。
本系统支持的命令格式:help
1.#include<stdio.h>
2.
3.int sh_help(){
4. printf("********************************************\n");
5. printf("2394 bash, version 1.0\n");
6. printf("----------------command list----------------\n");
7. printf(" cat\n");
8. printf(" cd\n");
9. printf(" cp\n");
10. printf(" echo\n");
11. printf(" help\n");
12. printf(" ls\n");
13. printf(" mkdir\n");
14. printf(" man\n");
15. printf(" pwd\n");
16. printf(" rm\n");
17. printf(" wc\n");
18. printf(" !!\n");
19. printf(" ![Number]\n");
20. printf(" history\n");
21. printf(" exit\n");
22. printf("********************************************\n");
}
b.sh_man.c
这个文件用来提供命令帮助。
本系统支持的命令格式:man [COMMAND]
例如,ls对应的命令帮助文件为sh_ls.txt,输入man ls将会将命令帮助文件sh_ls.txt的内容展示到标准输出上。
每一条命令均有命令帮助文件。
1.#include<stdio.h>
2.#include<string.h>
3.
4.int sh_cat();
5.
6.int sh_man(int argc, char *argv[]){
7. if (strcmp(argv[1], "cat") == 0 && argv[2] == NULL) {
8. char argc = 2;
9. char *args[] = {"cat", "sh_cat.txt"};
10. sh_cat(argc, args);
11. }
12. else if (strcmp(argv[1], "cd") == 0 && argv[2] == NULL) {
13. char argc = 2;
14. char *args[] = {"cd", "sh_cd.txt"};
15. sh_cat(argc, args);
16. }
17. else if (strcmp(argv[1], "cp") == 0 && argv[2] == NULL) {
18. char argc = 2;
19. char *args[] = {"cp", "sh_cp.txt"};
20. sh_cat(argc, args);
21. }
22. else if (strcmp(argv[1], "echo") == 0 && argv[2] == NULL) {
23. char argc = 2;
24. char *args[] = {"echo", "sh_echo.txt"};
25. sh_cat(argc, args);
26. }
27. else if (strcmp(argv[1], "ls") == 0 && argv[2] == NULL) {
28. char argc = 2;
29. char *args[] = {"ls", "sh_ls.txt"};
30. sh_cat(argc, args);
31. }
32. else if (strcmp(argv[1], "mkdir") == 0 && argv[2] == NULL) {
33. char argc = 2;
34. char *args[] = {"mkdir", "sh_mkdir.txt"};
35. sh_cat(argc, args);
36. }
37. else if (strcmp(argv[1], "help") == 0 && argv[2] == NULL) {
38. char argc = 2;
39. char *args[] = {"help", "sh_help.txt"};
40. sh_cat(argc, args);
41. }
42. else if (strcmp(argv[1], "man") == 0 && argv[2] == NULL) {
43. char argc = 2;
44. char *args[] = {"man", "sh_man.txt"};
45. sh_cat(argc, args);
46. }
47. else if (strcmp(argv[1], "rm") == 0 && argv[2] == NULL) {
48. char argc = 2;
49. char *args[] = {"rm", "sh_rm.txt"};
50. sh_cat(argc, args);
51. }
52. else if (strcmp(argv[1], "pwd") == 0 && argv[2] == NULL) {
53. char argc = 2;
54. char *args[] = {"pwd", "sh_pwd.txt"};
55. sh_cat(argc, args);
56. }
57. else if (strcmp(argv[1], "wc") == 0 && argv[2] == NULL) {
58. char argc = 2;
59. char *args[] = {"wc", "sh_wc.txt"};
60. sh_cat(argc, args);
61. }
62. else if (strcmp(argv[1], "history") == 0 && argv[2] == NULL) {
63. char argc = 2;
64. char *args[] = {"history", "sh_history.txt"};
65. sh_cat(argc, args);
66. }
67. else{
68. printf("Error: invalid man input\n");
69. }
}
(5)主程序2394_mysh.c
该主程序包含七个函数,其中:
- main()函数:负责调用接收命令函数get_command(),并创建子进程,在子进程中调用执行命令函数execute_command(),在每次执行命令结束后循环出现下一次命令的接口。
- get_command()函数:负责接收标准输入的命令,并调用命令处理函数process_command()。
- process_command()函数:负责调用命令保存函数save_command(),并分解处理接收到的命令,将处理好的命令放至args数组中。
- save_command()函数:负责保存命令,可通过history命令查看历史命令。
- delete_last_elem()函数:负责删除当前命令。
- display_commands_history()函数:当输入history命令时,展示保存的历史命令。
- execute_command()函数:进行命令匹配,判断应该调用哪个命令执行函数,并将参数传入命令执行函数。
1.#include <stdio.h>
2.#include <unistd.h>
3.#include <stdlib.h>
4.#include <string.h>
5.#include <math.h>
6.#include <ctype.h>
7.#include <sys/types.h>
8.#include <wait.h>
9./*
10.#include "sh_ls.c"
11.#include "sh_echo.c"
12.#include "sh_cat.c"
13.#include "sh_mkdir.c"
14.#include "sh_rm.c"
15.#include "sh_cd.c"
16.#include "sh_pwd.c"
17.#include "sh_wc.c"
18.*/
19.#define MAX_LINE 80
20.#define MAX_CMDS_CNT 12//history保存10条命令
21.
22.int sh_ls();
23.int sh_echo();
24.int sh_cat();
25.int sh_mkdir();
26.int sh_rm();
27.int sh_cd();
28.int sh_wc();
29.int sh_cp();
30.int sh_pwd();
31.int sh_help();
32.int sh_man();
33.
34.typedef struct History {
35. char command_line[MAX_LINE];
36. int usage_cnt;
37.} history;
38.history h_lst[MAX_CMDS_CNT];
39.
40.int head = 0, tail = 0;
41.// head指向循环队列首部,tail指向队列尾部的下一个编号
42.//故判断Head和tail的值,可以得出队列是否为空。
43.int cmd_cnt = 0;//此变量代表所有保存过的命令的个数,可以大于10
44.int should_run = 1;
45.int tcnt = 0;
46.
47.void save_command(char *command_buffer){
48. strcpy(h_lst[tail].command_line, command_buffer);
49. h_lst[tail].usage_cnt ++;
50. tail = (tail + 1) % MAX_CMDS_CNT;//MAX_CMDS_CNT参加运算, 防止tail = 0时, tail - 1 = -1,负数索引
51. if(tail == head) head = (head + 1) % MAX_CMDS_CNT;//调整链表首部
52. cmd_cnt++;
53.}
54.
55.void delete_last_elem(){
56. if(head == tail)
57. return ;
58. tail = (tail - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
59. if(cmd_cnt >= 10){
60. if(tail == head) head = (head + 1) % MAX_CMDS_CNT;//调整链表首部
61. }
62. cmd_cnt --;
63.}
64.
65.void display_commands_history(){
66. if(head == tail) {
67. printf("No commands in history.\n");
68. return ;
69. }
70.
71. //从队尾,往队首遍历
72. int i = (tail - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
73. int endi = (head - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
74. //printf("%d %d\n",i,endi);
75. int t_cnt = cmd_cnt;
76. while(i != endi) {//由于条件原因,MAX_CMDS_CNT加二
77. printf("%3d %s\n", t_cnt--, h_lst[i].command_line);
78. i = (i - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
79. }
80.}
81.
82.
83.int process_command(char command_buffer[], char *args[]){
84. save_command(command_buffer);
85.
86. char *delim = " \r\t\n";
87. tcnt = 0;
88. args[tcnt++] = strtok(command_buffer, delim);
89. //分解字符串,command_buffer为要被分解的字符串,delim为用作分隔符的字符(可以是一个,也可以是集合),该函数返回被分解的第一个子字符串,若无可检索的字符串,则返回空指针
90. while(args[tcnt++] = strtok(NULL, delim)) ;//不需要再传入command_buffer,strtok会持续分割之前传入的command_buffer
91. //当args[n]为null时退出循环,所以数组中最后一个值为null,即args[tcnt - 1]为null,然后tcnt自加
92.
93. if(strcmp(args[0], "!!") == 0 && args[1] == NULL) {
94. delete_last_elem();//删掉保存好的'!!'命令
95. if(cmd_cnt == 0) {
96. printf("No commands in history!\n");
97. return 0;
98. }
99. else{
100. int indx = (tail - 1 + MAX_CMDS_CNT) % MAX_CMDS_CNT;
101. strcpy(command_buffer, h_lst[indx].command_line);
102. return process_command(command_buffer, args);
103. }
104. }
105. // N should [cmd_cnt - MAX_CMDS_CNT + 1, cmd_cnt] and > 0.
106. if(args[0][0] == '!' && args[1] == NULL) {
107. delete_last_elem();
108. int is_int = 1;
109. for(int j = 1; args[0][j]; ++j) {//判断参数是否为数字
110. if(!isdigit(args[0][j])) {
111. is_int = 0;
112. break;
113. }
114. }
115. if(is_int) {
116. int ret_n = atoi(&args[0][1]);//把字符串nptr转换为int
117. int min_cmd_ind = 1 > cmd_cnt - MAX_CMDS_CNT + 1 ? 1 : cmd_cnt - MAX_CMDS_CNT + 1;
118. int max_cmd_ind = cmd_cnt;
119. if( ! (ret_n >= min_cmd_ind && ret_n <= max_cmd_ind) ) {
120. printf("No such command in history.\n");
121. return 0;
122. }
123. int offset = (cmd_cnt - ret_n);
124. int indx = (tail - 1 - offset + MAX_CMDS_CNT) % MAX_CMDS_CNT;
125. strcpy(command_buffer, h_lst[indx].command_line);
126. return process_command(command_buffer, args);
127. }
128. }
129. if (strcmp(args[0], "exit") == 0 && args[1] == NULL) {
130. should_run = 0;
131. }
132. else if (strcmp(args[0], "history") == 0 && args[1] == NULL) {
133. delete_last_elem();
134. }
135. return 1;
136.
137.}
138.
139.
140.int get_command(char command_buffer[], char *args[]){
141. int length;
142. length = read(STDIN_FILENO, command_buffer, MAX_LINE);
143. //STDIN_FILENO:接收键盘的输入,MAX_LINE是请求读取的最大字节数,读上来的数据保存在缓冲区command_buffer中,同时文件的当前读写位置向后移。
144. //read():成功则返回读取的字节数,出错则返回-1并设置errno,如果在调read()之前已到达文件末尾,则返回0
145.
146. if (length == 1) // \n,说明只按了一个回车键,则重新接收命令
147. return 0;
148. if (length < 0) {
149. printf("Command reading failure!\n");
150. should_run = 0;
151. //exit(-1);//退出当前运行的程序,并将-1返回给主调进程
152. }
153.
154. command_buffer[length - 1] = 0;//将回车替换为'\0',表示字符串结束
155. return process_command(command_buffer, args);
156.}
157.
158.
159.int execute_command(char *args[])
160.{
161. if (strcmp(args[0], "history") == 0 && args[1] == NULL) {
162. display_commands_history();
163. return 1;
164. }
165. else if(strcmp(args[0], "exit") == 0 && args[1] == NULL){
166. should_run = 0;
167. }
168. else if(strcmp(args[0], "help") == 0 && args[1] == NULL){
169. sh_help();
170. }
171. else if(strcmp(args[0], "man") == 0){
172. sh_man(tcnt - 1, args);
173. }
174. else if(strcmp(args[0],"ls") == 0){
175. sh_ls(tcnt - 1, args);
176. }
177. else if(strcmp(args[0],"echo") == 0){
178. sh_echo(tcnt - 1, args);
179. }
180. else if(strcmp(args[0],"cat") == 0){
181. sh_cat(tcnt - 1, args);
182. }
183. else if(strcmp(args[0],"mkdir") == 0){
184. sh_mkdir(tcnt - 1, args);
185. }
186. else if(strcmp(args[0],"rm") == 0){
187. sh_rm(tcnt - 1, args);
188. }
189. else if(strcmp(args[0],"cd") == 0){
190. sh_cd(tcnt - 1, args);
191. }
192. else if(strcmp(args[0],"pwd") == 0){
193. sh_pwd(tcnt - 1, args);
194. }
195. else if(strcmp(args[0],"wc") == 0){
196. sh_wc(tcnt - 1, args);
197. }
198. else if(strcmp(args[0],"cp") == 0){
199. sh_cp(tcnt - 1, args);
200. }
201. else{
202. printf("Error: instruction not found.\n");
203. return -1;
204. }
205.}
206.
207.
208.
209.int main(int argc, char *argv[]){
210. char command_buffer[MAX_LINE];//存储输入的整个字符串
211. char *args[MAX_LINE / 2 + 1];//command line arguments
212. pid_t pid, tpid;
213.
214. while (should_run) {
215. printf("osh>");
216. fflush(stdout);//调用printf()时,输出的结果一般会被标准库缓存起来,可能不会及时打印写出到输出设备上面,此时就可以用fflush(stdout)强制把缓存内容进行输出
217.
218. int ret_flag = get_command(command_buffer, args);
219.
220. if (ret_flag > 0) {
221. pid = fork();
222. if (pid < 0) {
223. printf("Failed to fork a process!\n");
224. exit(1);
225. }
226. else if (pid == 0) {
227. //child process
228. if (execute_command(args) == -1){
229. printf("Failed to execute the command!\n");
230. }
231. exit(0);
232. }
233. else {
234. wait(0);
235. }
236. }
237. }
238. return 0;
}
(6)makefile文件
编写makefile文件来管理这些源文件。
1.cc = gcc
2.prog = 2394_mysh
3.obj = sh_ls.o sh_echo.o sh_cat.o sh_mkdir.o sh_rm.o sh_cd.o sh_pwd.o sh_wc.o sh_cp.o sh_help.o sh_man.o 2394_mysh.c
4.$(prog):$(obj)
5. $(cc) $^ -o $(prog)
6.%.o:%.c
7. $(cc) -c $< -o $@
8.clean:
rm *.o $(prog)
四、实验分析与实验结果
1.make编译
输入make命令可以运行makefile文件,对源程序进行编译。
2.运行
a.help命令
通过此命令可以查看本系统可以运行的命令。
b.man命令
通过此命令可以查看具体命令的帮助文档。
以man man为例:
图略。
c.ls命令
查看ls命令的帮助文档:
图略。
直接输入ls命令,可以查看当前路径下的文件:
在ls命令后加目录路径,可以查看一个(或多个)路径下的文件:
d.echo命令
查看echo命令的帮助文档:
图略。
echo命令可以在标准输出打印一行字符:
e.cat命令
查看cat命令的帮助文档:
图略。
cat命令可以链接文件并将其打印在标准输出上:
f.mkdir命令
查看mkdir命令的帮助文档:
图略。
mkdir命令可以创建一个(或多个)目录:
g.rm命令
查看rm命令的帮助文档:
图略。
rm命令可以删除文件或目录:
1.删除文件:
2.删除目录:可以看到,上级目录下有一个aaa目录,使用rm删除时,会给出提示:“aaa是一个目录,确定是否要删除目录”。
h.pwd命令
查看pwd命令的帮助文档:
图略。
pwd命令可以输出当前工作目录:
i.wc命令
查看wc命令的帮助文档:
图略。
wc命令可以打印一个(或多个)文件的相关信息:
j.cd命令
查看cd命令的帮助文档:
图略。
cd命令可以切换当前目录:
k.cp命令
查看cp命令的帮助文档:
图略。
cp命令可以复制文件:
1.复制文件到桌面(默认使用原文件名):
2.复制文件到桌面(给文件一个新名字):
l.history命令
查看history命令的帮助文档:
图略。
history命令可以显示近十条历史命令记录:
1.!!命令
!!命令可以执行最近一条命令cat sh_pwd.c:
2.!n命令
!n命令可以根据历史命令编号执行命令:
例如,执行第三十条命令wc sh_pwd.c sh_echo.c:
m.exit命令
输入exit命令可以退出系统:
五、设计文档
1.设计概述
shell是连接用户与系统内核的桥梁。本系统是一个简单的shell程序,支持常用的shell命令,并提供友好的人机界面,如命令列表与命令帮助功能,方便用户的使用。
2.运行环境
Linux操作系统
3.设计需求
(1)shell程序支持以下命令:
1) 浏览目录和文件的各种属性 ls(可以不支持参数)
2) 回显命令 echo
3) 显示文件内容 cat
4) 创建目录 mkdir
5) 删除文件 rm
6) 切换目录 cd
7) 显示当前目录 pwd
8) 文字统计 wc
9) 文件拷贝命令cp
(2)每一条命令单独对应一个源程序文件。
(3)提供makefile管理这些源文件。
(4)支持history历史命令查询功能、!!执行最近一条命令功能、!n根据历史命令编号执行命令功能与exit退出系统功能。
(5)提供友好的人机界面:命令列表、命令帮助。
4.概要设计
每个命令单独对应一个功能模块,用主模块循环生成一个新进程来调用各个功能子模块。
5.详细设计
主函数流程图:
图略。
6.用户操作流程
(1)在有系统程序的目录下,打开终端,输入命令make进行编译,如图。
(2)在当前目录下运行程序,如图。
(3)输入help命令可以查看命令列表,如图。
(4)输入man [COMMANDS]命令可以查看命令帮助文档,以ls命令为例,如图。
图略。
(5)输入命令即可运行命令,如图。
(6)输入exit命令可以退出系统,如图。
7.注意事项
当命令输入错误时,可以选择按backspace逐个删除或者按enter键直接提交,系统会判断命令有误并提供下一次命令输入接口。