【Linux 學習計劃】-- 簡易版shell編寫

目錄

思路

創建自己的命令行

獲取用戶命令

分割命令

檢查是否是內建命令

cd命令實現

進程程序替換執行程序

總代碼

結語


思路

int main()
{while (1){// 1. 自己的命令行PrintCommandLine();// 2. 獲取用戶命令char command[SIZE];int n = GetUserCommand(command, sizeof(command));if (n <= 0)return 1;// 3. 分割指令SplitCommand(command);// for(int i = 0; gArgv[i]; i++)//     printf("[%d]: %s\n", i, gArgv[i]);// 4. 檢查是否是內建命令n = CheckBuildin();if(n) continue;// 5. 進程程序替換執行命令ExecuteCommand();}return 0;
}

上圖中的五個函數,就是我們的思路

首先就是先寫命令行:

就是這個,這個獲取環境變量 + 指針操作 + 打印即可,較為簡單

接著就是需要獲取用戶輸入的指令,并且將其分割,放進數組里面,這里會涉及到strtok函數的使用

最后將我們的函數分割完之后,就是判斷是否是內建命令了

如果是內建命令的話,這里就只實現cd和echo $?,我們就單獨函數實現

如果不是內建命令的話,我們就正常進行程序替換即可

這里最關鍵的點就是程序替換了,因為我們要使用的所有的命令都在磁盤上保存著,這時我們fork創建子進程,并用程序替換將磁盤上待替換的程序加載到內存中并將這個子進程給覆蓋,最終跑起來

創建自己的命令行

#define SkipPath(p)         \do                      \{                       \p += strlen(p) - 1; \while (*p != '/')   \p--;            \} while (0)const char *GetHome()
{const char *home = getenv("HOME");return home;
}const char *GetHostName()
{const char *hostname = getenv("HOSTNAME");return hostname;
}const char *GetCwd()
{const char *cwd = getenv("PWD");return cwd;
}const char *GetUserName()
{const char *name = getenv("USER");return name;
}void PrintCommandLine()
{char line[SIZE];const char *UserName = GetUserName();const char *Cwd = GetCwd();const char *HostName = GetHostName();SkipPath(Cwd);snprintf(line, sizeof line, "[%s@%s %s]:>", UserName, HostName, strlen(Cwd) == 1 ? "/" : Cwd + 1);printf("%s", line);fflush(stdout);
}

這里沒什么好說的,主要就是getenv獲取環境變量,我們的參數就是從環境變量中來

需要講的一點就是do...while(0)那個宏,這里我們如果直接獲取PWD的話,就是所有都打出來:

這個是效果

但是整體是這樣的

所以我們需要將指針移動到最后面那里,然后只顯示最后一段

但是這里為什么用的是宏呢?因為這里是用 c 語言實現的,寫一個函數的話還要二級指針,太麻煩了,宏方便點

而使用do...while(0)是為了形成一個代碼塊,我們可以整體使用,并且在下面調用的時候可以隨便加 “ ; ”,這樣調用起來就和函數一樣了

最終效果如下:

獲取用戶命令

int GetUserCommand(char command[], size_t n)
{char *s = fgets(command, n, stdin);if (s == NULL)return 2;// 處理 \ncommand[strlen(command) - 1] = ZERO;// printf("命令: %s", command);return strlen(command);
}

其實如果是cpp的話,就直接getline了,但是c語言實現的話我們就用 fgets 獲取一行

直接獲取即可,完了之后放進提前開辟并傳進函數里面的數組中

需要說的一點就是,我們用戶輸入的指令看起來是這樣的:

ls -a -l

但其實最后還會加一個回車表示確定,所以實際上就是這樣子的:

ls -a -l\n

綜上,我們就需要將最后一個位置設置為\0,上面的ZERO其實就是\0,只不過設置成了宏而已

分割命令

void SplitCommand(char *command)
{gArgv[0] = strtok(command, SEP);int index = 1;while (gArgv[index++] = strtok(NULL, SEP));// 在最后一次判斷的時候,發現沒有字符串了,strtok 就會返回NULL// 正好讓 gArgv 的最后一個位置變為 NULL,這樣在后面進程程序替換的時候就可以直接用
}

這里其實就是strtok函數的使用技巧

先說說為什么要分割:

我們獲取了程序之后,不是說所有的功能都是我們自己寫,像ls、cd、mkdir等等,這些指令在我們電腦中的磁盤上人家都幫我們寫好了,shell也是調用的這些文件

所以我們要做的,就是在用戶輸入指令的時候,我們能將這些程序加載進來

這里就需要用到進程程序替換了,并且我們現在只有用戶輸入的數據,我們到后面肯定是execvp用起來最好,所以分割完直接放函數里面當參數即可,就實現這個功能了

再來說說 strtok

當你第一次使用strtok的時候,傳入一個數組當參數(第一個參數位置,第二個是分隔符)

當你從第二次開始,第一個參數直接傳NULL,就代表使用你一剛開始用個的那個數組

這也就是為什么一開始傳command數組,后面傳NULL的原因

接著最妙的就是,當strtok檢測到沒有可以分割的了,就會返回NULL,而我們的數組(execvp函數要求)最后一個位置必須以NULL結尾

檢查是否是內建命令

int CheckBuildin()
{int yes = 0;const char *enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0){yes = 1;Cd();}else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){yes = 1;printf("%d\n", lastcode);lastcode = 0;}return yes;
}

直接就是if、else檢查判斷就行,有多少個內建命令就有多少個if、else

cd命令實現

void Cd()
{const char* path = gArgv[1];if(path == NULL) path = GetHome();chdir(path);// 更新環境變量char cwd[SIZE];char temp[SIZE];getcwd(temp, sizeof temp);snprintf(cwd, sizeof(cwd), "PWD=%s", temp);setenv("PWD", cwd, 1);
}

這里主要用到的是chdir函數,ps:chdir支持直接使用 . 或者 ..

先說獲取path,程序走到這里,那么用戶輸入的指令一定是cd,那么就會有像cd ..這樣的指令

那么我們獲取完之后,分割的指令中,第二個就一定是path,直接獲取就好(下標是1)

當然也有可能用戶直接輸入一個cd,后面啥也不跟,我們隨便處理一下,給他返回到家目錄即可

最后我們在chdir之后,還需要更新一下路徑

這里我們先獲取環境變量中的PWD,然后使用snprintf寫入數組中,以PWD:%s的格式

最后直接用setenv函數修改(或者說更新)環境變量即可

當然你也可以在最后使用putenv,這樣的話,我們的cwd數組就需要開辟在全局,不然函數運行結束之后,cwd數組也就沒了,這樣putenv找不到他,就會直接異常終止我們的程序,顯示段錯誤(Segmentation fault)

setenv就不用,這里推薦這個,因為他更現代、好用

進程程序替換執行程序

void ExecuteCommand()
{pid_t id = fork();if (id == 0){// 子進程execvp(gArgv[0], gArgv);exit(errno);}else{// 父進程int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){if(WIFEXITED(status) == 0){lastcode = WEXITSTATUS(status);printf("exit code: %d, exit signal:%d\n", WEXITSTATUS(status), WTERMSIG(status));}}}
}

就是execvp函數的使用,創建子進程直接替換,然后父進程在外面waitpid阻塞等待即可,也不用父進程干什么事情

總代碼

MyShell.c

點開這個鏈接,里面就是代碼,放在gitee里面了

結語

這篇文章到這里就結束啦!!~( ̄▽ ̄)~*

如果覺得對你有幫助的,可以多多關注一下喔

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86695.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86695.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86695.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一個完整的日志收集方案:Elasticsearch + Logstash + Kibana+Filebeat (二)

&#x1f4c4; 本地 Windows 部署 Logstash 連接本地 Elasticsearch 指南 ? 目標 在本地 Windows 上安裝并運行 Logstash配置 Logstash 將數據發送至本地 Elasticsearch測試數據采集與 ES 存儲流程 &#x1f9f0; 前提條件 軟件版本要求安裝說明Java17Oracle JDK 下載 或 O…

Java使用Selenium反爬蟲優化方案

當我們爬取大站的時候&#xff0c;就得需要對抗反爬蟲機制的場景&#xff0c;因為項目要求使用Java和Selenium。Selenium通常用于模擬用戶操作&#xff0c;但效率較低&#xff0c;所以需要我們結合其他技術來實現高效。 在 Java 中使用 Selenium 進行高效反爬蟲對抗時&#xff…

狀態管理方案對比與決策

1. 狀態管理的基本概念 現代前端應用隨著功能復雜度提升&#xff0c;狀態管理已成為架構設計的核心挑戰。狀態管理本質上解決的是數據的存儲、變更追蹤和響應式更新問題&#xff0c;以確保UI與底層數據保持同步。 核心挑戰: 狀態共享與組件通信可預測的狀態變更性能優化與重…

Fetch與Axios:區別、聯系、優缺點及使用差異

Fetch與Axios&#xff1a;區別、聯系、優缺點及使用差異 文章目錄 Fetch與Axios&#xff1a;區別、聯系、優缺點及使用差異一、聯系二、區別1. 瀏覽器支持與兼容性2. 響應處理3. 請求攔截和響應攔截4. 錯誤處理 三、優缺點1. Fetch API優點缺點 2. Axios優點缺點 四、使用上的差…

【Docker】快速入門與項目部署實戰

我們在部署一個項目時&#xff0c;會出現一系列問題比如&#xff1a; 命令太多了&#xff0c;記不住軟件安裝包名字復雜&#xff0c;不知道去哪里找安裝和部署步驟復雜&#xff0c;容易出錯 其實上述問題不僅僅是新手&#xff0c;即便是運維在安裝、部署的時候一樣會覺得麻煩…

Java面試題尚硅谷版第1季

1、寫出如下代碼運行結果 1.1、 使用局部變量表和操作數棧解題 1.2、使用前置和后置遞增解題 2、寫一個單例模式 2.1、考察知識點 2.2、單例模式實現 3、類加載和初始化順序 package classload;public class Father {private int i test();private static int j method();st…

關于Qt阻斷樣式繼承的解決辦法

引言 在使用 Qt 開發桌面應用時&#xff0c;借助樣式表&#xff08;StyleSheet&#xff09;來統一定義界面風格是非常常見的做法。通常&#xff0c;你會在主程序中通過 qApp->setStyleSheet(...) 或者直接給某個父控件設置樣式表&#xff0c;讓所有的子控件都采用相同的配色…

鼠標右鍵添加新建某種文件的方法

場景 我經常用到.emmx&#xff0c;.eddx文件&#xff0c;電腦上裝的是wpsX億圖&#xff08;因為有wps會員&#xff09;&#xff0c;沒有開億圖會員。 然后問題就是&#xff0c;思維導圖和流程圖我都能正常開&#xff0c;正常編輯&#xff0c;但鼠標右鍵沒有新建這兩個文件的按…

Inxpect安全雷達傳感器與控制器:動態檢測 + 抗干擾技術重構工業安全防護體系

Inxpect 推出工業安全領域新型智能傳感器與控制器&#xff0c;其核心產品為雷達掃描儀&#xff0c;具備動態調整檢測區域、抗干擾能力強等特點&#xff0c;可精準檢測危險區域人員進入或存在情況&#xff0c;適用于移動機器人等場景。 Inxpect安全雷達傳感器核心功能 動態檢測…

【AI學習】李廣密與階躍星辰首席科學家張祥雨對談:多模態發展的歷史和未來

仔細閱讀了文章《專訪張祥雨&#xff1a;多模態推理和自主學習是未來的 2 個 「GPT-4」 時刻》 https://mp.weixin.qq.com/s/892QuRPH9uP6zN6dS-HZMw 非常贊嘆的一篇文章&#xff0c;說清楚了NLP、CV發展中的許多重大問題&#xff0c;讀來醍醐灌頂&#xff01;這樣的文章&…

C++中std::deque詳解和實戰工程代碼示例

C中std::deque詳解和實戰工程代碼示例 std::deque&#xff08;雙端隊列&#xff09;是 C 標準庫中的一個序列容器&#xff0c;與 std::vector 類似&#xff0c;但它支持從頭部和尾部高效地插入和刪除元素。它底層采用分段連續空間實現&#xff0c;兼具靈活性與性能。 一、基本…

【AI大模型入門指南】概念與專有名詞詳解 (二)

【AI大模型入門指南】概念與專有名詞詳解 &#xff08;二&#xff09; 一 、前言 當你和聊天機器人聊得天花亂墜時&#xff0c;當你用文字讓AI生成精美圖片時&#xff0c;當手機相冊自動幫你分類照片時 —— 這些看似智能的操作背后&#xff0c;都藏著 AI 大模型的身影。 本…

AIStor 的模型上下文協議 (MCP) 服務器:管理功能

在本系列的上一篇博文中&#xff0c;我們討論了 MinIO AIStor 的模型上下文協議 (MCP) 服務器的基本用戶級功能。我們學習了如何使用人類語言命令查看存儲桶的內容、分析對象并標記它們以便將來處理&#xff0c;以及如何通過 LLM&#xff08;例如 Anthropic Claude&#xff09;…

期權末日輪實值期權盈利未平倉怎么辦?

本文主要介紹期權末日輪實值期權盈利未平倉怎么辦&#xff1f;期權末日輪實值期權盈利未平倉該怎么辦&#xff0c;需要明確幾個關鍵點&#xff1a;末日輪指的是期權到期日臨近的時候&#xff0c;通常指最后一周&#xff0c;尤其是最后一天&#xff0c;這時候時間價值衰減很快&a…

C++/Qt 聯合編程中的定時器使用陷阱:QObject::startTimer 報錯詳解

在 Qt 開發中&#xff0c;QTimer 是一個常用的工具類&#xff0c;用于處理定時事件。但不少開發者在 C/Qt 聯合編程&#xff0c;尤其是在工具類、靜態類、線程中使用定時器時&#xff0c;會遇到如下令人困惑的報錯&#xff1a; QObject::startTimer: Timers can only be used …

CentOS7.9 查詢運維安全日志,排查惡意用戶

1、查看系統版本 cat /etc/redhat-release uname -a 2、查看所有賬號 cat /etc/shadow 3、修改 root 密碼 passwd 3、查看賬號ID id jinzhi 4、查看登錄日志 lastlog 5、查看操作日志 cat .bash_history sudo cat /home/yunwei/.bash_history sudo grep root /va…

多模態大語言模型arxiv論文略讀(117)

Training-free Zero-shot Composed Image Retrieval via Weighted Modality Fusion and Similarity ?? 論文標題&#xff1a;Training-free Zero-shot Composed Image Retrieval via Weighted Modality Fusion and Similarity ?? 論文作者&#xff1a;Ren-Di Wu, Yu-Yen L…

如何正確的配置eureka server集群

將 Eureka Server 實例的 hostname 都配置成相同的值&#xff0c;在 Eureka Server 集群環境下同樣是不推薦且通常會導致嚴重問題的&#xff0c; 核心問題&#xff1a;Eureka Server 集群的工作機制 Eureka Server 集群通過相互注冊&#xff08;Peering&#xff09;來實現高可…

AI支持下的-ArcGIS數據處理、空間分析、可視化及多案例綜合應用

查看原文>>> 從入門到精通-AI支持下的-ArcGIS數據處理、空間分析、可視化及多案例綜合應用 結合ArcGIS和GPT的優勢&#xff0c;本文重點進行AI大模型應用、ArcGIS工作流程及功能、Prompt使用技巧、AI助力工作流程、AI助力數據讀取與處理、AI助力空間分析、AI助力遙感…

vue3-ts: v-model 和 props 的關系

在 Vue.js 中&#xff0c;v-model 是一個語法糖&#xff0c;它實際上是 :value 和 input 事件的組合。 當你使用 v-model 綁定一個組件時&#xff0c;默認情況下&#xff0c;組件會通過 props 接收 value 這個 prop&#xff0c; 并通過觸發 input 事件來更新父組件中的數據。 …