首页 > 系统相关 >如何用C语言写一个简易的shell

如何用C语言写一个简易的shell

时间:2024-07-31 14:42:10浏览次数:10  
标签:shell buffsize args C语言 char 简易 position line 输入

参考文章

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表示执行完毕 */
}

标签:shell,buffsize,args,C语言,char,简易,position,line,输入
From: https://www.cnblogs.com/winter-z/p/18333675

相关文章

  • 使用finallshell连接linux
    用户可以去FinalShell的官网上下载,只需点击下载地址,即可轻松下载安装包。傻瓜式安装点击到底。使用双击打开页面,新建连接右击连接,新建》ssh 连接,双击新建的连接,如下界面即连接成功。新建文件夹,右键新建文件夹。新建文件,在文件夹右键新建文件。命令在命令区域正......
  • C语言7~8 DAY
    循环结构什么是循环代码的重复执行,就叫做循环。循环的分类无限循环:程序设计中尽量避免无限循环。(程序中的无限循环必须可控)有限循环:循环限定循环次数或者循环的条件。循环的构成循环体循环条件当型循环的实现while语法:while(循环条件){循环语句;}说......
  • C语言6 DAY
    分支结构分支结构:又被称为选择结构。概念选择结构:根据条件成立与否,选择相应的操作。条件构建关系表达式:含有关系运算符的表达式(>,<,>=,<=,!=,==)逻辑表达式:含有其逻辑运算符的表达式(&&,||,!),往往是用来构建复杂的符合条件,比如:if(year%100==0&&year%4!=0)//既有关系表达式......
  • POSIX-shell学习笔记
    学习POSIXshell建议使用dash,因为它很快:https://unix.stackexchange.com/a/148098mandash:OnlyfeaturesdesignatedbyPOSIX,plusafewBerkeleyextensions,arebeingincorporatedintothisshell.条件判断mandash,然后搜索testexpression,可以看到完整的列表。ife......
  • 嵌入式学习第12天——C语言循环结构
    循环结构什么是循环代码的重复执行,就叫做循环。循环的分类无限循环:程序设计中尽量避免无限循环(程序中的无限循环必须可控)。有限循环:循环限定循环次数或者循环的条件。循环的构成循环体循环条件当型循环的实现while语法: while(循环条件) { 循环语句;......
  • C语言——数组和排序
    C语言——数组和排序数组数组的概念数组的初始化数组的特点排序选择排序冒泡排序插入排序二分查找数组数组的概念数组是一组数据;数组是一组相同类型的数据或变量的集合;应用场景:用于批量的处理多个数据;语法:类型说明符数组名[常量表达式]类型说明符也就......
  • C语言程序设计(初识C语言前部分)
    新晋大学生计算机专业中的小小准程序员学习小笔记一,什么是C语言C语言是一门通用计算机编程语言,广泛用于底层开发,通俗的说就是人与计算机交流的计算机语言之一。底层开发就是指上图的下层(底层)部分。美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSIC,作为C语......
  • C语言理解——static、extern等
    目录1、static修饰局部变量2、static修饰全局变量3、static修饰函数4、extern修饰变量或函数5、register修饰变量6、const修饰变量7、typedef起别名8、#define文本替换1、static修饰局部变量普通的局部变量创建后是放在栈区中,这种局部变量进入作用......
  • c语言笔记(2024.7.24)第三天
    常量与变量概念:·表面:程序运行过程中取值可以改变的数据·实际:变量其实代表了一块内存区域/单元/空间。变量名可视为该区域的标识。整个变量分为三部分:·变量名:这个只是变量的一个标识,我们借助变量名来存取数据。·变量空间/存储单元:这个就是内存中分配的一块用来存放......
  • 嵌入式初学-C语言-练习二
    #针对于前六篇学习所出习题,题型内容均为本人敲写,若有不合理,或逻辑重复,请多多包涵,也请大家指点!!#1、通过键盘输入一个年份,判断是不是润年,是显示“是润年”,否则显示“不是润年”#include<stdio.h>intmain(){unsignedintyears;printf("输入一个年份\n");scanf("%d......