不知不覺竟然來到第 26 天了,因為還有一點時間,今天就來實作之前想做、但不知道放在哪裡的功能: 在背景執行程式
在 Linux 裡面如果想把一個程式跑在背景,可以在結尾的地方加個 &
,譬如說 ping google.com &
就會把 ping
跑在背景。背景 Process 不會佔用 Stdin 也 不會卡住 Shell,所以可以繼續下其他指令,下圖就是我一邊 ping google.com
一邊跑 ls
雖然 ping
是跑在背景,但從上圖能看出來他 還是會輸出到終端機,因此整個畫面有點亂
為了不讓畫面上有太多東西,通常把指令跑在背景時都會把 Stdout 重新導向到檔案,這樣他就會在背景默默的輸出到檔案,真的要看結果時再用 cat
就好
仔細看上面的圖,第一次 cat
時只有一行,但過了一陣子再 cat
就變成五行,所以他確實是 一直在背景執行 哦~
這種跑在背景的 Process 因為 不受 Shell 控制,所以沒辦法按
<Ctrl>-C
殺掉他,只能透過送 Signal 直接賜死那個 Process
要像 zsh 那樣讓程式在背景執行有兩個關鍵:
之前都會把預設的資料來源 inputStream
設為 os.Stdin
,但如果是要跑在背景的程式,就直接把輸入來源設為 nil
,這樣他就不會佔用 Stdin
目前用的 cmd.Run()
會一直等到 Child Process 執行結束,Shell 才會恢復可以輸入的狀態,為了讓 Shell 不用等 Child Process,下面會介紹另一個 cmd.Start()
(Cmd) Start()
之前在 Day05 講的 (Cmd) Run()
會另外執行一個 Child Process,並且會一直等到他執行結束;而這個 (Cmd) Start()
是執行一個 Child Process 然後就不理他了,完全是一個放牛吃草的概念XD,所以等等會用到它來實作「讓 Shell 不用等程式執行結束」的部分
func executeInput(input string) error {
// ...
// 用一個變數來紀錄是否要跑在背景
// 預設是 false
shouldRunInBackground := false
// 預設的 inputStream 是 os.Stdin
inputStream := os.Stdin
// 判斷最後一個參數是不是 &
if args[len(args)-1] == "&" {
// 如果指令是 & 結尾
// 就把 shouldRunInBackground 標為 true
// 意思是這個指令要跑在背景
shouldRunInBackground = true
// 因為跑在背景的程式不會跟 Stdin 互動
// 所以把 inputStream 設為 nil
inputStream = nil
// 結尾的 & 並不是指令的一部分
// 只是用來表示要把這個指令跑在背景
// 所以要把結尾的 & 刪掉
// [ping, google.com, &] -> [ping, google.com]
args = args[:len(args)-1]
}
// ...
// 以 shouldRunInBackground 判斷指令需不需要跑在背景
if shouldRunInBackground {
// 如果指令需要跑在背景
// 那就用 cmd.Start() 啟動 Child Process
// 啟動完之後 execInput 就結束了
// 會恢復可以輸入指令的狀態
err := cmd.Start()
return err
}
// shouldRunInBackground == false
// 如果指令不需要跑在背景就會執行到這裡
// 這邊是之前寫的執行指令
// 也就是把指令跑在前景
currentCmd = cmd
err := cmd.Run()
currentCmd = nil
return err
}
先把 ping google.com
跑在背景再跑 ls
今天又完成了一個小小功能,有問題的話歡迎在下方留言,沒問題的話明天要來做一個 zsh 沒有的功能,大家可以期待一下~