Shell命令的本質到底是什麼?

2020-07-16 10:04:46
《Shell是什麼》一節中講到,使用者通過在 Shell 中輸入一些命令來使用 Linux。給命令附帶不同的選項後,同一個命令的功能也會有所差異。

Shell 命令分為兩種:
  • Shell 自帶的命令稱為內建命令,它在 Shell 內部可以通過函數來實現,當 Shell 啟動後,這些命令所對應的程式碼(函數體程式碼)也被載入到記憶體中,所以使用內建命令是非常快速的。
  • 更多的命令是外部的應用程式,一個命令就對應一個應用程式。執行外部命令要開啟一個新的進程,所以效率上比內建命令差很多。

使用者輸入一個命令後,Shell 先檢測該命令是不是內建命令,如果是就執行,如果不是就檢測有沒有對應的外部程式:有的話就轉而執行外部程式,執行結束後再回到 Shell;沒有的話就報錯,告訴使用者該命令不存在。

內建命令

內建命令不宜過多,過多的內建命令會導致 Shell 程式本身體積膨脹,執行 Shell 程式後就會佔用更多的記憶體。Shell 是一個常駐記憶體的程式,占用過多記憶體會影響其它的程式。

只有那些最常用的命令才有理由成為內建命令,比如 cd、kill、echo 等;你可以轉到《Shell內建命令》來了解所有的內建命令,以及如何判斷一個命令是否是內建命令。

外部命令

外部命令可能是讀者比較疑惑的,一個外部的應用程式究竟是如何變成一個 Shell 命令的呢?

應用程式就是一個檔案,只不過這個檔案是可以執行的。既然是檔案,那麼它就有一個名字,並且存放在檔案系統中。使用者在 Shell 中輸入一個外部命令後,只是將可執行檔案的名字告訴了 Shell,但是並沒有告訴 Shell 去哪裡尋找這個檔案。

難道 Shell 要遍歷整個檔案系統,檢視每個目錄嗎?這顯然是不能實現的。

為了解決這個問題,Shell 在啟動檔案中增加了一個叫做 PATH 的環境變數,該變數就儲存了 Shell 對外部命令的查詢路徑,如果在這些路徑下找不到同名的檔案,Shell 也不會再去其它路徑下查詢了,它就直接報錯。

你不用關心啟動檔案(我們將在《Shell啟動檔案》中詳解),只需要知道 PATH 變數儲存了檢索路徑即可。

我們使用 echo 命令輸出 PATH 變數的值,看看它儲存了哪些檢索路徑:
[[email protected] ~]$ echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/mozhiyan/.local/bin:/home/mozhiyan/bin
不同的路徑之間以:分隔。你看,Shell 只會在幾個固定的路徑中查詢外部命令。

如果我們自己用C語言或者 C++ 編寫一個應用程式,並將它放到這幾個目錄下面,那麼我們的程式也會成為 Shell 命令。當然,你也可以修改 PATH 變數給它增加另外的路徑,不過這並不是本文的重點,有興趣的讀者請轉到《編寫自己的Shell組態檔》。

我自己使用C語言編寫了一個叫做 getsum 的程式,它用來計算從 m 累加到 n 的和,程式碼如下:
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
    int start = 0;
    int end = 0;
    int sum = 0;
    int opt;
    char *optstring = ":s:e:";

    while((opt = getopt(argc, argv, optstring))!= -1){
        switch(opt){
            case 's': start = atoi(optarg); break;
            case 'e': end = atoi(optarg); break;
            case ':': puts("Missing parameter"); exit(1);
        }
    }
   
    if(start<0 || end<=start){
        puts("Parameter error"); exit(2);
    }
   
    for(int i=start; i<=end; i++){
        sum+=i;
    }
    printf("%dn", sum);

    return 0;
}
將這段程式碼編譯成名為 getsum 的應用程式,並放在~/bin目錄(~ 表示使用者主目錄)下,然後在 Shell 中輸入下面的命令,就可以計算 1+2+3 ...... +99+100 的值。
[[email protected] ~]$ getsum -s 1 -e 100
5050
-s選項表示起始數位,-e選項表示終止數位。

對於不瞭解C語言的讀者,我也提供了編譯好的 getsum 程式,請猛擊這裡下載。

總結

Shell 內建命令的本質是一個自帶的函數,執行內建命令就是呼叫這個自帶的函數。因為函數程式碼在 Shell 啟動時已經被載入到記憶體了,所以內建命令的執行速度很快。

Shell 外部命令的本質是一個應用程式,執行外部命令就是啟動一個新的應用程式。因為要建立新的進程並載入應用程式的程式碼,所以外部命令的執行速度很慢。