寫在最前面:其實這是一個上課作業,我覺得我不能就這麼簡單的實現了而不給後人留下點什麼糟粕。所以下面有興趣的同學就跟著我誤入歧途吧。我沒想按著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 <errno.h> /* for definition of errno */
#include <stdarg.h> /* 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.
*/
void
err_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.
*/
void
err_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.
*/
void
err_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.
*/
void
err_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.
*/
void
err_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.
*/
void
err_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 void
err_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 <sys/wait.h>
#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] == `
`){
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倒沒什麼特殊的含義。