1. 程式人生 > >一步一步實現自己的shell程式(一)---《Unix環境高階程式設計》讀書筆記

一步一步實現自己的shell程式(一)---《Unix環境高階程式設計》讀書筆記

引言

用過Unix/Linux系統的人都知道,Unix系統中很多命令和程式都是在終端中執行,這個終端就是shell。不同的Unix都有不同的預設shell程式,包括sh、csh、bash等等不同的shell程式。那麼什麼是shell,shell究竟做了什麼,如何實現簡單的shell呢?

基礎知識

程式與程序

程序實際上就是執行中的程式,而程式就是儲存在檔案中的機器指令,每個程序就是程式的一個例項,逐條執行程式檔案中的CPU指令。
程序有很多屬性,包括程序ID,父程序ID等等,內容太多現在介紹也很難記住,我們在使用時再介紹也不遲。

shell的基本功能

首先,shell本身就是一個程式,但是他是用來管理程序和執行程序的程式。主要有三個功能:

  • 執行程式
  • 可以程式設計
  • 管理輸入和輸出

首先是執行程式,ls和date實際上都是C語言編寫的普通程式,shell將他們載入記憶體並執行,之後將結果輸出到螢幕。因此,shell很大的作用就是執行程式。

 wutao@wutao-XXXX:~$ ls
Desktop    Downloads         git    Pictures  Templates
Documents  examples.desktop  Music  Public    Videos
wutao@wutao-XXXX:~$ date
20170311日 星期六 15:36:12 CST

其次是管理輸入和輸出,shell可以使用< > 和|將輸入和輸出重定向。如下程式碼所示,我們就能將程式ls的輸出重定向到檔案file中(預設為螢幕終端)。

[email protected]-XXXX:~$ ls -l > file
[email protected]-XXXX:~$ cat file
total 48
drwxr-xr-x 2 wutao wutao 4096 311  2017 Desktop
drwxr-xr-x 2 wutao wutao 4096 311  2017 Documents
drwxr-xr-x 2 wutao wutao 4096 311 15:05 Downloads
-rw-r--r-- 1 wutao wutao 8980 311 14:28 examples.
desktop -rw-rw-r-- 1 wutao wutao 0 311 15:36 file drwxrwxr-x 3 wutao wutao 4096 311 15:00 git drwxr-xr-x 2 wutao wutao 4096 311 2017 Music drwxr-xr-x 2 wutao wutao 4096 311 2017 Pictures drwxr-xr-x 2 wutao wutao 4096 311 2017 Public drwxr-xr-x 2 wutao wutao 4096 311 2017 Templates drwxr-xr-x 2 wutao wutao 4096 311 2017 Videos

同理<可以將輸入從預設的鍵盤輸入重定向為檔案輸入,|則用於程序之間管道通訊。
最後是可程式設計,也就是說shell命令中可以帶有變數,迴圈等流程控制語句。如下圖,我們將變數NAME賦值(等號兩邊不能有空格),然後用fileNAME
變數的值,如果包含則echo(反射程式)輸出hello,最後finish條件語句。

[email protected]-XXXX:~$ NAME=wutao
[email protected]-XXXX:~$ if grep $NAME file;then echo hello;fi;
drwxr-xr-x 2 wutao wutao 4096 311  2017 Desktop
drwxr-xr-x 2 wutao wutao 4096 311  2017 Documents
drwxr-xr-x 2 wutao wutao 4096 311 15:05 Downloads
-rw-r--r-- 1 wutao wutao 8980 311 14:28 examples.desktop
-rw-rw-r-- 1 wutao wutao    0 311 15:44 file
drwxrwxr-x 3 wutao wutao 4096 311 15:00 git
drwxr-xr-x 2 wutao wutao 4096 311  2017 Music
drwxr-xr-x 2 wutao wutao 4096 311  2017 Pictures
drwxr-xr-x 2 wutao wutao 4096 311  2017 Public
drwxr-xr-x 2 wutao wutao 4096 311  2017 Templates
drwxr-xr-x 2 wutao wutao 4096 311  2017 Videos
hello

shell如何執行程式

從我們可以看到的東西出發,我們來研究shell程式到底做了什麼?我們開啟一個終端,shell會列印一些提示字元,比如上面程式碼顯示的[email protected]:~$,主要包括了使用者名稱和使用者路徑(~)。然後我們鍵入命令,shell程式執行命令直到執行的程式終止,之後shell再次列印提示符。
我們可以把shell程式的主迴圈分為以下幾步:
1. 使用者鍵入命令
2. shell啟動新程序執行程式
3. shell等待程式執行完畢
4. 程式結束,shell完成一次主迴圈

因此,我們只要理解以上幾步的原理,我們就能寫出簡單的shell程式了。主要知識就是:

  • 如何在一個程式中新建程序執行另一個程式
  • 如何等待程式的結束

一個程式如何執行另一個程式

熟悉作業系統的話,對exec()系統呼叫一定不會陌生,exec()函式簇包括7個和執行程式有關的函式,主要區別在於執行是按檔名還是路徑名,傳遞引數的方式等。我們這裡只用其中的execvp(),函式原型如下:

int execvp(const char *filename,char *const argv[]);

函式引數為一個檔名和一個引數列表。比如說,如果要在一個程式中執行ls程式,我們就可以呼叫函式execvp(“ls”,arglist),函式會在環境變數中搜索ls程式,並將命令引數arglist傳遞給ls程式。這裡我寫了一個示範程式碼如下所示:

#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
int main(){

        char *arglist[3] = {"ls","-l",0};
        printf("exec ls\n");
        execvp("ls",arglist);
        printf("ls has done\n");
}

編譯執行程式,結果輸出如下(需要注意的是,第一個printf函式字串一定要有換行符,否則printf會將字元存放在快取區,只有呼叫重新整理快取區函式fflush或者快取區滿才會輸出):

wutao@wutao-XXXX:~/git/shell$ ./a.out
exec ls
total 16
-rwxrwxr-x 1 wutao wutao 8872 311 16:32 a.out
-rw-rw-r-- 1 wutao wutao  175 311 16:33 test_exec.c

我們發現,函式只打印了第一個printf的字串,而沒有列印第二個字串。原因就在於execvp()函式。

execvp函式做了什麼

前面說到,execvp能夠執行一個新程式,但是需要注意的是,程式執行完新的程式沒有能力返回到原先的程式,原因在於execvp函式將新程式載入當前程序,並替換了當前程序的程式碼和資料。也就是說新程式開始後,原程式就自動被清除了。

讓原程式也能夠存活

我們的shell程式是一個無止境的迴圈,不能夠執行一次命令就終止了,因此我們必須想辦法讓原程式執行命令後還能夠繼續等待命令的完成。所以我們要新建一個程序,讓命令在這個新建的程序中呼叫execvp函式,這樣我們的shell函式就不會被清除了。這裡我們使用的系統呼叫就是fork()函式。

fork函式

fork函式也是Unix的系統呼叫,呼叫fork函式能夠複製呼叫該函式的程序(寫時複製,只有產生寫操作時會對將要寫的部分進行復制)。複製產生新的程序後,我們再呼叫execvp函式,這樣就能既儲存shell程式和又能執行新程式。
那麼我們如何判斷哪個程序執行新程式呢?實際上fork的返回值就能判斷程序是子程序還是父程序。

fork的返回值

fork函式很特殊的地方在於一次呼叫會產生兩個返回值,父程序返回值為子程序的ID,子程序返回值為0。原因在於,父程序有可能有很多子程序,因此必須在呼叫子程序時獲取子程序ID,而子程序只有一個父程序,可以通過呼叫getppid函式得到父程序ID。瞭解了父子程序返回值的不同,就能夠通過判斷返回值來決定程序是執行新程式還是等待子程式結束。

wait函式

如果只使用fork和execvp函式,shell程式不會等待子程式結束,而會自顧自地繼續主迴圈,為了等待子程序的結束,我們要使用系統的wait函式。

Unix系統中,當一個程序終止時,核心會向父程序傳送SIGCHLD訊號。系統預設是忽略SIGCHLD訊號的,但是通過呼叫wait函式(還有waitpid函式),可以獲取子程序的結束狀態:

pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);

呼叫wait或者waitpid函式後:

  • 如果所有子程序都還在執行,則阻塞
  • 如果一個子程序已經終止,正等待父程序獲取其終止狀態,則獲取子程序終止狀態並立即返回
  • 如果沒有子程序則立即出錯返回

而waitpid和wait函式的區別則在於waitpid可以選擇需要等待的子程序,也可以通過調整選項使得呼叫者不阻塞,statloc 儲存子程序終止狀態,如果不關心終止狀態則設定statloc為NULL。

至此我們理解了execvp函式 fork函式以及wait函式,可以利用這三個函式寫出簡單的shell程式了。strtok函式可將字串根據delimiter分割。

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/wait.h>
#define MAXARGS 20
#define ARGLEN 80

int main(){
        while(1){
                printf(">");
                char argbuf[ARGLEN * MAXARGS];
                char *arglist[MAXARGS + 1];
                int index = 0;
                fgets(argbuf,ARGLEN,stdin);
                arglist[index++] = strtok(argbuf," \n");
                while((arglist[index++] = strtok(NULL," \n")));
                arglist[index-1] = 0;
                int newpid;
                if((newpid =fork()) == -1)
                        perror("fork");

                //child code
                else if (newpid == 0)
                        execvp(*arglist,arglist);

                //parent code
                else
                {
                        int wait_rv;
                        wait_rv = wait(NULL);
                        printf("done waiting for %d.returned:%d\n",newpid,wait_rv);
                }

}}

小結

本文主要介紹了shell程式的主要功能,利用了execvp函式,fork函式和wait函式簡單實現了在shell程式中執行其他程式的功能。

相關推薦

實現自己shell程式()---《Unix環境高階程式設計讀書筆記

引言 用過Unix/Linux系統的人都知道,Unix系統中很多命令和程式都是在終端中執行,這個終端就是shell。不同的Unix都有不同的預設shell程式,包括sh、csh、bash等等不同的shell程式。那麼什麼是shell,shell究竟做了什麼

python高階程式設計讀書筆記

python高階程式設計讀書筆記(一) python 高階程式設計讀書筆記,記錄一下基礎和高階用法 python2和python3相容處理 使用sys模組使程式python2和python3相容 import sysver=sys.version_info#(ma

UNIX環境高階程式設計的學習(

UNIX環境高階程式設計第一個例子的編譯 這本書中有很多的例子,為了加深理解,習慣性自己敲一遍程式碼然後看執行結果,再去理解其中的知識點,但是在虛擬機器下如何編譯這些程式碼呢,需要以下幾步: 下載並解壓縮apue.3e檔案包 在“apue.h”中最後一行加:#inclu

UNIX環境高階程式設計》中涉及到段程式碼,ourhdr.h檔案,以後備用!

#ifndef __ourhdr_h   #define __ourhdr_h   #include    <errno.h>               /*for definition of errno                      */ #inc

Javascript高階程式設計--讀書筆記之面向物件(

哈哈哈萬物皆物件,終於到了js的面向物件篇。 一、屬性型別 (1)資料屬性 資料屬性包含一個數據值的位置,在這個位置可以寫入和讀取數值,資料屬性有四個描述器行為的特性 [[Configurable]]:表示能否通過 delete 刪除屬性而重新定義屬性,預設值是ture [[Enumerab

UNIX環境高階程式設計UNIX網路程式設計(卷)環境搭建

最近學習這兩本書,在直接編譯書本源程式時,出現標頭檔案“apue.h”(UNIX環境高階程式設計)及“unp.h”(UNIX網路程式設計)錯誤,在這裡坐下配置的筆記。 首先需要安裝Linux系統。(在VirtualBox虛擬機器裡安裝Centos6.7的Linux系統) 一

檔案和目錄()--unix環境高階程式設計

     普通檔案和目錄linux中最多的兩類檔案,linux中一共有七種型別的檔案,如下:1.普通檔案 2.目錄 3.字元特殊裝置 4.塊特殊裝置 5.FIFO,又叫命名管道 6.Socket,即套接字 7.符號連結 獲取一個檔案的詳細資訊可以使用stat函式組,stat

UNIX環境高階程式設計》(APUE) 筆記第十章 - 執行緒

# 11 - 執行緒 [Github 地址](https://github.com/XutongLi/Learning-Notes/blob/master/APUE/11-%E7%BA%BF%E7%A8%8B.md) *** ## 1. 執行緒概念 典型的 **UNIX程序** 可以看成只有一個 **控制

linux下執行《UNIX環境高階程式設計》的第一個程式時原始碼編譯出錯的處理方法

前幾天買了《UNIX環境高階程式設計》這本書,想好好學習下linux的程式設計。誰知道看到第一個列出指定目錄的內容的那個例子,其實就是shell中 ls 的內容,打好程式碼要執行時一直出問題。後來在網上找了挺多的解決方法,終於解決了。先把方法貼上。 先在終端裡面輸入 vi

Android從零開始之一教你實現聯絡人功能(

在最近的專案中有這樣的一個需求,就是要實現類似聯絡人的列表,包含模糊查詢、按照A到Z拼音首字母分組排序、和收藏功能。參考了一下網上的例子,我覺得還是自己親自操刀來實現所有的功能。今天帶領大家先實現聯絡人右邊的側邊欄【A~Z】。先上一張圖: 可以看到,右邊是

教你如何用Java實現飛機大戰(

資源交流群 飛機大戰第一天 1.抽象類:飛行物 public abstract class FlyingObject{ protected BufferedImage image; protected int width; //寬 protected int h

IOS布局筆記(代碼實現自己主動布局)

tde tps space sina idt normal title 2014年 otto 1.將一個試圖放置在其父視圖的中央位置,使用限制條件。 2.創建兩個限制條件:一個是將目標視圖的 center.x 位置排列在其父視圖的 center.x 位置,而且另

oracle sql 高階程式設計學習筆記(二十)

一、行求解順序 select product, country, year, week, inventory, sale, receipts from sales_fact where country = 'Australia' and product = 'Xtend

Java併發程式設計讀書筆記

  前幾天整理電腦檔案的時候,突然發現了之前還在kindle儲存了關於併發程式設計的書,剛好自己在這方面挺薄弱的,故整理一波讀書筆記,繼續加強學習。   1.上下文切換 1.1 時間片分配演算法 時間片是CPU分配給各個執行緒的時間,CPU通過不停地切換執行緒執行,使各個執行緒彷彿是”同

windows程式設計讀書筆記之練習

看完第5章,突然想寫一個類cad的程式,先做簡單功能描述及可能用到的函式 1、畫直線,線寬、線形可以設定,滑鼠左鍵開始繪圖,滑鼠移動時,跟蹤滑鼠繪製直線,再次左鍵,直線繪製完畢,同時開始繪製下一直線,右鍵結束直線繪製。線形線寬在開始繪圖時設定。 2、滑鼠滾動,放大 縮小,

實戰java高併發程式設計讀書筆記

基本概念 同步非同步    同步和非同步這兩個是相對的概念用來描述方法的呼叫。同步指的是方法呼叫開始方法的呼叫者必須等待方法呼叫返回時,才能進行下一步操作。而相對的概念非同步就是相反,呼叫者不需要進行等待。 併發並行    併發和並行兩個概念特別的容易混淆,他們都表示兩個以

windows程式設計讀書筆記

用VC MFC有一段時間了,總覺得還沒掌握到精髓,專案程式碼基本是拼湊起來的。總是少點什麼。前段時間,看完 快樂鸚鵡 的程式人生, 裡面提到 全域性變數 的一段。突然想到自己用 vc+opencv的一個小專案, 那裡面真可謂 全域性變數滿天飛啊,只要是mat 的變數都成了全

PHP與MySQL程式設計讀書筆記

一、PHP基本型別及函式 1. 短標籤 sprintf??? 2. 型別自動裝換 3. 與型別相關的函式 gettype(),settype(),判斷是否為某個型別:is_name() 變數賦

任務和特權級保護()——《x86組合語言:從真實模式到保護模式》讀書筆記27

本文及後面的幾篇文章是原書第14章的讀書筆記。 1.LDT(區域性描述符表) 在之前的學習中,不管是核心程式還是使用者程式,我們都是把段描述符放在GDT中。但是,為了有效實施任務間的隔離,處理器建議每個任務都應該有自己的描述符表,稱為區域性描述符表LDT

Windows高階程式設計學習筆記

寫在前面的話 之前學的Windows程式設計都是介面啊、網路程式設計啊之類的純應用層面的東西,總是感覺而自己沒有達到自己期望中的水平。什麼水平呢?如果讓你編寫監控系統資源的工具,或者DLL注入相關軟體,或者底層安全軟體,可以勝任嗎?我的答案是,並不會。