【Linux实验】构造一个简单的 shell

一、实验目的

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

该主程序包含七个函数,其中:

  1. main()函数:负责调用接收命令函数get_command(),并创建子进程,在子进程中调用执行命令函数execute_command(),在每次执行命令结束后循环出现下一次命令的接口。
  2. get_command()函数:负责接收标准输入的命令,并调用命令处理函数process_command()。
  3. process_command()函数:负责调用命令保存函数save_command(),并分解处理接收到的命令,将处理好的命令放至args数组中。
  4. save_command()函数:负责保存命令,可通过history命令查看历史命令。
  5. delete_last_elem()函数:负责删除当前命令。
  6. display_commands_history()函数:当输入history命令时,展示保存的历史命令。
  7. 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键直接提交,系统会判断命令有误并提供下一次命令输入接口。

 

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值