这篇博客主要讲一下实现myshell前的准备工作
我们先从基础需求开始
1. 1,2 ,3 要求支持重定向,管道命令以及后台运行,那么首先需要考虑的就是解析参数,输入输出重定向(<,>,>>),管道(|),后台运行(&)与一般命令需要区别开来。
注:>与>> 的区别就是打开文件时的方式不同
fd = open(file,O_RDWR|O_CREAT|O_TRUNC,0644);
fd = open(file,O_RDWR|O_CREAT|O_APPEND,0644);
2. 要实现输入输出重定向的功能就离不开dup() 这个函数。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup用来复制 oldfd 所指的文件描述符,当复制成功时,返回最小的尚未被使用的文件描述符。
而dup2与dup的区别就是dup2可以指定新文件描述符。
默认的三个数据流的文件描述符分别为0,1,2,默认的标准输入为终端键盘IO,标准输出和错误输出都是为终端屏幕。
> 以覆盖的方式将屏幕上正确的输出流入到指定的文件或设备上
>> 以追加的方式将屏幕上正确的输出流入到指定的文件或设备上
< 将原本由键盘输入的数据,改由指定的文件内容来替换
3. 实现管道,首先得知道管道是干什么用的?管道简单来说就是让使用者可以同时连续执行命令,但是这是有限制的,管道只能处理前面一个命令传来的正确信息(即标准输出的信息),而且管道必须能使前面传来的数据成为标准输入才能继续执行,还有管道后面的一定要是命令并且还能接受标准输入的数据。了解了管道的定义后,我们来看看应该如何解析执行它,应该以管道为分界线将其划分为两个命令,先执行第一个命令,后等待其进程结束再执行第二个命令。
既然提到等待进程结束我们就来说一说 wait() 这个函数(非常必要的,避免产生僵尸进程)。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
wait函数让父进程暂停执行,直到它的一个子进程结束为停止,状态信息将会被写入到statloc。
waitpid 函数也用来等待子进程的结束,但是它可以通过参数 pid 指定要等待的子进程的。
4. 支持后台运行,让父进程直接返回,不等待子进程结束。
5. 如何让命令可以执行,在我们解析判断完之后,我们在 ./, /bin, /usr/bin 这些目录下寻找可执行程序(因为linux的shell上的自带执行程序基本都在这些目录),再寻找到后我们需要通过exec函数族来执行程序
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
使用exec族执行一个可执行的文件来代替当前进程的内存映像。
exec的族的使用并没有产生新的进程,而是把新程序的代码换入,重新分配数据段和栈堆,只保留进程ID。
6. 实现cd,由于cd是shell的内置命令,所以需要我们自己去实现,感觉需要注意的就是 cd 与 cd ~ 都是返回家目录,而 cd - 是返回上一次所在目录(可以定义一个全局字符数组,在切换目录前储存上一次目录所在路径)。
注:基础需求大概就是这些,而界面美观要自己去感受一下,不过可以使用printf() 输出染色试一试
进阶需求
1. tab补全以及历史记录的问题可以通过readline() 这个库函数来解决。
这是ubuntu下的安装代码:
sudo apt-get install libreadline6-dev
注意编译时需要加上 -lreadline
下面给出一段测试代码:
#include <stdio.h>
#include<stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>
int main()
{
char *buf = NULL;
int len;
while(1)
{
buf = (char *)malloc(sizeof(char) * 256);
buf = memset(buf,0,256);
buf = readline("");
len = strlen(buf);
printf("len = %d buf = %s\n",len,buf);
add_history(buf);
}
return 0;
}
而history 命令的实现就是将其储存进所定义的 history 数组前先判断是否与前一条命令相同,不相同再储存。
2. 不能被ctrl + c 打断
signal(SIGINT,SIG_IGN);
SIGINT: 终止进程
3. 设置环境变量这条只需把我们的可执行文件放置在/bin目录即可。
!@#$%^&*~