Linux自行實現的一個Shell(15)

文章目錄

  • 前言
  • 一、頭文件和全局變量
    • 頭文件
    • 全局變量
  • 二、輔助函數
    • 獲取用戶名
    • 獲取主機名
    • 獲取當前工作目錄
    • 獲取最后一級目錄名
    • 生成命令行提示符
    • 打印命令行提示符
  • 三、命令處理
    • 獲取用戶輸入
    • 解析命令行
    • 執行外部命令
  • 四、內建命令
    • 添加環境變量
    • 檢查和執行內建命令
  • 五、初始化
    • 初始化環境變量
    • 主循環
  • 總結


前言

??MyShell源代碼公開

??本篇是對之前知識的一個綜合運用,也是檢驗你是否對前置知識有個較為透徹的理解的好時機

在這里插入圖片描述


一、頭文件和全局變量

頭文件

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

全局變量

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;// 全局的命令行參數表
char *gargv[argvnum];
int gargc = 0;// 全局的變量
int lastcode = 0;// 我的系統的環境變量
char *genv[envnum];// 全局的當前shell工作路徑 
char pwd[basesize];
char pwdenv[basesize];
  • basesize:緩沖區基本大小
  • argvnum 和 envnum:參數和環境變量的最大數量
  • gargv 和 gargc:存儲解析后的命令參數
  • lastcode:存儲上一條命令的退出狀態碼
  • genv:存儲環境變量
  • pwd 和 pwdenv:存儲當前工作目錄

二、輔助函數

獲取用戶名

string GetUserName()
{string name = getenv("USER");return name.empty() ? "None" : name;
}
  • 通過 getenv(“USER”) 獲取當前用戶名
  • 如果獲取失敗返回 “None”

獲取主機名

string GetHostName()
{string hostname = getenv("HOSTNAME");return hostname.empty() ? "None" : hostname;
}
  • 通過 getenv(“HOSTNAME”) 獲取主機名
  • 如果獲取失敗返回 “None”

獲取當前工作目錄

string GetPwd()
{if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);putenv(pwdenv); // PWD=XXXreturn pwd;
}
  • 使用 getcwd() 獲取當前工作目錄
  • 如果失敗返回 “None”
  • 將當前目錄設置到環境變量 PWD 中
  • 返回當前目錄路徑

獲取最后一級目錄名

string LastDir()
{string curr = GetPwd();if(curr == "/" || curr == "None") return curr;size_t pos = curr.rfind("/");if(pos == std::string::npos) return curr;return curr.substr(pos+1);
}
  • 獲取當前目錄
  • 如果是根目錄或無效目錄直接返回
  • 查找最后一個 ‘/’ 的位置
  • 返回最后一個 ‘/’ 之后的部分

生成命令行提示符

string MakeCommandLine()
{char command_line[basesize];snprintf(command_line, basesize, "[%s@%s %s]# ",\GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());return command_line;
}
  • 生成類似 [user@host dirname]# 的提示符

打印命令行提示符

void PrintCommandLine() // 1. 命令行提示符
{printf("%s", MakeCommandLine().c_str());fflush(stdout);
}
  • 打印提示符
  • fflush(stdout) 確保立即顯示

三、命令處理

獲取用戶輸入

bool GetCommandLine(char command_buffer[], int size)
{char *result = fgets(command_buffer, size, stdin);if(!result){return false;}// 因為 command_line 里有一個 \n,我們把它替換成 \0 即可command_buffer[strlen(command_buffer)-1] = '\0';if(strlen(command_buffer) == 0) return false;return true;
}
  • 使用 fgets 讀取用戶輸入
  • 移除末尾的換行符
  • 檢查是否為空輸入

解析命令行

??獲取用戶輸入后,我們需要將接收到的字符串拆分為命令及其參數

??將接收到的字符串拆開

??通過 strtok 函數,我們可以將一個字符串按照特定的分隔符打散,依次返回子串

void ParseCommandLine(char command_buffer[], int len)
{(void)len;memset(gargv, 0, sizeof(gargv));gargc = 0;const char *sep = " ";gargv[gargc++] = strtok(command_buffer, sep);while((bool)(gargv[gargc++] = strtok(nullptr, sep)));gargc--;
}
  • 重置參數數組和計數器
  • 使用 strtok 以空格為分隔符分割命令
  • 將分割后的參數存入 gargv 數組
  • 調整 gargc 為實際參數數量

執行外部命令

bool ExecuteCommand()
{pid_t id = fork();if(id < 0) return false;if(id == 0){execvpe(gargv[0], gargv, genv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){if(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}else{lastcode = 100;}return true;}return false;
}
  • 創建子進程
  • 子進程使用 execvpe 執行命令
  • 父進程等待子進程結束
  • 保存子進程退出狀態到 lastcode

四、內建命令

??內建命令是指直接內置在操作系統內核中的一些命令,與普通的外部命令(外部程序文件)不同。這些內建命令是直接由shell解釋器(如Bash、Zsh等)所處理,而不需要通過外部文件的方式來執行。這些內建命令通常在操作系統的shell環境中被頻繁使用,并且執行速度更快,因為它們不需要創建新的進程來執行

??在Unix和類Unix操作系統中,通常會有一些內建命令,比如cd、echo、exit等。這些命令不需要單獨的可執行文件,而是直接由shell內核提供支持。當用戶在shell中輸入這些命令時,shell會直接處理它們,而不需要通過搜索系統路徑來找到可執行文件

??值得一提的是,某些shell也允許用戶通過自定義的方式添加新的內建命令,這樣用戶可以根據自己的需求來擴展shell的內建功能

添加環境變量

void AddEnv(const char *item)
{int index = 0;while(genv[index]){index++;}genv[index] = (char*)malloc(strlen(item)+1);strncpy(genv[index], item, strlen(item)+1);genv[++index] = nullptr;
}
  • 找到環境變量數組的末尾
  • 分配內存并復制新環境變量
  • 確保數組以 NULL 結尾

檢查和執行內建命令

bool CheckAndExecBuiltCommand()
{if(strcmp(gargv[0], "cd") == 0){if(gargc == 2){chdir(gargv[1]);lastcode = 0;}else{lastcode = 1;}return true;}else if(strcmp(gargv[0], "export") == 0){if(gargc == 2){AddEnv(gargv[1]);lastcode = 0;}else{lastcode = 2;}return true;}else if(strcmp(gargv[0], "env") == 0){for(int i = 0; genv[i]; i++){printf("%s\n", genv[i]);}lastcode = 0;return true;}else if(strcmp(gargv[0], "echo") == 0){if(gargc == 2){if(gargv[1][0] == '$'){if(gargv[1][1] == '?'){printf("%d\n", lastcode);lastcode = 0;}}else{printf("%s\n", gargv[1]);lastcode = 0;}}else{lastcode = 3;}return true;}return false;
}

支持的內建命令有:

  1. cd:改變工作目錄
  2. export:設置環境變量
  3. env:顯示所有環境變量
  4. echo:打印內容或上一條命令的退出碼

五、初始化

初始化環境變量

void InitEnv()
{extern char **environ;int index = 0;while(environ[index]){genv[index] = (char*)malloc(strlen(environ[index])+1);strncpy(genv[index], environ[index], strlen(environ[index])+1);index++;}genv[index] = nullptr;
}

從父進程復制環境變量

主循環

int main()
{InitEnv();char command_buffer[basesize];while(true){PrintCommandLine();if( !GetCommandLine(command_buffer, basesize) ){continue;}ParseCommandLine(command_buffer, strlen(command_buffer));if ( CheckAndExecBuiltCommand() ){continue;}ExecuteCommand();}return 0;
}

主循環流程:

  1. 打印提示符
  2. 獲取用戶輸入
  3. 解析命令
  4. 嘗試執行內建命令
  5. 如果不是內建命令,則執行外部命令

總結

??感覺如何呢!

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

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

相關文章

RocketMQ和kafka 的區別

一、數據可靠性與容錯機制 數據可靠性 RocketMQ支持同步刷盤和同步復制&#xff0c;確保消息寫入磁盤后才返回確認&#xff0c;單機可靠性高達10個9&#xff0c;即使操作系統崩潰也不會丟失數據。而Kafka默認采用異步刷盤和異步復制&#xff0c;雖然吞吐量高&#xff0c;但極端…

在 openEuler 24.03 (LTS) 操作系統上添加 ollama 作為系統服務的步驟

以下是在 openEuler 操作系統上添加 ollama 作為系統服務的步驟&#xff1a; 創建 systemd 服務文件 sudo vi /etc/systemd/system/ollama.service將以下內容寫入服務文件&#xff08;按需修改參數&#xff09;&#xff1a; [Unit] DescriptionOllama Service Afternetwork.…

光譜相機的關鍵技術參數

光譜相機的關鍵技術參數直接影響其數據獲取能力和應用場景適配性。以下是核心參數的詳細解析&#xff0c;涵蓋光譜性能、空間性能、硬件性能及環境適應性&#xff1a; 一、光譜性能參數? ?1. 光譜范圍&#xff08;Spectral Range&#xff09;? ?定義?&#xff1a;相機可…

ARM內核與寄存器

ARM內核與寄存器詳解 目錄 ARM架構概述ARM處理器模式 Cortex-M3內核的處理器模式Cortex-A系列處理器模式 ARM寄存器集 通用寄存器程序計數器(PC)鏈接寄存器(LR)堆棧指針(SP)狀態寄存器(CPSR/SPSR) 協處理器寄存器NEON和VFP寄存器寄存器使用規范常見ARM指令與寄存器操作 ARM架…

Git 拉取時常見沖突及解決方法總結

Git 拉取時常見沖突及解決方法總結 一、常見錯誤場景1. 本地修改與遠程修改沖突解決方法 2. 未跟蹤文件與遠程文件沖突解決方法 3. 子模塊權限問題解決方法 二、總結 在日常開發中&#xff0c;使用 Git 進行團隊協作和代碼管理時&#xff0c;經常會遇到拉取代碼&#xff08;git…

深度學習、圖像算法學習記錄

深度學習加速 綜述文檔&#xff1a; https://chenzomi12.github.io/02Hardware01Foundation/02ArchSlim.html winograd: https://zhuanlan.zhihu.com/p/260109670 ncnn 1.修改模型結構&#xff0c;優化模型內存訪問次數&#xff0c;加速。 VGG 和 InceptionNet &#xff1a; …

Java中的Exception和Error有什么區別?還有更多擴展

概念 在Java中&#xff0c;Exception和Error都是Throwable的子類&#xff0c;用于處理程序中的錯誤和異常情況。 然而&#xff0c;它們在用途和處理方式上有顯著的不同&#xff1a; Exception&#xff1a; 用于表示程序在正常運行過程中可能出現的錯誤&#xff0c;如文件未找…

文章記單詞 | 第26篇(六級)

一&#xff0c;單詞釋義 actor&#xff1a;名詞&#xff0c;演員mask&#xff1a;名詞&#xff0c;面具&#xff1b;口罩&#xff1b;遮蓋物&#xff1b;動詞&#xff0c;掩飾&#xff1b;戴面具&#xff1b;遮蓋construct&#xff1a;動詞&#xff0c;建造&#xff1b;構造&a…

LeetCode算法題(Go語言實現)_38

題目 給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。 一、代碼實現 type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode }func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {if root nil || root p || root q {return root}left : lowes…

Java 基礎語法、Java注釋

Java 基礎語法 一個 Java 程序可以認為是一系列對象的集合,而這些對象通過調用彼此的方法來協同工作。下面簡要介紹下類、對象、方法和實例變量的概念。 對象:對象是類的一個實例,有狀態和行為。例如,一條狗是一個對象,它的狀態有:顏色、名字、品種;行為有:搖尾巴、叫…

用VScode來編寫前后端——構建基礎框架

前言 我寫這一個板塊的原因是我參加了我們學校的新生項目課&#xff0c;需要創立一個系統&#xff0c;我們小組選的標題的基于計算機視覺的商品識別系統&#xff0c;那么我們需要一個網站來展示我們的功能&#xff0c;故寫這些來記錄一下自己&#xff0c;大家如果有什么問題的話…

git clone阻塞問題

問題描述 git clone采用的ssh協議&#xff0c;在克隆倉庫的時候&#xff0c;會經常卡一下&#xff0c;亦或是直接卡死不動。 最開始以為是公司電腦配置的問題&#xff0c;想著自己實在解決不了找it幫忙。 查閱資料發現&#xff0c;最終發現是git版本的問題&#xff0c;這個是…

WEB攻防-Java安全JNDIRMILDAP五大不安全組件RCE執行不出網不回顯

目錄 1. RCE執行-5大類函數調用 1.1 Runtime方式 1.2 Groovy執行命令 1.3 腳本引擎代碼注入 1.4 ProcessImpl 1.5 ProcessBuilder 2. JNDI注入(RCE)-RMI&LDAP&高版本 2.1 RMI服務中的JNDI注入場景 2.2 LDAP服務中的JNDI注入場景 攻擊路徑示例&#…

【Hadoop入門】Hadoop生態之Sqoop簡介

1 什么是Sqoop&#xff1f; 在企業的數據架構中&#xff0c;關系型數據庫與Hadoop生態系統之間的數據流動是常見且關鍵的需求。Apache Sqoop&#xff08;SQL-to-Hadoop&#xff09;正是為解決這一問題而生的高效工具&#xff0c;它專門用于在結構化數據存儲&#xff08;如RDBMS…

如何自動檢測使用的組件庫有更新

&#x1f916; 作者簡介&#xff1a;水煮白菜王&#xff0c;一位前端勸退師 &#x1f47b; &#x1f440; 文章專欄&#xff1a; 前端專欄 &#xff0c;記錄一下平時在博客寫作中&#xff0c;總結出的一些開發技巧和知識歸納總結?。 感謝支持&#x1f495;&#x1f495;&#…

Go語言編寫一個進銷存Web軟件的demo

Go語言編寫一個進銷存Web軟件的demo 用戶現在要求用。之前他們已經討論了用Django實現的方案&#xff0c;現在突然切換到Go&#xff0c;可能有幾個原因。首先&#xff0c;用戶可能對Go語言感興趣&#xff0c;或者他們公司的技術棧轉向了Go。其次&#xff0c;用戶可能希望比較不…

【前綴和】矩陣區域和(medium)

矩陣區域和&#xff08;medium&#xff09; 題?描述&#xff1a;解法&#xff1a;代碼Java 算法代碼&#xff1a;C 算法代碼&#xff1a; 題?描述&#xff1a; 題?鏈接&#xff1a;1314. 矩陣區域和 給你?個 m x n 的矩陣 mat 和?個整數 k &#xff0c;請你返回?個矩陣 …

Java學習手冊:Java發展歷史與版本特性

Java作為全球最流行的編程語言之一&#xff0c;其發展歷程不僅見證了技術的演進&#xff0c;也反映了軟件開發模式的變革。從1995年的首次發布到如今的持續更新&#xff0c;Java始終保持著強大的生命力和廣泛的影響力。本文將簡要回顧Java的發展歷程&#xff0c;并重點介紹其關…

winserver2022備份

安裝備份&#xff0c;然后等待安裝完成即可 然后可以在這里看到安裝好的win server2022備份 一直下一步然后到這里 不要用本地文件夾備份 備份到遠程服務器&#xff0c;遠程服務器路徑 然后確定備份即可 如何恢復呢&#xff1f; 點擊右側的恢復就可以了 打開任務計劃程序 這…

Unity 設置彈窗Tips位置

根據鼠標位于屏幕的區域&#xff0c;設置彈窗錨點以及位置 public static void TipsPos(Transform tf) {//獲取ui相機var uiCamera GetUICamera();var popup tf.GetComponent<RectTransform>();//獲取鼠標位置Vector2 mousePos Input.mousePosition;float screenWidt…