欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入探究Linux shell的實(shí)現(xiàn)原理

 更新時間:2024年02月10日 10:17:40   作者:春人.  
這篇文章主要介紹了Linux shell的實(shí)現(xiàn)原理,文中通過代碼示例和圖文介紹的非常詳細(xì),對大家探究Linux shell的實(shí)現(xiàn)原理有一定的幫助,需要的朋友可以參考下

一、打印命令行提示符

const char* getusername() // 獲取用戶名
{
    return getenv("USER");
}

const char* gethostname() // 獲取主機(jī)名
{
    return getenv("HOSTNAME");
}

const char* getpwd() // 獲取當(dāng)前所處的目錄
{
    char* pos = strrchr(getenv("PWD"), '/'); // 查找最后一個 ‘/' 
    if(*(pos+1) != '\0') return pos+1; // 說明不是根目錄,返回最后一個文件夾
    return pos;
}

void tooltip() // 打印命令行提示框
{
    printf(LEFT "%s@%s %s" RIGHT PROMPT" ", getusername(), gethostname(), getpwd());
}

代碼分析:獲取基礎(chǔ)信息本質(zhì)上是通過調(diào)用 getenv 接口來獲取對應(yīng)環(huán)境變量的值。借助 strrchr 函數(shù)來查找當(dāng)前路徑中的最后一個文件分隔符 /,它有可能是文件分隔符也有可能是根目錄因此要單獨(dú)判斷。

二、讀取鍵盤輸入的指令

char command[1024]; // 存儲鍵盤輸入的指令

int getcommand(char* command, int size) // 讀取指令
{
    memset(command, '\0', size);
    char* ret = fgets(command, size, stdin); // 這里 ret 一定不為空,因?yàn)橹辽贂斎胍粋€回車,fgets 可以讀取回車
    assert(ret != NULL);
    (void)ret;// “假裝使用一下ret,防止有些編譯器警告”
    // aaabc\n\0
    command[strlen(command)-1] = '\0'; // 去掉結(jié)尾的 \n
    return 1;
}

int interact(char* command, int size) // 交互
{
    tooltip();
    while(getcommand(command, size) && (strlen(command) == 0))
    {
        tooltip();
    }
}

int main()
{
    interact(command, sizeof(command)); // 交互
    printf("echo: %s\n", command);
    return 0;
}

代碼分析:鍵盤輸入的指令本質(zhì)上就是一串字符串,這里不能用 scanf 來獲取字符串,因?yàn)?scanf 是不會讀取空格和回車的(遇到空格和回車就停止讀?。?,而我們一般的指令都是帶選項(xiàng)的,指令和選項(xiàng)之間一般會用空格隔開,用 scanf 會導(dǎo)致我們指令讀不全。這里使用 fgets 函數(shù)來讀取鍵盤輸入,其第一參數(shù)是存儲指令的空間的首地址;第二個參數(shù)是空間的大小;第三個參數(shù)是從哪個文件流中讀取,一個 C/C++ 程序默認(rèn)會打開三個文件流 stdin、stdout、stderr,這里選擇從 stdin 中讀取,也就是從標(biāo)準(zhǔn)輸入中讀取。gets 函數(shù)會在結(jié)尾自動幫我們添加 \0,并且當(dāng)讀取的字符個數(shù)大于存儲容量時,該函數(shù)會自動在結(jié)尾放 \0,因此我們可以不用考慮為 \0 預(yù)留空間或者認(rèn)為的在字符串結(jié)尾加 \0。其次該函數(shù)讀取成功返回 command 的首地址,否則返回 NULL,在當(dāng)前場景下,除非讀取錯誤,否則至少都會讀入一個 \n,一般我們輸入完指令就是敲回車,什么指令不輸也敲回車,因此正常情況下 ret 不可能為 NULL。這里還要考慮刪除掉讀取到的 \n,因?yàn)槲覀儾恍枰?,我們只要完整的指令?/p>

三、指令切割

#define SEPARATOR " " // 指令分隔符
char* argv[ARGC_LONG] = {NULL}; // 存儲指令和選項(xiàng)的起始地址

void commandcut(char* command, char** argv, int argvsize) // 指令切割
{
    memset(argv, 0, argvsize); // 清空
    char cop_command[COMMAND_LONG] = {'\0'}; // 保證 command 串不被改變
    for(int i = 0; command[i] != '\0'; i++)
    {
        cop_command[i] = command[i];
    }
    // 開始切割子串
    char* ret = strtok(cop_command, SEPARATOR);
    int i = 0;
    while(ret != NULL)
    {
        argv[i++] = ret;
        ret = strtok(NULL, " ");
    }
}

int main()
{
    while(1)
    {
        // 1、交互獲取命令行參數(shù)
        interact(command, sizeof(command)); // 交互

        // 到這里說明指令已經(jīng)獲取到了,接下來將指令打散
        // 2、指令切割
        commandcut(command, argv, sizeof(argv));
        for(int i = 0; argv[i]; i++)
        {
            printf("[%d]: %s\n", i, argv[i]);
        }
        printf("echo: %s\n", command);
    }
    return 0;
}

代碼分析:這一步主要是借助 strtok 函數(shù)將獲取到的指令切割成一個一個的子串,將所有子串的起始地址存儲在 argv 里面。注意 strtok 函數(shù)會改變原空間的內(nèi)容,因此創(chuàng)建了一段臨時的空間 cop_command

四、普通命令的執(zhí)行

void normalcommandexecution(char** _argv, int* _lastcode) // 普通命令的執(zhí)行
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
    }
    else if(id == 0)
    {
        // child
        int ret = execvp(_argv[0], _argv);
        if(ret == -1)
        {
            perror("exeecp");
            exit(EXIT_CODE);
        }
    }
    else
    {
        // father
        int status;
        pid_t ret = waitpid(id, &status, 0); // 阻塞等待
        if(ret == id)
        {
            *_lastcode = WEXITSTATUS(status);
        }
    }
}

int main()
{
    while(1)
    {
        // 1、交互獲取命令行參數(shù)
        interact(command, sizeof(command)); // 交互

        // 到這里說明指令已經(jīng)獲取到了,接下來將指令打散
        // 2、指令切割
        commandcut(command, argv, sizeof(argv));
        
        // 3、普通命令執(zhí)行
        normalcommandexecution(argv, &lastcode);
    }
    return 0;
}

代碼分析:對于 ls 這種普通指令(非內(nèi)建指令),先通過 fork 創(chuàng)建子進(jìn)程,然后再調(diào)用 execvp 接口進(jìn)行程序替換,去執(zhí)行輸入的指令。

五、內(nèi)建指令執(zhí)行

5.1 cd指令

bool isnormalcommand(char **_argv) // 指令判斷
{
    if (strcmp(_argv[0], "cd") == 0)
        return false;

    return true;
}

void changpwd(char** _argv) // 更改當(dāng)前工作目錄
{
    chdir(_argv[1]); // 更改當(dāng)前工作目錄
    // getpwd(pwd, sizeof(pwd));
    sprintf(getenv("PWD"), "%s", getcwd(pwd, sizeof(pwd))); // 修改環(huán)境變量
}

void builtincommand(char **_argv) // 內(nèi)建命令執(zhí)行
{
    if (strcmp(_argv[0], "cd") == 0)
    {
        changpwd(_argv);
    }
}

int main()
{
    while (1)
    {
        // 1、交互獲取命令行參數(shù)
        interact(command, sizeof(command)); // 交互

        // 到這里說明指令已經(jīng)獲取到了,接下來將指令打散
        // 2、指令切割
        commandcut(command, argv, sizeof(argv));

        // 3、指令判斷

        // 3、普通命令執(zhí)行
        if (isnormalcommand(argv)) // 普通指令
            normalcommandexecution(argv, &lastcode);
        else // 內(nèi)建指令
            builtincommand(argv);
    }
    return 0;
}

代碼分析:要考慮內(nèi)建指令,那在指令切割之后要先對指令進(jìn)行判斷。內(nèi)建指令不需要創(chuàng)建子進(jìn)程去執(zhí)行,而是直接由當(dāng)前的 bash 進(jìn)程去執(zhí)行。比如說 cd 指令,執(zhí)行完 cd 指令后,我們要讓當(dāng)前的 bash 更改工作目錄,而不是讓其創(chuàng)建子進(jìn)程去執(zhí)行 cd 指令,那樣改變的就是子進(jìn)程的工作目錄??梢园l(fā)現(xiàn),一個指令執(zhí)行完后,如果會對 bash 產(chǎn)生影響,那么它就必須是內(nèi)建指令。其次關(guān)于 cd 指令,它改變了當(dāng)前的工作目錄,這一點(diǎn)該如何理解呢?我 myshell 就是一個可執(zhí)行程序,我的源代碼和編譯得到的可執(zhí)行文件始終都放在 /home/wcy/linux-s/2023-10-28a/myshell 目錄下,你 cd 命令憑什么能改變我的工作錄?其實(shí)并不然,這里改變工作目錄是:一個可執(zhí)行程序在變成進(jìn)程產(chǎn)生 PCB 對象后,PCB 里面維護(hù)了一個屬性就叫做當(dāng)前可執(zhí)行程序的工作目錄,cd 指令改變的其實(shí)就是這一屬性,并不是改變 myshell 程序的存儲位置,我們通過調(diào)用 chdir 系統(tǒng)調(diào)用來修改這一屬性。最后,因?yàn)槲覀兦懊媸峭ㄟ^環(huán)境變量來獲取當(dāng)前工作目錄,而環(huán)境變量在被當(dāng)前 myshell 進(jìn)程從父進(jìn)程繼承下來后是不會自動發(fā)生改變的,因此在執(zhí)行完 cd 指令后,我們要對 PWD 環(huán)境變量進(jìn)行修改,環(huán)境變量本質(zhì)上就是存儲在內(nèi)存中的一段字符串信息,因此我們可以采用 sprintf 函數(shù)對該字符串信息進(jìn)行修改。

5.2 export指令

#define USER_ENV_SIZE 100  // 允許用戶添加的環(huán)境變量個數(shù)
#define USER_ENV_LONG 1024 // 用戶一個環(huán)境變量的最大長度

char userenv[USER_ENV_SIZE][USER_ENV_LONG]; // 保存用戶添加的環(huán)境變量
int userenvnum = 0;                         // 當(dāng)前用戶輸入的環(huán)境變量個數(shù)

void exportcommand(char** _argv, char(*_userenv)[USER_ENV_LONG], int* _userenvnum)
{
    // 將用戶輸入的環(huán)境變量存儲起來
    strcpy(_userenv[*_userenvnum], _argv[1]);
    int ret = putenv(_userenv[(*_userenvnum)++]);
    if (ret == 0)
        perror("putenv");
}

代碼分析:只要 bash 不退出,我們每次添加的環(huán)境變量都應(yīng)該被保存起來,我們輸入的環(huán)境變量是被當(dāng)做指令保存在 command 里面,當(dāng)下一次輸入指令,上一次輸入的內(nèi)容就會被清空。putenv 添加環(huán)境變量,并不是把對應(yīng)的字符串拷貝到系統(tǒng)的表當(dāng)中,而是把該字符串的地址保存在系統(tǒng)的表中,因此我們要確保保存環(huán)境變量字符串的那個地址里的環(huán)境變量不會被修改,所以我們需要為用戶輸入的環(huán)境變量,也就是那一串字符串單獨(dú)開辟一塊空間進(jìn)行存儲,保證在內(nèi)次重新輸入指令的時候,不會影響到之前用戶添加的環(huán)境變量。因?yàn)榄h(huán)境變量本質(zhì)就是一個字符串,所以這里我們定義了一個字符二維數(shù)組來存儲用戶輸入的環(huán)境變量,先把用戶輸入的環(huán)境變量存入我們定義的這個數(shù)組,然后再調(diào)用 putenv 函數(shù)將數(shù)組中的內(nèi)容添加到當(dāng)前的環(huán)境變量。這樣就可以保證只要當(dāng)前 bash 不退出,用戶歷史上添加的環(huán)境變量都在。這里涉及到二維數(shù)組傳參的問題,再來回顧一下,數(shù)組名表示首元素地址,二維數(shù)組的首元素是一個一維數(shù)組,所以函數(shù)形參的類型是一個字符一維數(shù)組的地址,也就是 char(*)[USER_ENV_LONG]

5.3 echo指令

void echocommand(char **_argv, int _argc)
{
    if (_argv[1][0] == '$')
    {
        char *ptr = _argv[1] + 1;
        printf("%s\n", getenv(ptr));
    }
    else
    {
        int i = 1;
        while (i < _argc)
        {
            char *ret = strtok(_argv[i], "\"");
            while (ret != NULL)
            {
                printf("%s", ret);
                ret = strtok(NULL, "\"");
            }
            printf("%c", ' ');
            i++;
        }
        printf("\n");
    }
}

代碼分析:echo 指令需要考慮將輸入的 " 去掉,其次可能連續(xù)輸入多個字符串,還要考慮 echo 和 $ 配合使用是去打印環(huán)境變量的值。

小結(jié):當(dāng)我們登陸的時候,系統(tǒng)就是要啟動一個 shell 進(jìn)程,我們 shell 本身的環(huán)境變量是在用戶登錄的時候,shell 會讀取用戶目錄下的 .bash_profile 文件,里面保存了導(dǎo)入環(huán)境變量的方式。

六、結(jié)語

以上就是深入探究Linux shell的實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于Linux shell的實(shí)現(xiàn)原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • shell寫的告警次數(shù)控制及恢復(fù)示例代碼

    shell寫的告警次數(shù)控制及恢復(fù)示例代碼

    自己寫的一個監(jiān)控腳本,發(fā)現(xiàn)告警短信次數(shù)未控制時,垃圾短信N多。經(jīng)過思考,做了個簡單的控制,同一故障只發(fā)送二次。之后不再發(fā)送?;謴?fù)后發(fā)送恢復(fù)短信
    2013-02-02
  • Shell腳本計(jì)算字符串長度和判斷字符串為空小技巧

    Shell腳本計(jì)算字符串長度和判斷字符串為空小技巧

    這篇文章主要介紹了Shell腳本計(jì)算字符串長度和判斷字符串為空小技巧,本文分別給出計(jì)算字符串長度和判斷字符串為空各3種實(shí)現(xiàn)方法,需要的朋友可以參考下
    2015-04-04
  • shell腳本for循環(huán)實(shí)現(xiàn)文件和目錄遍歷

    shell腳本for循環(huán)實(shí)現(xiàn)文件和目錄遍歷

    本文主要介紹了shell腳本for循環(huán)實(shí)現(xiàn)文件和目錄遍歷,首先進(jìn)行一個要遍歷的文件夾,然后循環(huán)查看每個文件,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • 利用Linux Find命令快速查找文件方法

    利用Linux Find命令快速查找文件方法

    這篇文章主要介紹了利用Linux Find命令快速查找文件方法,這篇文章應(yīng)用最多的就是linux find命令進(jìn)行快速查找定位,本文給大家介紹了find 命令基本使用方法,需要的朋友可以參考下
    2022-12-12
  • 101個shell腳本 猜數(shù)字游戲代碼

    101個shell腳本 猜數(shù)字游戲代碼

    原本書上這個例子是教調(diào)試腳本的,它故意給出的腳本中有幾個錯誤,教我們認(rèn)識系統(tǒng)提示的錯誤,并修改運(yùn)行。但比較難寫出來,所以把修改好了的腳本放上來,這個腳本比較有趣味的
    2016-08-08
  • Linux 中awk 提取包含某個關(guān)鍵字的段落

    Linux 中awk 提取包含某個關(guān)鍵字的段落

    AWK是一種處理文本文件的語言,是一個強(qiáng)大的文本分析工具。這篇文章主要介紹了Linux 中awk 提取包含某個關(guān)鍵字的段落實(shí)例代碼,需要的朋友可以參考下
    2020-01-01
  • 快速入門Shell腳本之條件判斷語句與循環(huán)

    快速入門Shell腳本之條件判斷語句與循環(huán)

    這篇文章主要介紹了快速入門Shell腳本之條件判斷語句與循環(huán),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • 使用ubuntu搭建公網(wǎng)個人郵件服務(wù)器(基于postfix,dovecot,mysql)

    使用ubuntu搭建公網(wǎng)個人郵件服務(wù)器(基于postfix,dovecot,mysql)

    這篇文章主要介紹了基于ubuntu搭建公網(wǎng)個人郵件服務(wù)器(基于postfix,dovecot,mysql),免費(fèi)的郵箱每天發(fā)信數(shù)量是有限制的,所以呢就想著搭建一個自己的郵件服務(wù)器,需要的朋友可以參考下
    2019-06-06
  • linux自動化交互腳本expect詳解

    linux自動化交互腳本expect詳解

    這篇文章主要介紹了linux自動化交互腳本expect的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • linux shell實(shí)現(xiàn)批量主機(jī)遠(yuǎn)程執(zhí)行命令腳本

    linux shell實(shí)現(xiàn)批量主機(jī)遠(yuǎn)程執(zhí)行命令腳本

    這篇文章主要介紹了linux shell實(shí)現(xiàn)批量主機(jī)遠(yuǎn)程執(zhí)行命令腳本,文章通過代碼示例講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-09-09

最新評論