iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 26
1

前言

不知不覺竟然來到第 26 天了,因為還有一點時間,今天就來實作之前想做、但不知道放在哪裡的功能: 在背景執行程式

some command &

在 Linux 裡面如果想把一個程式跑在背景,可以在結尾的地方加個 &,譬如說 ping google.com & 就會把 ping 跑在背景。背景 Process 不會佔用 Stdin 也 不會卡住 Shell,所以可以繼續下其他指令,下圖就是我一邊 ping google.com 一邊跑 ls

雖然 ping 是跑在背景,但從上圖能看出來他 還是會輸出到終端機,因此整個畫面有點亂

為了不讓畫面上有太多東西,通常把指令跑在背景時都會把 Stdout 重新導向到檔案,這樣他就會在背景默默的輸出到檔案,真的要看結果時再用 cat 就好

仔細看上面的圖,第一次 cat 時只有一行,但過了一陣子再 cat 就變成五行,所以他確實是 一直在背景執行 哦~

這種跑在背景的 Process 因為 不受 Shell 控制,所以沒辦法按 <Ctrl>-C 殺掉他,只能透過送 Signal 直接賜死那個 Process

實作

要像 zsh 那樣讓程式在背景執行有兩個關鍵:

  • 不要佔用 Stdin

    之前都會把預設的資料來源 inputStream 設為 os.Stdin,但如果是要跑在背景的程式,就直接把輸入來源設為 nil,這樣他就不會佔用 Stdin

  • 讓 Shell 不用等程式執行結束

    目前用的 cmd.Run() 會一直等到 Child Process 執行結束,Shell 才會恢復可以輸入的狀態,為了讓 Shell 不用等 Child Process,下面會介紹另一個 cmd.Start()

會用到的 function

  • (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
}

Demo

先把 ping google.com 跑在背景再跑 ls

小結

View commit on Github

今天又完成了一個小小功能,有問題的話歡迎在下方留言,沒問題的話明天要來做一個 zsh 沒有的功能,大家可以期待一下~


上一篇
Day25-Signal 訊號(三)
下一篇
Day27-timeout 限時指令
系列文
Gosh!原來用 Go 寫一個 Unix Shell 這麼簡單30

尚未有邦友留言

立即登入留言