写在最前面:其实这是一个上课作业,我觉得我不能就这么简单的实现了而不给后人留下点什么糟粕。所以下面有兴趣的同学就跟着我误入歧途吧。我没想按着APUE这本书一章一章来,我认为既然是我自己读完了这本书并要实现东西,就需要有我自己的节奏,这种节奏不仅仅让我自己,也要让各位看客感到轻松,并偶有所获~我就十分开心了。
Preface
这学期报了一门课叫《Unix环境高级编程》,本来打算水水过去的,没想到老师需要我们每个人完成一份大作业,作业内容就是艰深复杂的各种调用底层API实现的shell。
怎么说呢?从严格意义上,操作系统可以定义为一种软件,它控制计算机硬件资源,提供程序运行环境,我们将这种软件成为内核(也就是kernel),内核的接口被称为系统调用,公用函数库构建在系统调用接口之上,应用程序既可以使用公用函数库,也可以使用系统调用。shell是一个特殊的应用程序,为运行其他程序提供了一个接口。
我们要实现的shell就是这么一个东西。
这么大段的定义还不如说【oh-my-zsh ,zsh ,bash】 来的实在,以上三种就是我们常见的shell,我现在最喜欢的就是 oh-my-zsh 了,其优点太多了,tabtab可以自动提示参数,配色好看几百倍等等等等。
废话不多说,要开车了哦。
准备工作
首先
一台Linux系统的计算机(比较方便,在windows下需要配置很多的环境,因为windows没有Unix的很多库函数,我使用的是 Ubuntu 16.04)
下载apue源码
解压源码文件 tar -zxvf *.tar.gz
cd apue.3e/ & make
在这个过程中,你会看到最后由于can,t find-lbsd而不能make成功,解决办法是添加libbsd.a的静态链接库sudo apt-get install libbsd-dev
5. 重新 make 然后再
sudocp ./include/apue.h /usr/include/ sudocp ./lib/libapue.a /usr/local/lib/
有时候以上步骤完成后就可以随便找书上的例子开始装逼了,但是我的电脑不行,因为里面有很多err_sys之类的报错函数未定义,那么还需要再执行一步。
复制下面的代码,并保存成 /usr/include/myerr.h
以后你写的例子里需要用到err_sys之类的函数,你需要 #include "myerr.h"#include "apue.h"#include/* for definition of errno */#include /* ISO C variable aruments */static void err_doit(int, int, const char *, va_list);/** Nonfatal error related to a system call.* Print a message and return.*/voiderr_ret(const char *fmt, ...){ va_list ap; va_start(ap, fmt); err_doit(1, errno, fmt, ap); va_end(ap);}/** Fatal error related to a system call.* Print a message and terminate.*/voiderr_sys(const char *fmt, ...){ va_list ap; va_start(ap, fmt); err_doit(1, errno, fmt, ap); va_end(ap); exit(1);}/** Fatal error unrelated to a system call.* Error code passed as explict parameter.* Print a message and terminate.*/voiderr_exit(int error, const char *fmt, ...){ va_list ap; va_start(ap, fmt); err_doit(1, error, fmt, ap); va_end(ap); exit(1);}/** Fatal error related to a system call.* Print a message, dump core, and terminate.*/voiderr_dump(const char *fmt, ...){ va_list ap; va_start(ap, fmt); err_doit(1, errno, fmt, ap); va_end(ap); abort(); /* dump core and terminate */ exit(1); /* shouldn't get here */}/** Nonfatal error unrelated to a system call.* Print a message and return.*/voiderr_msg(const char *fmt, ...){ va_list ap; va_start(ap, fmt); err_doit(0, 0, fmt, ap); va_end(ap);}/** Fatal error unrelated to a system call.* Print a message and terminate.*/voiderr_quit(const char *fmt, ...){ va_list ap; va_start(ap, fmt); err_doit(0, 0, fmt, ap); va_end(ap); exit(1);}/** Print a message and return to caller.* Caller specifies "errnoflag".*/static voiderr_doit(int errnoflag, int error, const char *fmt, va_list ap){ char buf[MAXLINE]; vsnprintf(buf, MAXLINE, fmt, ap); if (errnoflag) snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s", strerror(error)); strcat(buf, " "); fflush(stdout); /* in case stdout and stderr are the same */ fputs(buf, stderr); fflush(NULL); /* flushes all stdio output streams */}
尾声
既然是第一章,当然我们轻松点,那么就来一个最最开始的1-7程序吧,这个程序搭建了最基本的功能,也就是输入,连命令也不是去解析调API而是直接用原始bash的命令~
//// Created by jasperyang on 17-6-6.//#include "apue.h"#include#include "myerr.h"int main(void) { char buf[MAXLINE]; /* from apue.h */ pid_t pid; int status; printf("%% "); /* print prompt (printf requires %% to print %) */ while(fgets(buf,MAXLINE,stdin) != NULL) { if(buf[strlen(buf) -1] == '\n'){ buf[strlen(buf)-1]=0; /* replace newline with null */ } if((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0){ /* child */ execlp(buf,buf,(char*)0); err_ret("couldn't execute: %s",buf); exit(127); } /* parent */ if((pid = waitpid(pid,&status,0)) < 0) err_sys("waitpid error"); printf("%% "); } exit(0);}
简单易懂老少皆宜,这段程序输入命令,并fork一个child去执行命令,执行命令用的是execlp这个函数,执行失败与否子程序都exit(127),127倒没什么特殊的含义。