1. 打印命令行提示符
在我們使用系統提供的shell時,每次都會打印出一行字符串,這其實就是命令行提示符,那我們自定義的shell當然也需要這一行字符串。
這一行字符串包含用戶名,主機名,當前工作路徑,所以,我們在打印這行字符串時,需要獲取這些信息。根據我們之前學過的知識,我們可以用getenv系統調用來獲取!
這里有一個接口gethostname,我試過了用getenv來獲取系統的主機名,但是,在我的系統上似乎無法獲取,這可能和系統有關,但是我們用gethostname這個接口也可以很安全的獲得主機名,具體用法用man手冊看一看也就會了。?
?結果沒有問題:
2. 獲取用戶輸入
當我們解決了命令行提示符的問題后,接下來我們就會注意到每次執行指令都會有一個光標在閃爍等待用戶輸入指令!所以我們現在就要解決這個問題!
如果用scanf來獲取緩沖區的字符串坑定是不行的,因為scanf默認以空格作為分隔符,而我們在輸入指令帶選項時,就會有空格!
那我們就用fgets:
?可是為什么回顯時有兩次換行呢?
原因含很簡單:我們在輸入指令時,最后輸入的換行也在緩沖區中被fges獲取保留在數組commandline的最后一個字符,解決方法也很簡單,只需要把最后一個字符置為0即可!
現在寫的代碼還是不夠優雅,我們稍微封裝一下:
1 #include <iostream>2 #include <cstdlib>3 #include <cstdio>4 #include <unistd.h>5 #include <cstring>6 7 using namespace std;8 9 #define COMMAND_SIZE 102410 #define FORMAT "%s@%s:%s$ "11 12 const char* get_user_name()13 {14 const char* user=getenv("USER");15 return user==NULL?"NONE":user;16 //return user;17 }18 19 const char* get_pwd()20 {21 const char* pwd=getenv("PWD");22 return pwd==NULL?"NONE":pwd;23 }24 25 //制作命令行提示符Command Prompt26 void make_command_prompt(char cmd_prompt[],int size)27{28 char hostname[256];29 gethostname(hostname,sizeof(hostname));30 snprintf(cmd_prompt,size,FORMAT,get_user_name(),hostname,get_pwd());31 } 32 33 //打印命令行提示符34 void print_cmd_prompt()35 {36 char prompt[COMMAND_SIZE];37 make_command_prompt(prompt,sizeof(prompt));38 printf("%s",prompt);39 fflush(stdout);40 }41 42 //獲取用戶輸入的命令43 bool get_command(char* out,int size)44 {45 char* c=fgets(out,size,stdin);46 if(c==NULL) return false;47 out[strlen(out)-1]=0;48 //如果用戶什么都沒有輸入則返回false49 if(strlen(out)==0) return false;50 return true;51 }52 53 int main()54 {55 //1.打印命令行提示法56 print_cmd_prompt();57 //2.獲取用戶輸入的命令58 char commandline[COMMAND_SIZE];59 if(get_command(commandline,sizeof(commandline)))60 {61 printf("%s\n",commandline);62 }63 return 0;64 }
我們使用的shell是不斷在獲取用戶的指令的,也就是說shell一旦跑起來就是一個死循環,直到我們退出shell!所以我們還應該將我們的主體邏輯改一下!
3. 解析命令行
我們獲取了用戶輸入的字符串后【ls -a -l】,我們不可能用這一長串字符串去執行我們的指令,我們需要做的下一步就是將我們獲取的字符串按空格切割!具體如何做到如下:
我們先在全局定義一個命令行參數表char* ?g_argv[MAXARGC]來記錄我們切割的命令行參數
接下來,我們封裝一個函數來完成我們的切割任務:
測試函數:
?測試結果:
4. 執行命令?
執行命令也非常簡單,這需要用到我們之前學過的知識,創建子進程,將子進程進行程序替換!
5.簡化工作路徑的顯示?
通過上圖我們可以觀察到我們自定義的shell顯示的工作路徑太長了,為了和原shell盡可能保持一致,所以我們封裝一個函數來解決這個問題!
?6. 檢測并處理內建命令
我們在輸入ls,pwd等命令時,我們自定義的shell雀氏可以很好的幫我們完成工作。但是,當我們輸入cd,export等命令時,此時的shell就不再適用了。cd命令是改變當前的工作路徑,但是我們自定義的shell是子進程通過進程替換的方式幫我們執行命令,而cd這類命令是去環境變量表中那到當前的工作路徑,我們需要更改父進程bash的環境變量。所以對于cd這類的命令,我們需要用父進程去執行。而cd這類的命令我們又稱為內建命令,因此,在執行命令之前,我們需要一個檢測并處理內建命令的操作!
?
?下面是測試結果:
?我們發現工作路徑果然發生改變了,但是命令行顯示的路徑為什么沒有發生改變呢?
但cd命令執行時,先是進程的工作路徑發生改變,然后環境變量中記錄的工作路徑再改變,而這個工作也是由shell來完成的,但是目前我們的自定義shell還沒有實現這個功能!并且,我們獲取當前工作路徑是通過獲取環境變量的方式拿到的,所以我們在命令行中顯示的工作路徑永遠是久的!
因此,獲取當前工作路徑有一個更好的方式->系統調用【getcwd】!
下面的測試就符合預期了!?
但是,環境變量中的pwd是實實在在發生了變化的,所以我們自定義的shell也應該實現這一個功能!
所以,我們僅需要在獲取當前工作路徑之后,用puenv導入到環境變量中即可!
?當然,還有許多內建命令,比如echo,我們可以完善這些內建命令,這里就不寫了【比較懶】。
7. 完善環境變量表
目前這里自定義的shell只有命令行參數表,還缺少一張環境變量表。父進程bash在啟動時,從配置文件中獲取環境變量,子進程則繼承父進程的環境變量。如果我們要模擬bash獲取環境變量的方式,就必須從配置文件中那數據。但是,這里目前是做不到的【沒辦法到配置文件中拿數據】。
不過,我們自定義的shell本質上還是bash的子進程,所以我們可以到父進程中獲取環境變量!
?