昨天講了 Process 跟 Standard IO 的基本概念,今天要用他們做到下 ls
指令時真的可以跑出 ls
的結果
昨天說過任何執行中的程式都是一個 Process,當然你正在用的 Shell 也是,他負責讀取指令並且啟動新的 Process
當我下 ls
指令時,Shell 會到 /bin
裡面找到 ls
的執行檔,接著馬上啟動一個 Process 來跑 ls
,一直等到 ls
跑完並輸出結果之後,Shell 才又恢復可以輸入的狀態
所以 Shell 其實就是個不斷循環的指令執行器,他不斷在 等待使用者輸入 與 等待 Child Process 完成 之間循環,執行完一個指令後又繼續等待下個指令(跑到上圖的最右邊之後又從左邊重新開始)
PS. 連結可以點哦
exec.Command
os/exec
是 Go 中專門在執行程式的 package,其中的 exec.Command
可以把一個指令包裝成 Cmd
型別的變數,譬如說可以用 exec.Command("ls")
把 ls
指令包裝成一個 Cmd
(Cmd) Run
Cmd
型別的變數(指令)可以用 Run
跑起來,他會在 PATH
裡面找到執行檔並且另外開一個 Child Process 來執行,所以要在 Go 裡面開一個 Child Process 來跑很簡單,就是把 exec.Command
跟 (Cmd) Run
兩個 function 組合起來而已
log.Println
log
是專門用來印紀錄的 package,其中的 log.Println
可以把 log 印出來然後換行,跟普通的 Println
很像
executeInput
為了防止 main()
太複雜,我把執行指令的 function 獨立出來叫做 executeInput()
,他會負責執行使用者給的指令,並且回傳過程中發生的錯誤
func executeInput(input string) error {
// 根據使用者的輸入建立一個指令
// 譬如說使用者輸入 ls,就建立一個 ls 指令
cmd := exec.Command(input)
// 使用 exec.Command 建立的 cmd 預設是不輸出(超怪XD)
// 所以要把他的 Stdandard IO 重新設定成系統預設(終端機)
// 他才能正常輸出到終端機、從終端機讀取資料
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// 啟動一個 Child Process 執行剛建立好的指令
// 如果使用者輸入 ls 那就是執行 ls 的執行檔
err := cmd.Run()
// 如果有發生錯誤的話就回傳
return err
}
首先根據使用者輸入的 input
建立指令,使用者輸入 ls
就建立 ls
的指令,接著把指令的輸入輸出位置設定成系統預設(終端機),然後就啟動一個 Child Process 來執行它
main
在 main()
裡面使用剛寫好的 executeInput
來執行指令,過程中若發生什麼錯誤就 log 出來
func main() {
// ...
for {
fmt.Print("> ")
input, _ := stdin.ReadString('\n')
input = strings.TrimSpace(input)
// 執行使用者輸入的指令
// 如果有錯誤的話就 log 出來
err := executeInput(input)
if err != nil {
log.Println(err)
}
}
}
ls
跟 ps
vim
中新增、編輯檔案做完執行指令之後,真的越來越像可以用的 Shell 了~
今天的 commit 放在 這裡,有任何問題歡迎在下方留言,沒問題的話就明天見囉
蠻不錯的.
但是直接執行 command 之前,是不是先判斷有沒有在path,會比較好一些.
謝謝你的留言~我也有在關注你的文章XD
我原本也有想要用 LookPath
先檢查,但後來發現 (Cmd) Run
在跑之前也會檢查,如果沒有這個執行檔的話就會回傳 ErrNotFound
,所以就想說給他檢查就好了
PS. 連結可以點哦
期待後續的發展,加油!
你也是~一起加油!