posts - 95,comments - 0,views - 12934

参考文章

https://danishpraka.sh/posts/write-a-shell/
参考文章是英文的,我基本上是结合自己的理解翻译了一下,代码加了些注释,对阅读英文感兴趣的可以直接看这篇就可以了

然后原作者还增加了管道等等功能,在参考文章最后的click那里,可以跳转到github上的仓库

运行环境

linux环境即可

Shell

命令解释器,可以简易的理解为与操作系统交互的工具

main()

int main()
{
	loop();   /* 不停的循环等待输入,直至退出 */
	return 0;
}

loop()函数就是让shell不停地循环运行,直到我们输入退出命令

shell的主要运行过程

主要由read_line()、split_line()、dash_execute()这3个函数来实现shell,注释比较详细,就不多说了

/* 
 * shell的循环过程: 
 *   1. 等待并读取输入
 *   2. 处理输入
 *   3. 执行输入的命令
 */

void loop()
{
	char *line; 						/* 存储用户的输入 */
	char **args; 						/* 存储用户的输入处理后的结果 */
	int status = 1; 					/* status=0 表示退出shell */

	do {
		printf("> "); 					/* 提示符 */
		line = read_line(); 		    /* 读取用户输入 */
		args = split_line(line); 		/* 处理用户的输入,命令+参数 */
		status = dash_execute(args); 	/* 执行输入的命令,更新status */

		/* 释放内存 */
		free(line);
		line = NULL;
		free(args);
		args = NULL;
	} while (status);
}

读取用户的输入

读取用户的输入字符串,并将其存储在动态分配的缓冲区中

/* 读取用户的输入 */
char *read_line()
{	
	int buffsize = 1024; 					/* 存储用户输入的缓冲区大小 */	
	int position = 0; 						/* 定位输入字符串的每个字符 */
	char *buffer = (char*) malloc(sizeof(char) * buffsize);
	int c; 									/* 存储输入的字符 */

	/* 分配失败, 输出错误信息 */
	if (buffer == NULL) {
		fprintf(stderr, "%sdash: Allocation error%s\n", RED, RESET);
		exit(EXIT_FAILURE);
	}

	while (1) {
		c = getchar(); 						/* 获取输入的字符 */
		if (c == EOF || c == '\n') { 		/* 输入结束 */
			buffer[position] = '\0';
			return buffer;
		} else {
			buffer[position] = c;
		}
		position++;

		/* 缓冲区大小不够存储用户的输入 */
		if (position >= buffsize) {
			buffsize += 1024;
			buffer = (char*) realloc(buffer, buffsize);

			/* 分配失败, 输出错误信息 */
			if (buffer == NULL) {
				fprintf(stderr, "%sdash: Allocation error%s\n", RED, RESET);
				exit(EXIT_FAILURE);
			}
		}
	}
}

处理用户的输入

其实就是将获取到的字符串分割,比如输入命令mkdir test创建一个目录,我们要将"mkdir test"这个字符串分割成"mkdir"(命令)、"test"(参数),方便后续命令的执行

需要多注意的是,strtok()函数的使用,大家可以去了解下如何使用,方便理解

/* 处理用户的输入,例如 "mkdir d" ---> "mkdir" 、"d" */
char **split_line(char *line)
{
	int buffsize = 1024; 				/* 存储处理后的输入的缓冲区大小 */
	int position = 0; 					/* 定位每一个分隔后的参数 */
	char **tokens = (char**) malloc(buffsize * sizeof(char*));
	char *token; 						/* 存储单个参数 */

	if (tokens == NULL) {
		fprintf(stderr, "%sdash: Allocation error%s\n", RED, RESET);
    	exit(EXIT_FAILURE);
	}
	token = strtok(line, TOK_DELIM); 	/* 将用户输入line切割 */
	while (token != NULL) {
		/* 将一个个分隔后的token存储在tokens列表中 */
		tokens[position] = token;
		position++;

		/* 缓冲区大小不够存储处理后的输入 */
		if (position >= buffsize) {
		  buffsize += 1024;
		  tokens = (char**) realloc(tokens, buffsize * sizeof(char*));

		  if (tokens == NULL) {
			fprintf(stderr, "%sdash: Allocation error%s\n", RED, RESET);
			exit(EXIT_FAILURE);
		  }
		}
		token = strtok(NULL, TOK_DELIM); /* 获取下一个参数 */
	}

	tokens[position] = NULL;

	return tokens;
}

执行输入的命令

dash_execute()函数返回1表示正常执行完命令,返回0表示命令为exit,退出shell,由loop()函数中的status变量接收

需要注意的是,大家要去了解下fork()和execvp()这两个函数,方便理解代码

简单来说fork()是用来创建子进程,shell是不停地循环的工作,每一次执行命令都可以看做是一个子进程在工作,所以执行完命令要及时关闭创建的子进程,释放占有的资源

execvp()是用来执行命令,是一个内置的函数,具体怎么使用大家自行搜索哈,它需要的参数就是我们之前处理用户输入后返回的参数列表

/* 执行输入的命令 */
int dash_execute(char **args)
{
	pid_t cpid; 					/* 子进程ID */
	int status; 					/* 子进程状态 */

	/* 输入exit退出shell */
	if (strcmp(args[0], "exit") == 0) {
		return 0;
	}	

	cpid = fork(); 					/* 创建一个子进程 */

	if (cpid == 0) {
		/* 子进程代码块 */
		if (execvp(args[0], args) < 0) {
			/* 执行命令,如果execvp返回值小于0,说明命令未找到 */
			printf("%sdash: command not found: %s%s\n", YELLOW, args[0], RESET);
		}
		exit(EXIT_FAILURE);
	} else if (cpid < 0) {
		/* fork() 的情况 */
		printf("%sError forking%s\n", RED, RESET);
	} else {
		/* 父进程代码块 */
		/* 等待子进程结束或停止 */
		waitpid(cpid, &status, WUNTRACED);
	}	

	return 1; 						/* 返回1表示执行完毕 */
}

完整源码

/*    include    */
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

/*    define    */
#define RED 		"\e[0;31m" 			/* 让终端字符显示为红色 */
#define YELLOW 		"\e[0;33m" 			/* 让终端字符显示为黄色 */
#define RESET 		"\e[0m" 			/* 让终端字符恢复默认 */
#define TOK_DELIM 	" \t\r\n" 			/* 命令的分隔符 */

/* 
 * shell的循环过程: 
 *   1. 等待并读取输入
 *   2. 处理输入
 *   3. 执行输入的命令
 */
void loop();

/* 读取用户的输入 */
char *read_line();

/* 处理用户的输入,例如 "mkdir d" ---> "mkdir" 、"d" */
char **split_line(char *line);

/* 执行输入的命令 */
int dash_execute(char **args);

int main()
{
	loop();   /* 不停的循环等待输入,直至退出 */
	return 0;
}

void loop()
{
	char *line; 						/* 存储用户的输入 */
	char **args; 						/* 存储用户的输入处理后的结果 */
	int status = 1; 					/* status=0 表示退出shell */

	do {
		printf("> "); 					/* 提示符 */
		line = read_line(); 			/* 读取用户输入 */
		args = split_line(line); 		/* 处理用户的输入,命令+参数 */
		status = dash_execute(args); 	/* 执行输入的命令,更新status */

		/* 释放内存 */
		free(line);
		line = NULL;
		free(args);
		args = NULL;
	} while (status);
}

char *read_line()
{	
	int buffsize = 1024; 					/* 存储用户输入的缓冲区大小 */	
	int position = 0; 						/* 定位输入字符串的每个字符 */
	char *buffer = (char*) malloc(sizeof(char) * buffsize);
	int c; 									/* 存储输入的字符 */

	/* 分配失败, 输出错误信息 */
	if (buffer == NULL) {
		fprintf(stderr, "%sdash: Allocation error%s\n", RED, RESET);
		exit(EXIT_FAILURE);
	}

	while (1) {
		c = getchar(); 						/* 获取输入的字符 */
		if (c == EOF || c == '\n') { 		/* 输入结束 */
			buffer[position] = '\0';
			return buffer;
		} else {
			buffer[position] = c;
		}
		position++;

		/* 缓冲区大小不够存储用户的输入 */
		if (position >= buffsize) {
			buffsize += 1024;
			buffer = (char*) realloc(buffer, buffsize);

			/* 分配失败, 输出错误信息 */
			if (buffer == NULL) {
				fprintf(stderr, "%sdash: Allocation error%s\n", RED, RESET);
				exit(EXIT_FAILURE);
			}
		}
	}
}

char **split_line(char *line)
{
	int buffsize = 1024; 				/* 存储处理后的输入的缓冲区大小 */
	int position = 0; 					/* 定位每一个分隔后的参数 */
	char **tokens = (char**) malloc(buffsize * sizeof(char*));
	char *token; 						/* 存储单个参数 */

	if (tokens == NULL) {
		fprintf(stderr, "%sdash: Allocation error%s\n", RED, RESET);
    	exit(EXIT_FAILURE);
	}
	token = strtok(line, TOK_DELIM); 	/* 将用户输入line切割 */
	while (token != NULL) {
		/* 将一个个分隔后的token存储在tokens列表中 */
		tokens[position] = token;
		position++;

		/* 缓冲区大小不够存储处理后的输入 */
		if (position >= buffsize) {
		  buffsize += 1024;
		  tokens = (char**) realloc(tokens, buffsize * sizeof(char*));

		  if (tokens == NULL) {
			fprintf(stderr, "%sdash: Allocation error%s\n", RED, RESET);
			exit(EXIT_FAILURE);
		  }
		}
		token = strtok(NULL, TOK_DELIM); /* 获取下一个参数 */
	}

	tokens[position] = NULL;

	return tokens;
}

int dash_execute(char **args)
{
	pid_t cpid; 					/* 子进程ID */
	int status; 					/* 子进程状态 */

	/* 输入exit退出shell */
	if (strcmp(args[0], "exit") == 0) {
		return 0;
	}	

	cpid = fork(); 					/* 创建一个子进程 */

	if (cpid == 0) {
		/* 子进程代码块 */
		if (execvp(args[0], args) < 0) {
			/* 执行命令,如果execvp返回值小于0,说明命令未找到 */
			printf("%sdash: command not found: %s%s\n", YELLOW, args[0], RESET);
		}
		exit(EXIT_FAILURE);
	} else if (cpid < 0) {
		/* fork() 的情况 */
		printf("%sError forking%s\n", RED, RESET);
	} else {
		/* 父进程代码块 */
		/* 等待子进程结束或停止 */
		waitpid(cpid, &status, WUNTRACED);
	}	

	return 1; 						/* 返回1表示执行完毕 */
}
posted on   Dylaris  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示