這裡可以找到程式碼
支援的特性
- 單條指令的執行
- 引號引起的引數(如
$ some_program "hello, world"
) - 重定向(>、< )
- 管道(|)
- 內建指令(如 cd、history、quit)
- 指令別名(如 ll → ls -l)
- 家目錄(~)
執行截圖
如何寫一個簡單的 Shell
這裡簡單介紹寫 Shell 時比較關鍵的一些部分,具體請檢視原始碼。
展示提示符
見 show_command_prompt
函式。
command_prompt 是在每行最開始顯示的一段與使用者名稱、路徑等相關的提示資訊。ExpShell 顯示的 prompt 形如 [root@localhost tmp]>
。用 > 而非 #、$ 作為提示符,以區分原生 Shell。
-
獲取使用者名稱
passwd *pwd = getpwuid(getuid()); string username(pwd->pw_name);
-
獲取當前目錄
getcwd(char_buf, CHAR_BUF_SIZE); string cwd(char_buf);
- prompt 中目錄只顯示最近一級,此處用
/
來 split 後取最後一個即可 - 家目錄需要摺疊為
~
,這裡順便把家目錄地址存到全域性變數home_dir
,後續要用到
- prompt 中目錄只顯示最近一級,此處用
-
獲取主機名
gethostname(char_buf, CHAR_BUF_SIZE); string hostname(char_buf);
- 有時 hostname 會是形如
localhost.locald.xxx
的形式,也 split 處理一下
- 有時 hostname 會是形如
-
輸出之即可
cout << "[" << username << "@" << hostname << " " << cwd << "]> ";
解析命令
-
為儲存解析結果,定義如下四個類:
- cmd:各種 cmd 的基類
- exec_cmd:形如
argv[0] argv[1] ...
的普通命令 - pipe_cmd:管道命令,形如
left: cmd* | right: cmd*
- redirect_cmd:重定向命令,形如
cmd_: cmd* > (or <) file
-
(最基礎的)解析 exec_cmd
見
parse_exec_cmd
函式。注意這裡使用string_split_protect
函式來 split 出 argv,這樣可以保持被引號引起的帶空格的 argument 不被拆分。 -
解析一條命令
見
parse
函式。採用分治法遞迴地解析命令。- 從左到右掃描字串
- 如果是普通字元,則讀入快取
- 如果是重定向符號,將當前快取解析為 exec_cmd,作為左手邊 cmd;繼續不斷讀入直到再次遇到符號或字串結束,作為右手邊 file,構建 redirect_cmd
- 如果是管道符號,遞迴地呼叫
parse
解析右側剩餘,解析結果作為本層遞迴的右手邊,構建 pipe_cmd
-
解析內建命令
主要支援 cd 、history 和 quit 命令。
- 呼叫
exit(0)
即可實現 quit - history 命令根據記錄列印即可
- 對於 cd,考慮如下情況
- 無參 cd 等價於
cd ~
- 對於形如
cd ~/some_path
的命令,使用home_dir
替換~
- 其他情況呼叫
chdir
即可
- 無參 cd 等價於
- 呼叫
執行命令
主要見 run_cmd
函式。該函式接收一個 cmd*
,遞迴地完成其鏈上所有 cmd 的執行。
-
對於 exec_cmd
- 檢查別名,替換別名,例如 ll → ls -l
- 使用
execvp
函式執行命令- 看 這篇博文 瞭解 exec 族函式,可見
execvp
在當前場景最為合適 - 第二個引數是一個末元素為 NULL 的 char**(char*[]),內容為 argv
- 看 這篇博文 瞭解 exec 族函式,可見
-
對於 pipe_cmd
-
為 pipe_cmd 的 left 和 right 分別 fork 子程式執行,並使用管道讓這兩個兄弟程式通訊
-
這張圖很好地說明了父子程式使用管道通訊的方法
-
依據上圖,不難類比出兄弟程式進行 IPC 的方法如下
- 父程式 pipe
- 父程式 fork 兩次
- child1 關讀端,重定向寫端,執行命令,關寫端
- child2 關寫端,重定向讀端,執行命令,關讀端
- 父程式關閉讀、寫端,並 wait
-
-
對於 redirect_cmd
- 開啟 file,獲得 fd
- 重定向 stdin 或 stdout 到 fd
- 執行命令
- 關閉 fd
主函式
在一個死迴圈中讀入當前命令,如果不是 builtin_command,則 fork 子程式進行解析和執行(避免阻塞 ExpShell 自身),執行完成後子程式 exit。
其他細節
- pipe、open、dup2 等方法返回值小於 0 均表示出現錯誤,需要觸發 panic
- 對於 wait 方法的狀態字,當
WIFEXITED(status)
為 0 時表示子程式異常退出,使用WEXITSTATUS(status)
可以進一步獲得子程式的 exit code