這里可以找到代碼
支持的特性
- 單條指令的執行
- 引號引起的參數(如
$ 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
