Linux 學習-模擬實現【簡易版bash】

1、bash本質

在模擬實現前,先得了解?bash?的本質

bash?也是一個進程,并且是不斷運行中的進程
證明:常顯示的命令輸入提示符就是?bash?不斷打印輸出的結果

輸入指令后,bash?會創建子進程,并進行程序替換
證明:運行自己寫的程序后,可以看到當前進程的?父進程?為?bash

?此時可以斷定神秘的?bash?就是一個運行中的進程,因為進程間具有獨立性,因此可以同時存在多個?bash,這也是多用戶登錄?Linux?可以同時使用?bash?的重要原因

系統自帶的?bash?是一個龐然大物,我們只需根據其本質,實現一個簡易版?bash?就行了


2、需求分析

bash?需要幫我們完成命令解釋+程序替換的任務,因此它至少要具備以下功能:

  • 接收指令(字符串)
  • 對指令進行分割,構成有效信息
  • 創建子進程,執行進程替換
  • 子進程運行結束后,父進程回收僵尸進程
  • 輸入特殊指令時的處理

3、核心內容

核心內容主要為?讀取切割替換?這三部分,逐一實現,首先從指令讀取開始

?3.1、指令讀取

讀取指令前,首先要清楚待讀取命令可能有多長

  • 常見命令如?ls -a -l?長度不超過?10
  • 為了避免極端情況,這里預設命令最大長度為?1024
  • 使用數組進行指令存儲(緩沖區)
char commandline[1024];//命令行

?考慮什么是指令?如何讀取指令?

  • Linux?中的大部分指令由?指令 [選項]?構成,在?指令?和?[選擇]?間有空格
  • 常規的?scanf?無法正常讀取指令,因為空格會觸發輸入緩沖區刷新
  • 這里主要使用?fgets?逐行讀取,可以讀取到空格
void interact(char* cline,int size)//輸出命令行{getpwd();printf("[%s@%s%s]#  " ,getusrname(),gethostname(),pwd); char *s=fgets(cline,size,stdin);//輸入指令,有可能什么也沒有輸入直接回車   assert(s);(void)s;//”abcd\n\0" cline[strlen(cline)-1]='\0';//原來\n,在輸入的時候也會加入到字符串中;checkdir(cline);//檢查重定向}

?注意:?可能存在讀取失敗的情況,assert?斷言解決;因為?fgets?也會把最后的?'\n'?讀進去,為了避免出錯,手動置為?'\0';

3.2、指令分割

獲得指令后,就需要將指令進行分割

?為何要分割指令?

  • 程序替換時,需要使用?argv?表,這張表由?指令選項NULL?構成
  • 利用指令間的空格進行分割

如何分割指令?

  • C語言?提供了字符串分割函數?strtok,可以直接使用
  • 當然也可以手動實現分割

指令分割后呢?

  • 將分割好的指令段,依次存入?argv?表中,供后續程序替換使用
  • argv?表實際為一個指針數組,可以存儲字符串

如?command?一樣,表?argv?也需要考慮大小,這里設置為?64實際使用時也就分割為四五個指令段

strtok 是 C 語言中的一個字符串處理函數,用于將一個字符串分割成多個子字符串(tokens)。該函數定義在 string.h 頭文件中。strtok 通常用于解析由分隔符(如空格、逗號等)分隔的字符串。

函數原型:

char *strtok(char *str, const char *delim);

參數說明:

  • str:要分割的字符串。在第一次調用時,傳入需要分割的原始字符串,之后的調用則傳入 NULL,以繼續分割上次 strtok 返回的部分。

  • delim:一個包含所有分隔符字符的字符串。例如,如果分隔符是空格和逗號,delim 可以是 " ,"

返回值:

  • 成功:返回指向分割出的子字符串的指針(tokens)。子字符串會從原始字符串中分割出來,并且這個分割后的子字符串是原始字符串的一部分,它們將共享內存空間。

  • 失敗:如果沒有更多的子字符串可供提取,strtok 返回 NULL

使用說明:

  1. 第一次調用:傳入待分割的字符串。

  2. 后續調用:每次調用時,傳入 NULL 以繼續分割上次 strtok 返回的部分,直到沒有更多的子字符串為止(返回 NULL)。

#define DEF_CHAR " "	//預設分割項,需為字符串void split(char* argv[ARGV_SIZE], char* ps)
{assert(argv && ps);//調用 C語言 中的 strtok 函數分割字符串int pos = 0;argv[pos++] = strtok(ps, DEF_CHAR);  //有空格就分割while(argv[pos++] = strtok(NULL, DEF_CHAR));  //不斷分割argv[pos] = NULL; //確保安全
}

注意:?指令分割結束后,需要在添加?argv?表結尾?NULL

3.3、程序替換

獲得實際可用的?argv?表后,就可以開始子進程程序替換操作了

這里使用的是函數?execvp,理由:

  • v?表示?vector,正好和我們的?argv?表對應
  • p?為?path,可以根據?argv[0](指令),在?PATH?中尋找該程序并替換

當然也可以使用?execve?系統級替換函數

//子進程進行程序替換
pid_t id = fork();
if(id == 0)
{//直接執行程序替換,這里使用 execvpexecvp(argv[0], argv);exit(168); //替換失敗后返回
}

注意:?程序替換成功后,exit(168)?語句不會執行.?

4、特殊情況處理

對特殊情況進行處理,使?myBash?更加完善

4.1、ls 顯示高亮

系統中的?bash?在面對?ls?等文件顯示指令時,不僅會顯示內容,還會將特殊文件做顏色高亮處理,比如在我的環境下,可執行文件顯示為綠色

實現原理

  • 在指令結尾加上?--color=auto?語句,即可實現高亮處理這個問題很簡單,在指令分割結束后,判斷是否為?ls,如果是,就在?argv?表后尾插入語句?--color=auto?即可
//特殊處理
//顏色高亮處理,識別是否為 ls 指令
if(strcmp(argv[0], "ls") == 0)
{int pos = 0;while(argv[pos++]); //找到尾argv[pos - 1] = (char*)"--color=auto"; //添加此字段argv[pos] = NULL; //結新尾
}

?注意:

  • 因為?argv?表中的元素類型為?char*,所以在尾插語句時,需要進行類型轉換
  • 尾插語句后,需要再次添加結尾,確保安全
4.2、內建命令

內建命令是比較特殊的命令,不同于普通命令直接進行程序替換,內建命令需要進行特殊處理,比如?cd?命令調用系統級接口?chdir?讓?父進程(myBash)?進行目錄間的移動

?5.3、cd

首先實現不同目錄間的切換

切換的本質:令當前?bash?移動至另一個目錄下,不能直接使用?子進程?,因為需要移動的是?父進程(bash)

對于當前的?myBash?來說,cd?沒有絲毫效果,因為此時?指令會被拆分后交給子進程處理,這個方向本身就是錯誤的

?特殊情況特殊處理,同?ls?高亮一樣,對指令進行識別,如果識別到?cd?命令,就直接調用?chdir?函數令當前進程?myBash?移動至指定目錄即可(不必再創建子進程進行替換)

//目錄間移動處理
if(strcmp(argv[0], "cd") == 0)
{//直接調用接口,然后 continue 不再執行后續代碼if(strcmp(argv[1], "~") == 0)chdir("/home");  //回到家目錄else if(strcmp(argv[1], "-") == 0)chdir(getenv("OLDPWD"));else if(argv[1])chdir(argv[1]);  //argv[1] 中就是路徑continue;  //終止此次循環
}
4.3、export

當添加環境變量時,環境變量具有全局屬性,需要持久存在,所以要定義一個全局的數組存儲環境變量的值。myenv 是一個全局的數組。

strcpy(myenv[count],_argv[1]);
putenv(myenv[count++]);
4.4、重定向

?重定向的本質:關閉默認輸出/輸入流,打開新的文件流,從其中寫入/讀取數據

重定向的三種情況:

  • echo 字符串 > 文件?向文件中寫入數據,寫入前會先清空內容
  • echo 字符串 >> 文件?向文件中追加數據,追加前不會先清空內容
  • 可執行程序 < 文件?從文件中讀取數據給可執行程序

所以實現重定向的關鍵在于判斷指令中是否含有?>>><?這三個字符,如果有,就具體問題具體分析,完成重定向

具體實現步驟:

  • 判斷字符串中是否含有目標字符,如果有,就置當前位置為?'\0‘,其后半部分不參與指令分割?
  • 后半部分就是文件名,在打開文件時需要使用
  • 根據不同的字符,設置不同的標記位,用于判斷打開文件的方式(只寫、追加、只讀)
  • 判斷是否需要進行重定向,如果需要,在子進程創建后,打開目標文件,并調用?dup2?函數進行標準流的替換

open?函數的打開選項

O_RDONLY	//只讀
O_WRONLY | O_CREAT | O_TRUNC	//只寫
O_WRONLY | O_CREAT | O_APPEND	//追加

?標準流交換函數?dup2

//給參數1傳打開文件后的文件描述符,給參數2傳遞待關閉的標準流
//讀取:關閉0號流
//寫入、追加:關閉1號流
int dup2(int oldfd, int newfd);
void checkdir(char * cmd)48 {49   char *pos =cmd;50   while(*pos)51   {52     if(*pos=='>')53     {54         if(*(pos+1)=='>')//'>>'55         {56            *(pos++)='\0';57            *(pos++)='\0';58           while(*pos==' ') pos++;59 60           rdirfilename=pos;61           rdir = APPEND_RDIR;62           break;63         }64         else //'>'65         {66          *(pos++)='\0';                                                                                                                                              67        while(*pos==' ') pos++;68        rdirfilename=pos;69        rdir=OUT_RDIR;70         }72     }73     else if(*pos=='<')74     {75        *pos='\0';76         pos++;77         while(*pos==' ') pos++;78                                                                                                                                                                      79         rdirfilename=pos;80         rdir=IN_RDIR;81          break;82     }83     else{}84 85     pos++;86    }   87   88 }

5.源碼:好好理解

#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>#include <sys/stat.h>#include <fcntl.h>extern char** environ;#define  NONE -1
#define  IN_RDIR 0 //輸入
#define  OUT_RDIR 1//stdout
#define  APPEND_RDIR 2//stderrchar commandline[1024];//命令行
char *argv[32];//參數表
char pwd[1024];//路徑長
char myenv[10][10];//環境變量表
int count=0;
int lastcode=0;//退出碼char * rdirfilename=NULL; //重定向的文件
int rdir =NONE;const char* getusrname()
{ const  char* str=getenv("USER");return str;
}const char* gethostname()
{return getenv("HOSTNAME");
}void getpwd()
{getcwd(pwd,sizeof(pwd));//是一個接口函數,將路徑寫到pwd里面
}void checkdir(char * cmd)
{char *pos =cmd;while(*pos){if(*pos=='>'){if(*(pos+1)=='>')//'>>'{*(pos++)='\0';*(pos++)='\0';while(*pos==' ') pos++;rdirfilename=pos;rdir = APPEND_RDIR;break;}else //'>'{*(pos++)='\0';                                                                                                                  while(*pos==' ') pos++;rdirfilename=pos;rdir=OUT_RDIR;}}else if(*pos=='<'){*pos='\0';pos++;while(*pos==' ') pos++;rdirfilename=pos;rdir=IN_RDIR;break;}else{}pos++;}   }void interact(char* cline,int size)//輸出命令行
{getpwd();printf("[%s@%s%s]#  " ,getusrname(),gethostname(),pwd);char *s=fgets(cline,size,stdin);//輸入指令,有可能什么也沒有輸入直接回車assert(s);(void)s;//”abcd\n\0" cline[strlen(cline)-1]='\0';//原來\n,在輸入的時候也會加入到字符串中;checkdir(cline);//檢查重定向
}int splitstring(char * cline,char *_argv[])
{int i=0;argv[i++]=strtok(cline," ");//字符串分割while(_argv[i++]=strtok(NULL," "));//如果截取失敗就會返回NULL,正好是參數表尾;return i-1; //含回指令的參數個數。NULL不算
}int buildCommand(char*_argv[],int _argc)
{if(_argc==2&&strcmp(_argv[0],"cd")==0){chdir(argv[1]);//改變當前進程的路徑,但是并不影響環境變量當中的路徑getpwd();sprintf(getenv("PWD"),"%s",pwd);return 1;}else if(_argc==2&&strcmp(_argv[0],"export")==0){strcpy(myenv[count],_argv[1]);putenv(myenv[count++]);return 1;}else if(_argc==2&&strcmp(_argv[0],"echo")==0){if(strcmp(_argv[1],"$?")==0){printf("%d\n",lastcode);lastcode=0;//查看完后置為0;}else if(*_argv[1]=='$'){char* val=getenv(_argv[1]+1);if(val) printf("%s\n",val);}else printf("%s\n",_argv[1]);return 1;}if(strcmp(_argv[0],"ls")==0){ _argv[_argc++]="--color=auto";_argv[_argc]=NULL;// return 1;}return 0;
}void NormalExcute(char* _argv[]){pid_t id=fork();if(id<0){perror("fork");return ;}else if(id==0){int fd=0;if(rdir==IN_RDIR){fd=open(rdirfilename,O_RDONLY);dup2(fd ,0);}else if(rdir==OUT_RDIR){fd=open(rdirfilename,O_CREAT|O_WRONLY|O_TRUNC,0666);dup2(fd,1);}else if(rdir==APPEND_RDIR){fd=open(rdirfilename, O_CREAT|O_WRONLY|O_APPEND,0666);dup2(fd,1);}execvp(_argv[0],_argv);exit(1);}else {int status=0;pid_t rid=waitpid(id,&status,0);//正常含回子進程的PIDif(rid==id){lastcode=WEXITSTATUS(status);}}
}int main()
{while(1){rdirfilename=NULL;rdir=NONE;    interact(commandline,sizeof(commandline));int argc=splitstring(commandline,argv);if(argc==0) continue;//for(int i=0;i<argc;i++) printf("argv[%d]:%s\n",i,argv[i]);int n=buildCommand(argv,argc);if(!n) NormalExcute(argv);}return 0;
}

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

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

相關文章

GitHub 趨勢日報 (2025年05月31日)

&#x1f4ca; 由 TrendForge 系統生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日報中的項目描述已自動翻譯為中文 &#x1f4c8; 今日獲星趨勢圖 今日獲星趨勢圖 1153 prompt-eng-interactive-tutorial 509 BillionMail 435 ai-agents-for-begin…

“人單酬“理念:財稅行業的自我驅動革命

引言&#xff1a;當薪酬不再是"固定數字"&#xff0c;而是"成長標尺" "為什么有人拼命工作卻收入停滯&#xff1f;為什么企業總在人才流失中掙扎&#xff1f;"這些問題背后&#xff0c;往往隱藏著傳統薪酬體系的僵化。而"人單酬"&…

AI大模型賦能,aPaaS+iPaaS構建新一代數智化應用|愛分析報告

01 aPaaS和iPaaS成為企業用戶關注重點 PaaS市場定義 根據Gartner的定義&#xff0c;PaaS&#xff08;Platform as a Service&#xff09;平臺是應用基礎架構&#xff08;中間件&#xff09;服務的廣泛集合&#xff0c; 包含應用平臺、集成、業務流程管理、數據服務和AI應用等…

WPS快速排版

論文包括&#xff08;按順序&#xff09;&#xff1a;封面&#xff08;含題目&#xff09;、摘 要、關鍵詞、Abstract&#xff08;英文摘要&#xff09;、Keywords、目錄、正文、參考文獻、在讀期間發表的學術論文及研究成果&#xff0c;致 謝 題目&#xff08;黑小一加粗&…

python第39天打卡

1.灰度圖像 作為圖像數據&#xff0c;相較于結構化數據&#xff08;表格數據&#xff09;他的特點在于他每個樣本的的形狀并不是(特征數&#xff0c;)&#xff0c;而是(寬&#xff0c;高&#xff0c;通道數) # 先繼續之前的代碼 import torch import torch.nn as nn import t…

win11小組件功能缺失的恢復方法

問題說明&#xff1a;重置了win11系統&#xff0c;結果小組件功能找不到了&#xff0c;最后用以下辦法解決。 1. 以管理員身份打開 PowerShell 或 CMD。 2. 運行以下命令&#xff1a; winget install 9MSSGKG348SP 注&#xff1a;如果報錯&#xff0c;可嘗試先卸載再安裝…

Kali Linux從入門到實戰:系統詳解與工具指南

一、Kali Linux簡介 Kali Linux是一款基于Debian的Linux發行版&#xff0c;專為滲透測試和網絡安全審計設計&#xff0c;由Offensive Security團隊維護。其前身是BackTrack&#xff0c;目前集成了超過600款安全工具&#xff0c;覆蓋滲透測試全流程&#xff0c;是網絡安全領域…

C語言 — 文件

目錄 1.流1.1 流的概念1.2 常見的的流 2.文件的打開和關閉2.1 fopen函數2.2 fclose函數2.3 文件的打開和關閉 3.文件的輸入輸出函數3.1 fputc函數3.2 fgetc函數3.3 feof函數和ferror函數3.4 fputs函數3.5 fgets函數3.6 fwrite函數3.7 fread函數3.8 fprintf函數3.9 fscanf函數 4…

Pull Request Integration 拉取請求集成

今天我想要把我創建的項目&#xff0c;通過修改yaml里面的內容&#xff0c;讓我在main分支下的其他分支拉取請求的時候自動化測試拉取的內容&#xff0c;以及將測試結果上傳到控制臺云端。 首先我通過修改yaml文件里面的內容 name: Build and Teston:push:branches:- mainjobs:…

NodeJS全棧開發面試題講解——P3數據庫(MySQL / MongoDB / Redis)

3.1 如何用 Node.js 連接 MySQL&#xff1f;你用過哪些 ORM&#xff1f; 面試官您好&#xff0c;我先介紹如何用 Node.js 連接 MySQL&#xff0c;然后補充我常用的 ORM 工具。 &#x1f50c; 原生連接 MySQL 使用 mysql2 模塊&#xff1a; npm install mysql2 const mysql …

Redis最佳實踐——性能優化技巧之數據結構選擇

Redis在電商應用中的數據結構選擇與性能優化技巧 一、電商核心場景與數據結構選型矩陣 應用場景推薦數據結構內存占用讀寫復雜度典型操作商品詳情緩存Hash低O(1)HGETALL, HMSET購物車管理Hash中O(1)HINCRBY, HDEL用戶會話管理Hash低O(1)HSETEX, HGET商品分類目錄Sorted Set高O…

題單:最大公約數(輾轉相除法)

題目描述 所謂 “最大公約數&#xff08;GCD&#xff09;” &#xff0c;是指所有公約數中最大的那個&#xff0c;例如 12 和 1818 的公約數有 1,2,3,6 &#xff0c;所以 12 和 18 的最大公約數為 6 。 輾轉相除法&#xff0c;又名歐幾里德算法&#xff08;Euclidean Algorit…

hadoop完整安裝教程(附帶jdk1.8+vim+ssh安裝)

本篇帶領大家在uabntu20虛擬機上安裝hadoop&#xff0c;其中還包括jdk1.8、ssh、vim的安裝教程&#xff0c;&#xff08;可能是&#xff09;史上最全的安裝教程&#xff01;&#xff01;&#xff01;若有疑問可以在評論區或者私信作者。建議在虛擬機上觀看此博客&#xff0c;便…

Flutter、React Native、Unity 下的 iOS 性能與調試實踐:兼容性挑戰與應對策略(含 KeyMob 工具經驗)

移動端跨平臺開發逐漸成為常態&#xff0c;Flutter、React Native、Unity、Hybrid App 等框架在各類 iOS 項目中頻繁出現。但隨之而來的&#xff0c;是一系列在 iOS 設備上調試難、性能數據采集難、日志整合難的問題。 今天這篇文章&#xff0c;我從實際項目出發&#xff0c;聊…

PyCharm接入DeepSeek,實現高效AI編程

介紹本土AI工具DeepSeek如何結合PyCharm同樣實現該功能。 一 DeepSeek API申請 首先進入DeepSeek官網&#xff1a;DeepSeek 官網 接著點擊右上角的 “API 開放平臺“ 然后點擊API keys 創建好的API key&#xff0c;記得復制保存好 二 pycharm 接入deepseek 首先打開PyCh…

Cinnamon開始菜單(1):獲取應用數據

看了半天&#xff1a;/usr/share/cinnamon/applets/menucinnamon.org&#xff0c;終于挖到了精髓。 Cinnamon.AppSystem.get_default() 獲取系統應用數據 get_tree() 獲取樹機構 get_root_directory() 獲取根目錄 iter() 遍歷 get_name() 獲取名稱 get_desktop_file_id()…

git reset --hard HEAD~1與git reset --hard origin/xxx

git reset --hard HEAD~1與git reset --hard origin/xxx git reset --hard origin/xxx有時候會太長&#xff0c;手工輸入略微繁瑣&#xff0c;可以考慮&#xff1a; git reset --hard HEAD~1 替代。 或者使用這種方式 git reset撤銷當前分支所有修改&#xff0c;恢復到最近一…

鴻蒙OSUniApp PWA開發實踐:打造跨平臺漸進式應用#三方框架 #Uniapp

UniApp PWA開發實踐&#xff1a;打造跨平臺漸進式應用 前言 在過去的一年里&#xff0c;我們團隊一直在探索如何利用UniApp框架開發高性能的PWA應用。特別是隨著鴻蒙系統的普及&#xff0c;我們積累了不少有價值的實踐經驗。本文將分享我們在開發過程中的技術選型、架構設計和…

ansible自動化playbook簡單實踐

方法一&#xff1a;部分使用ansible 基于現有的nginx配置文件&#xff0c;定制部署nginx軟件&#xff0c;將我們的知識進行整合 定制要求&#xff1a; 啟動用戶&#xff1a;nginx-test&#xff0c;uid是82&#xff0c;系統用戶&#xff0c;不能登錄 啟動端口82 web項目根目錄/…

【Office】Excel兩列數據比較方法總結

在Excel中&#xff0c;比較兩列數據是否相等有多種方法&#xff0c;以下是常用的幾種方式&#xff1a; 方法1&#xff1a;使用公式&#xff08;返回TRUE/FALSE&#xff09; 在空白列&#xff08;如C列&#xff09;輸入公式&#xff0c;向下填充即可逐行比較兩列&#xff08;如…