参考文章
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