Tiny-shell (一)
Tiny-shell(一): 一个模仿bash的极简shell
概述
通过构建一个简单的shell,能够对shell的工作原理进行一些了解。主要有:
- 重定向
- 流水线
- 前台信号处理
- 进程组
- 后台进程
- 作业控制
这篇文章里先实现一个极简的shell,后续再不断对功能进行完善添加。
思路
在main
函数里,我们需要一个循环来一直进行输出,打印shell的提示符,用于提示命令的输入。同时,我们需要对从标准输入的命令进行处理,也就是字符串的处理。当按下回车时,需要我们fork
出一个子进程,并调用execvp
来执行处理过的命令。然后main
函数wait
子进程执行完毕继续打印新的一行提示符即可。这就是一个极简的shell。
函数说明
make_argv
首先我们需要写一个处理字符串的函数int make_argv(const char *s, const char *delimiters, char ***argvp)
。s
即是我们传入的字符串,比如ls -l
,我们要做的就是将此字符串处理成一个参数数组,便于传递给int execvp(const char *file, char *const argv[])
。函数中主要涉及用strtok
来进行处理。
execute_cmd
这个函数用于调用make_argv
,并进行检错,然后调用execvp
。
main
main
函数里进行fork
,并调用execute_cmd
。
运行情况
编译:`gcc -o tsh1 tsh1.c execute_cmd_simple.c make_argv.c"
tsh1>>
tsh1>> ls
execute_cmd_simple.c make_argv.c tsh1 tsh1.c
tsh1>>
tsh1>> echo "This is a tiny shell~"
"This is a tiny shell~"
tsh1>> q
源代码
tsh1.c
:
/**
* @Filename: tsh1.c
* @Description: tiny shell 的main函数
*/
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define PROMPT_STRING "tsh1>> "
#define QUIT_STRING "q" /* 按q退出 */
void execute_cmd(char *incmd);
int main (void) {
pid_t childpid;
char inbuf[MAX_CANON];
int len;
for( ; ; ) {
if (fputs(PROMPT_STRING, stdout) == EOF)
continue;
if (fgets(inbuf, MAX_CANON, stdin) == NULL)
continue;
len = strlen(inbuf);
if (len == 1 && inbuf[len - 1] == '\n') /* 若直接按回车就忽略 */
continue;
if (inbuf[len - 1] == '\n')
inbuf[len - 1] = 0;
if (strcmp(inbuf, QUIT_STRING) == 0)
break;
if ((childpid = fork()) == -1)
perror("Failed to fork child");
else if (childpid == 0) { /* 子进程 */
execute_cmd(inbuf);
return 1;
} else
wait(NULL);
}
return 0;
}
execite_cmd_simple.c
:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BLANK_STRING " "
int make_argv(const char *s, const char *delimiters, char ***argvp);
void execute_cmd(char *incmd) {
char **chargv;
if (make_argv(incmd, BLANK_STRING, &chargv) <= 0) {
fprintf(stderr, "Failed to parse command line\n");
exit(1);
}
execvp(chargv[0], chargv);
perror("Failed to execute command");
exit(1);
}
make_argv.c
:
/**
* @Filename: make_argv.c
* @Description: 处理字符串,形成参数数组
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/** s是传入字符串
* delimiters是分隔符
* argvp是形成的参数数组
*/
int make_argv(const char *s, const char *delimiters, char ***argvp) {
int error;
int i;
int numtokens; /* 形成的token总数 */
const char *snew;
char *t;
if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) {
errno = EINVAL;
return -1;
}
*argvp = NULL;
snew = s + strspn(s, delimiters); /* snew是字符串的真正的开始,去掉前面多余的分隔符*/
if ((t = malloc(strlen(snew) + 1)) == NULL)
return -1;
strcpy(t, snew);
numtokens = 0;
if (strtok(t, delimiters) != NULL) /* 计算token的数目,for从1开始是因为参数数组最后一个空间要给NULL */
for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;
/* 创建参数数组 */
if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {
error = errno;
free(t);
errno = error;
return -1;
}
/* 对每个子串进行拷贝 */
if (numtokens == 0)
free(t);
else {
strcpy(t, snew);
**argvp = strtok(t, delimiters);
for (i = 1; i < numtokens; i++)
(*argvp)[i] = strtok(NULL, delimiters);
}
(*argvp)[numtokens] = NULL; /* 最后放NULL */
return numtokens;
}