自定義Shell命令行解釋器

目錄

1、目標

2、顯示命令提示符

2.1 getenv

2.2 getcwd

2.3 putenv

3、獲取用戶輸入的命令

4、解析命令

5、處理內建命令

6、處理外部命令

7、完整代碼

7.1 myshell.cpp

7.2 Makefile


1、目標

實現一個Linuxmyshell,有以下基本的功能。

  1. 顯示命令提示符
  2. 獲取用戶輸入的命令
  3. 解析命令
  4. 處理內建命令
  5. 處理外部命令

myshell有一張命令行參數表環境變量表(繼承bash的,其實應該要從配置文件中獲取,但比較麻煩)。

2、顯示命令提示符

這里就要了解一下:getenv(),getcwd(),putenv()了。

2.1 getenv

獲取環境變量

char *getenv(const char *name);
  • 根據環境變量名(如 "PATH")返回其對應的值(字符串)。

  • 如果變量不存在返回 NULL

2.2 getcwd

獲取當前工作路徑

char *getcwd(char *buf, size_t size);
  • 當前工作目錄的絕對路徑寫入 buf,并返回 buf

  • 需確保 buf?足夠大(否則返回 NULL,errno = ERANGE)。

注意:

進程的環境變量表中的PWD,是根據進程的CWD(進程當前的工作路徑,在/proc/pid/下可以看到),進行更新的。?

2.3 putenv

設置環境變量

int putenv(char *string);
  • 設置環境變量,格式"KEY=VALUE"?的字符串。如果已存在相同的KEY,就覆蓋VALUE

  • 成功返回 0失敗返回非零

注意:

putenv()傳的是指針要放在環境變量表里生命周期要和程序一樣長,所以傳全局變量

改變這個全局變量環境變量表中也會改變(當時不太理解,中坑了。)

const char* getUserName()
{const char* USER = getenv("USER");return USER == NULL?"None":USER;
}const char* getHostName()
{const char* HOSTNAME = getenv("HOSTNAME");return HOSTNAME == NULL?"None":HOSTNAME;
}std::string getCwd()
{char cwdenv[1024];char* cwd = getcwd(cwdenv,sizeof(cwdenv));return cwd == NULL? "None":cwdenv; // 因為返回的是局部變量,所以使用string,進行拷貝
}char pwd[1024];
void cmdPrompt()
{std::string path = getCwd();// 更新環境變量PWD,不能putenv局部變量。snprintf(pwd,sizeof(pwd),"PWD=%s",path.c_str());putenv(pwd);if(path.size() != 1) // 不是/根目錄{int index = path.rfind('/');path = path.substr(index+1);}std::cout<<"["<<getUserName()<<"@"<<getHostName()<<" "<<path<<"]"<<"$$";
}

3、獲取用戶輸入的命令

bool getCmd(char cmd[],int size)
{char* s = fgets(cmd,size,stdin);if(s != NULL){int len = strlen(cmd);cmd[len-1] = '\0'; // fgets會讀取'\n',需要處理}return s != NULL;
}

獲取成功,返回true,獲取失敗,返回false。?

4、解析命令

bool parseCmd(char cmd[],char* argv[])
{const char* delim = " ";char* token = strtok(cmd,delim);int i = 0;while(token != NULL){argv[i] = token;token = strtok(NULL,delim);++i;}argv[i] = NULL; // 進程替換,要求以NULL結尾return i != 0;
}
char *strtok(char *str, const char *delim);
  • str:要分割的字符串。第一次調用時傳入原始字符串后續調用傳入 NULL

  • delim:包含所有可能分隔符的字符串。

  • 返回分割出的子字符串的指針。如果沒有更多子字符串,則返回 NULL

  • strtok 會在找到的分隔符位置插入 '\0' 字符,因此會修改原始字符串。

5、處理內建命令

內建命令需要父進程自己執行(如:cd,需要改變自己的路徑),或父進程自己執行效率更高(如:echo)。?

std::string getCwd()
{char cwdenv[1024];char* cwd = getcwd(cwdenv,sizeof(cwdenv));return cwd == NULL? "None":cwdenv; // 因為返回的是局部變量,所以使用string,進行拷貝
}const char* getHome()
{const char* HOME = getenv("HOME");return HOME == NULL?"None":HOME;
}const char* getOldPwd()
{const char* OLDPWD = getenv("OLDPWD");return OLDPWD == NULL?"None":OLDPWD;
}char oldPwd[1024];
void cd(char* argv[])
{ std::string cwdenv = getCwd();if(argv[1] == NULL || strcmp(argv[1],"~") == 0){snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd); // 更新OLDPWDchdir(getHome());}else if(strcmp(argv[1],"-") == 0){chdir(getOldPwd());snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd);}else{snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd); // 更新OLDPWDchdir(argv[1]);}
}int lastExitno = 0;
void echo(char* argv[])
{if(argv[1] == NULL)return;std::string cmd = argv[1];// echo $?// echo $PATH// echo "hello world"if(cmd[0] == '$'){if(cmd.substr(1) == "?"){std::cout<<lastExitno<<std::endl;lastExitno = 0;}else{if(getenv(cmd.substr(1).c_str()))std::cout<<getenv(cmd.substr(1).c_str())<<std::endl;}}else{std::cout<<cmd<<std::endl;}
}bool executeBuiltIn(char* argv[])
{std::string cmd = argv[0];if(cmd == "cd"){cd(argv);return true;}else if(cmd == "echo"){echo(argv);return true;}// ...else{}return false;
}

注意:

    else if(strcmp(argv[1],"-") == 0){chdir(getOldPwd());snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd);}

如果之前putenv(oldPwd),傳的是指針,

若先snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());

那么,環境變量表中的OLDPWD就已經是CWD了,那么chdir(getOldPwd());就出錯了。

6、處理外部命令

外部命令,防止父進程掛了,所以創建子進程,進行程序替換(可執行任意程序)。?

int executeExternal(char* argv[])
{pid_t id = fork();if(id == -1)return 1;if(id == 0){execvp(argv[0],argv);exit(2);}int status = 0;pid_t wid = waitpid(id,&status,0);if(wid == id && WIFEXITED(status)) // 子進程退出,且是正常退出lastExitno = WEXITSTATUS(status);return 0;
}

7、完整代碼

7.1 myshell.cpp

#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>const char* getUserName()
{const char* USER = getenv("USER");return USER == NULL?"None":USER;
}const char* getHostName()
{const char* HOSTNAME = getenv("HOSTNAME");return HOSTNAME == NULL?"None":HOSTNAME;
}const char* getHome()
{const char* HOME = getenv("HOME");return HOME == NULL?"None":HOME;
}std::string getCwd()
{char cwdenv[1024];char* cwd = getcwd(cwdenv,sizeof(cwdenv));return cwd == NULL? "None":cwdenv; // 因為返回的是局部變量,所以使用string,進行拷貝
}const char* getOldPwd()
{const char* OLDPWD = getenv("OLDPWD");return OLDPWD == NULL?"None":OLDPWD;
}char pwd[1024];
void cmdPrompt()
{std::string path = getCwd();// 更新環境變量PWD,不能putenv局部變量。snprintf(pwd,sizeof(pwd),"PWD=%s",path.c_str());putenv(pwd);if(path.size() != 1) // 不是/根目錄{int index = path.rfind('/');path = path.substr(index+1);}std::cout<<"["<<getUserName()<<"@"<<getHostName()<<" "<<path<<"]"<<"$$";
}bool getCmd(char cmd[],int size)
{char* s = fgets(cmd,size,stdin);if(s != NULL){int len = strlen(cmd);cmd[len-1] = '\0'; // fgets會讀取'\n',需要處理}return s != NULL;
}bool parseCmd(char cmd[],char* argv[])
{const char* delim = " ";char* token = strtok(cmd,delim);int i = 0;while(token != NULL){argv[i] = token;token = strtok(NULL,delim);++i;}argv[i] = NULL; // 進程替換,要求以NULL結尾return i != 0;
}char oldPwd[1024];
void cd(char* argv[])
{ std::string cwdenv = getCwd();if(argv[1] == NULL || strcmp(argv[1],"~") == 0){snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd); // 更新OLDPWDchdir(getHome());}else if(strcmp(argv[1],"-") == 0){chdir(getOldPwd());snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd);}else{snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd); // 更新OLDPWDchdir(argv[1]);}
}int lastExitno = 0;
void echo(char* argv[])
{if(argv[1] == NULL)return;std::string cmd = argv[1];// echo $?// echo $PATH// echo "hello world"if(cmd[0] == '$'){if(cmd.substr(1) == "?"){std::cout<<lastExitno<<std::endl;lastExitno = 0;}else{if(getenv(cmd.substr(1).c_str()))std::cout<<getenv(cmd.substr(1).c_str())<<std::endl;}}else{std::cout<<cmd<<std::endl;}
}bool executeBuiltIn(char* argv[])
{std::string cmd = argv[0];if(cmd == "cd"){cd(argv);return true;}else if(cmd == "echo"){echo(argv);return true;}// ...else{}return false;
}int executeExternal(char* argv[])
{pid_t id = fork();if(id == -1)return 1;if(id == 0){execvp(argv[0],argv);exit(2);}int status = 0;pid_t wid = waitpid(id,&status,0);if(wid == id && WIFEXITED(status)) // 子進程退出,且是正常退出lastExitno = WEXITSTATUS(status);return 0;
}int main()
{while(true){// 命令提示符cmdPrompt();// 獲取用戶輸入命令char cmd[1024] = {0};if(!getCmd(cmd,sizeof(cmd)))continue;// 解析命令char* argv[1024] = {0};if(!parseCmd(cmd,argv))continue;// 內建命令if(executeBuiltIn(argv))continue;// 執行命令executeExternal(argv);}return 0;
}

7.2 Makefile

TARGET := myshell
SRCS := myshell.cpp
SUFFIX := .cpp
OBJS := $(SRCS:$(SUFFIX)=.o)
CC := g++$(TARGET): $(OBJS)$(CC) -o $@ $^%.o: %$(SUFFIX)$(CC) -c $<.PHONY: clean
clean:rm -f $(OBJS) $(TARGET)

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

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

相關文章

Laplace 噪聲

Laplace 噪聲是一種特定概率分布&#xff08;拉普拉斯分布&#xff09;產生的隨機擾動。它是差分隱私&#xff08;Differential Privacy, DP&#xff09;中最核心、最常用的噪聲機制之一。它的核心作用是在不泄露個體信息的前提下&#xff0c;允許從包含敏感數據的數據庫中提取…

基于空天地一體化網絡的通信系統matlab性能分析

目錄 1.引言 2.算法仿真效果演示 3.數據集格式或算法參數簡介 4.MATLAB核心程序 5.算法涉及理論知識概要 5.1 QPSK調制原理 5.2 空天地一體化網絡信道模型 5.3 空天地一體化網絡信道特性 6.參考文獻 7.完整算法代碼文件獲得 1.引言 空天地一體化網絡是一種將衛星通信…

【Delphi】接收windows文件夾中文件拖拽

本文根據EmailX45的視頻文件&#xff0c;進行了優化改進&#xff0c;原文參見&#xff1a;Delphi: Drag and Drop Files from Explorer into TPanel / TMemo - YouTube 在Windows中&#xff0c;如果將選擇的文件拖動到Delphi程序的控件上&#xff0c;有很多實現方法&#xff0c…

基于熱力學熵增原理的EM-GAN

簡介 簡介:提出基于熱力學熵增原理的EM-GAN,通過生成器熵最大化約束增強輸出多樣性。引入熵敏感激活函數與特征空間熵計算模塊,在MNIST/CelebA等數據集上實現FID分數提升23.6%,有效緩解模式崩潰問題。 論文題目:Entropy-Maximized Generative Adversarial Network (EM-G…

HashMap與ConcurrentHashMap詳解:實現原理、源碼分析與最佳實踐

引言 在Java編程中&#xff0c;集合框架是最常用的工具之一&#xff0c;而HashMap和ConcurrentHashMap則是其中使用頻率最高的兩個Map實現。它們都用于存儲鍵值對數據&#xff0c;但在實現機制、性能特點和適用場景上有著顯著差異。 HashMap作為單線程環境下的首選Map實現&am…

CSS之動畫(奔跑的熊、兩面反轉盒子、3D導航欄、旋轉木馬)

一、 2D轉換 1.1 transform: translate( ) 轉換&#xff08;transform&#xff09; 是CSS3中具有顛覆性的特征之一&#xff0c;可以實現元素的位移、旋轉、縮放等效果 移動&#xff1a;translate 旋轉&#xff1a;rotate 縮放&#xff1a;scale 下圖為2D轉換的坐標系 回憶…

【筆記】在 MSYS2(MINGW64)中安裝 python-maturin 的記錄

#工作記錄 &#x1f4cc; 安裝背景 操作系統&#xff1a;MSYS2 MINGW64當前時間&#xff1a;2025年6月1日Python 版本&#xff1a;3.12&#xff08;通過 pacman 安裝&#xff09;目標工具&#xff1a;maturin —— 用于構建和發布 Rust 編寫的 Python 包 &#x1f6e0;? 安裝…

基于微信小程序的垃圾分類系統

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業六年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了六年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…

工作日記之權限校驗-token的實戰案例

背景說明 我們組負責維護的一個系統&#xff0c;前端界面掛載在其他兩個系統上&#xff0c;因為歷史遺留原因&#xff0c;同時也掛在公網上&#xff0c;沒有登陸功能和用戶體系&#xff0c;只要輸入網址就能訪問&#xff0c;雖然這個系統是給公司內部人員使用&#xff0c;但是…

mysql雙主模式下基于keepalived的虛擬ip實現高可用模式搭建

數據庫安裝和升級和雙主配置的操作可以參考我的另一篇文章&#xff1a; 數據庫安裝和升級和雙主配置 1、在兩臺服務器都下載和安裝keepalived 下載&#xff1a; yumdownloader --resolve keepalived 下載后得到&#xff1a; [rootlocalhost keepalivedRpm]# ll 總用量 1896 …

展會聚焦丨漫途科技亮相2025西北水務博覽會!

2025第三屆西北水務數字化發展論壇暨供排水節水灌溉新技術設備博覽會在蘭州甘肅國際會展中心圓滿落幕。本屆展會以“科技賦能水資源&#xff0c;數智引領新動能”為主題&#xff0c;活動匯集水務集團、科研院所、技術供應商等全產業鏈參與者&#xff0c;旨在通過前沿技術展示與…

單調棧(打卡)

本篇基于b站靈茶山艾府。 下面是靈神上課講解的題目與課后作業&#xff0c;課后作業還有三道實在寫不下去了&#xff0c;下次再寫。 739. 每日溫度 給定一個整數數組 temperatures &#xff0c;表示每天的溫度&#xff0c;返回一個數組 answer &#xff0c;其中 answer[i] 是…

【機器學習基礎】機器學習入門核心算法:層次聚類算法(AGNES算法和 DIANA算法)

機器學習入門核心算法&#xff1a;層次聚類算法&#xff08;AGNES算法和 DIANA算法&#xff09; 一、算法邏輯二、算法原理與數學推導1. 距離度量2. 簇間距離計算&#xff08;連接標準&#xff09;3. 算法偽代碼&#xff08;凝聚式&#xff09; 三、模型評估1. 內部評估指標2. …

已有的前端項目打包到tauri運行(windows)

1.打包前端項目產生靜態html、css、js 我們接下來用vue3 vite編寫一個番茄鐘案例來演示。 我們執行npm run build 命令產生的dist目錄下的靜態文件。 2.創建tarui項目 npm create tauri-applatest一路回車&#xff0c;直到出現。 3.啟動運行 我們將打包產生的dist目錄下的…

Unity3D仿星露谷物語開發55之保存地面屬性到文件

1、目標 將游戲保存到文件&#xff0c;并從文件中加載游戲。 Player在游戲中種植的Crop&#xff0c;我們希望保存到文件中&#xff0c;當游戲重新加載時Crop的GridProperty數據仍然存在。這次主要實現保存地面屬性&#xff08;GridProperties&#xff09;信息。 我們要做的是…

Java面試:企業協同SaaS中的技術挑戰與解決方案

Java面試&#xff1a;企業協同SaaS中的技術挑戰與解決方案 面試場景 在一家知名互聯網大廠&#xff0c;面試官老王正在對一位應聘企業協同SaaS開發職位的程序員謝飛機進行技術面試。 第一輪提問&#xff1a;基礎技術 老王&#xff1a;謝飛機&#xff0c;你好。首先&#xf…

SQL注入速查表(含不同數據庫攻擊方式與差異對比)

1. 字符串連接 字符串連接是SQL注入中常用的操作&#xff0c;用于將多個字符串拼接為一個&#xff0c;以構造復雜的注入語句。不同數據庫的字符串連接語法存在顯著差異&#xff0c;了解這些差異有助于精準構造payload。 Oracle&#xff1a;使用||操作符進行字符串連接&#xf…

uni-data-picker級聯選擇器、fastadmin后端api

記錄一個部門及部門人員選擇的功能&#xff0c;效果如下&#xff1a; 組件用到了uni-ui的級聯選擇uni-data-picker 開發文檔&#xff1a;uni-app官網 組件要求的數據格式如下&#xff1a; 后端使用的是fastadmin&#xff0c;需要用到fastadmin自帶的tree類生成部門樹 &#x…

Mac電腦上本地安裝 redis并配置開啟自啟完整流程

文章目錄 一、安裝 Redis方法 1&#xff1a;通過源碼編譯安裝&#xff08;推薦&#xff09;方法 2&#xff1a;通過 Homebrew 安裝&#xff08;可選&#xff09; 二、配置 Redis1. 創建配置文件和數據目錄2. 修改配置文件 三、配置開機自啟1、通過 launchd 系統服務&#xff08…

wsl安裝linux

安裝wsl 啟用適用于 Linux 的 Windows 子系統 以管理員身份打開 PowerShell &#xff08;> PowerShell > 右鍵單擊 > 以管理員身份運行&#xff09; 并輸入以下命令&#xff0c;然后重啟 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsyste…