Shell命令的選項和引數在本質上是什麼?

2020-07-16 10:04:47
很多 Shell 命令都是可以附帶選項和引數的,不同的選項和引數也使得命令的功能細節有所差異。

Shell 命令附帶引數的例子:
  • cd demo命令表示進入當前目錄下的 demo 目錄,其中demo就是 cd 命令的引數。
  • echo "123xyz"命令表示輸出字串併換行,其中"123xyz"就是 echo 命令的引數。

Shell 命令附帶選項的例子:
ls -l命令用來顯示當前目錄下的所有檔案以及它們的詳細資訊,其中-l就是 ls 命令的選項。
echo -n "http://c.biancheng.net/shell/"表示在輸出字串後不換行,其中-n是 echo 命令的選項,"http://c.biancheng.net/shell/"是 echo 命令的引數。

有些命令的選項後面也可以附帶引數:
  • getsum -s 1 -e 100命令用來計算從 1 累加到 100 的和,其中-s-e是 getsum 命令的選項,1100分別是-s-e選項的引數。
  • read -n 1 sex命令用來讀取一個字元並賦值給 sex 變數,其中-n是 read 命令的選項,1-n選項的引數,sex是 read 命令的引數。

你是否對這些形形色色的選項和引數感到好奇?你是否想知道它們在底層是如何實現的?你是否也想自己動手對它們進行解析?本節就來給你揭曉答案!

死磕這個細節並不是閒得無聊,它能幫助我們理解命令的真正含義。好了,廢話不多說,讓我們趕緊轉入正題吧。

上節我們講到,一個 Shell 內建命令就是一個內部的函數,一個外部命令就是一個應用程式。內建命令後面附帶的所有資料(所有選項和引數)最終都以引數的形式傳遞給了函數,外部命令後面附帶的所有資料(所有選項和引數)最終都以引數的形式傳遞給了應用程式。

也就是說,不管是內建命令還是外部命令,它後面附帶的所有資料都會被“打包”成引數,這些引數有的傳遞給了函數,有的傳遞給了應用程式。

有程式設計經驗的讀者應該知道,C語言或者 C++ 程式的入口函數是int main(int argc, char *argv[]),傳遞給應用程式的引數最終都被 main 函數接收了。從這個角度看,傳遞給應用程式的引數其實也是傳遞給了函數。

有了以上認知,我們就不用再區分函數和應用程式了,我們就認為:不管是內建命令還是外部命令,它後面附帶的資料最終都以引數的形式傳遞給了函數。實現一個命令的一項重要工作就是解析傳遞給函數的引數。

注意,命令後面附帶的資料並不是被合併在一起,作為一個引數傳遞給函數的;這些資料是由空格分隔的,它們被分隔成了幾份,就會轉換成幾個引數。例如getsum -s 1 -e 100要向函數傳遞四個引數,read -n 1 sex要向函數中傳遞三個引數。

並且,命令後面附帶的資料都是“原汁原味”地傳遞給了函數,比如getsum -s 1 -e 100要傳遞的四個引數分別是 -s、1、-e、100,減號-也會一起傳遞過去,在函數內部,減號-可以用來區分該引數是否是命令的選項。

至於在函數內部如何解析這些引數,對於外部命令來說那就是 C/C++ 程式設計師的工作了,這裡不再過多贅述,只給出演示程式碼。

上節我給大家演示了一個 getsum 程式,本節依然使用該程式演示引數的解析,只是對程式碼進行了微調。
#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);
    }
   
    //列印接收到的引數
    printf("Received parameters: ");
    for(int i=0; i<argc; i++){
        printf("%s  ", argv[i]);
    }
    printf("n");
   
    //計算累加的和
    for(int i=start; i<=end; i++){
        sum+=i;
    }
    printf("sum=%dn", sum);

    return 0;
}
第 11~20 行是解析引數的關鍵程式碼,getopt.h 標頭檔案中的 getopt() 函數是值得重點研究的,有了該函數我們就不用自己去解析引數了,省了很大的力氣。

第 27~32 行將接收到的引數列印出來,以便讀者更好地觀察。

根據上節給出的辦法就可以執行 getsum 命令:
[[email protected] ~]$ getsum -s 1 -e 100
Received parameters: getsum  -s  1  -e  100 
sum=5050