在Golang中執(zhí)行Shell命令的教程詳解
Exec 包
我們可以使用官方的 os/exec 包來運行外部命令。
當我們執(zhí)行 shell 命令時,我們是在 Go 應(yīng)用程序之外運行代碼。為此,我們需要在子進程中運行這些命令。
每個命令都作為正在運行的 Go 應(yīng)用程序中的子進程運行,并公開我們可以用來從進程讀取和寫入數(shù)據(jù)的 Stdin 和 Stdout 屬性。
運行基本的 Shell 命令
要運行一個簡單的命令并讀取其輸出,我們可以創(chuàng)建一個新的 *exec.Cmd 實例并運行它。在此示例中,讓我們使用 ls 列出當前目錄中的文件,并打印代碼的輸出:
// 創(chuàng)建了一個新的 *Cmd 實例 // 使用 "ls" 命令和 "./" 參數(shù)作為參數(shù) cmd := exec.Command("ls", "./") // 使用 `Output` 方法執(zhí)行該命令并收集其輸出 out, err := cmd.Output() if err != nil { // 如果執(zhí)行命令時出現(xiàn)錯誤,則輸出錯誤信息 fmt.Println("could not run command: ", err) } // 否則,輸出運行該命令的輸出結(jié)果 fmt.Println("Output: ", string(out))
由于我在示例倉庫中運行此代碼,因此它會打印項目根目錄中的文件:
> go run shellcommands/main.go Output: LICENSE README.md go.mod shellcommands
請注意,當我們運行 exec
時,我們的應(yīng)用程序不會生成 shell,而是直接運行給定的命令。這意味著將不會執(zhí)行任何基于 shell 的處理,例如 glob 模式或擴展。
執(zhí)行持久運行的命令
前面的示例執(zhí)行了 ls
命令,該命令立即返回了它的輸出。那些輸出是連續(xù)的,或者需要很長時間才能檢索的命令呢?
例如,當我們運行 ping
命令時,我們會定期獲得連續(xù)輸出:
? ~ ping google.com
PING google.com (142.250.77.110): 56 data bytes
64 bytes from 142.250.77.110: icmp_seq=0 ttl=116 time=11.397 ms
64 bytes from 142.250.77.110: icmp_seq=1 ttl=116 time=17.646 ms ## this is received after 1 second
64 bytes from 142.250.77.110: icmp_seq=2 ttl=116 time=10.036 ms ## this is received after 2 seconds
64 bytes from 142.250.77.110: icmp_seq=3 ttl=116 time=9.656 ms ## and so on
# ...
如果我們嘗試使用 cmd.Output
執(zhí)行此類型的命令,我們將不會得到任何輸出,因為 Output
方法等待命令執(zhí)行,而 ping
命令執(zhí)行時間無限。
相反,我們可以使用自定義的 Stdout
屬性來連續(xù)讀取輸出:
cmd := exec.Command("ping", "google.com") // pipe the commands output to the applications // standard output cmd.Stdout = os.Stdout // Run still runs the command and waits for completion // but the output is instantly piped to Stdout if err := cmd.Run(); err != nil { fmt.Println("could not run command: ", err) }
這段代碼使用 Go 語言的 exec 包來執(zhí)行 ping 命令并將輸出重定向到標準輸出流(os.Stdout)。具體來說,它創(chuàng)建了一個命令對象(cmd),該對象包含要執(zhí)行的命令(“ping"和"google.com”)。然后將命令的標準輸出流(cmd.Stdout)設(shè)置為應(yīng)用程序的標準輸出流(os.Stdout)。最后,使用 cmd.Run() 方法運行該命令,并等待其完成。如果運行命令時出現(xiàn)錯誤,將在控制臺輸出錯誤信息。
輸出結(jié)果:
> go run shellcommands/main.go
PING google.com (142.250.195.142): 56 data bytes
64 bytes from 142.250.195.142: icmp_seq=0 ttl=114 time=9.397 ms
64 bytes from 142.250.195.142: icmp_seq=1 ttl=114 time=37.398 ms
64 bytes from 142.250.195.142: icmp_seq=2 ttl=114 time=34.050 ms
64 bytes from 142.250.195.142: icmp_seq=3 ttl=114 time=33.272 ms
# ...
# and so on
通過直接分配 Stdout
屬性,我們可以捕獲整個命令生命周期的輸出,并在收到后立即處理。
自定義輸出寫入程序
與使用 os.Stdout
不同,我們可以創(chuàng)建實現(xiàn) io.Writer
接口的自己的編寫器。
讓我們創(chuàng)建一個編寫器,在每個輸出塊之前添加一個 "received output:"
前綴:
type customOutput struct{} func (c customOutput) Write(p []byte) (int, error) { fmt.Println("received output: ", string(p)) return len(p), nil }
現(xiàn)在我們可以指定一個新的 customWriter
實例作為輸出寫入器:
cmd.Stdout = customOutput{}
如果我們現(xiàn)在運行應(yīng)用程序,我們將得到以下輸出:
received output: PING google.com (142.250.195.142): 56 data bytes
64 bytes from 142.250.195.142: icmp_seq=0 ttl=114 time=187.825 ms
received output: 64 bytes from 142.250.195.142: icmp_seq=1 ttl=114 time=19.489 ms
received output: 64 bytes from 142.250.195.142: icmp_seq=2 ttl=114 time=117.676 ms
received output: 64 bytes from 142.250.195.142: icmp_seq=3 ttl=114 time=57.780 ms
使用 STDIN 將輸入傳遞給命令
在前面的示例中,我們在不提供任何輸入(或提供有限的輸入作為參數(shù))的情況下執(zhí)行命令。在大多數(shù)情況下,輸入是通過 STDIN
流給出的。
譯注:就是外部給命令,然后去執(zhí)行
一個著名的例子是 grep
命令,我們可以通過管道從另一個命令輸入:
? ~ echo "1. pear\n2. grapes\n3. apple\n4. banana\n" | grep apple 3. apple
在這里,輸入通過 STDIN
傳遞給 grep
命令。在本例中,輸入是一個水果列表,grep
過濾包含 " apple"
的行。
Cmd
實例為我們提供了一個可以寫入的輸入流。讓我們使用它向 grep
子進程傳遞輸入:
cmd := exec.Command("grep", "apple") // Create a new pipe, which gives us a reader/writer pair reader, writer := io.Pipe() // assign the reader to Stdin for the command cmd.Stdin = reader // the output is printed to the console cmd.Stdout = os.Stdout go func() { defer writer.Close() // the writer is connected to the reader via the pipe // so all data written here is passed on to the commands // standard input writer.Write([]byte("1. pear\n")) writer.Write([]byte("2. grapes\n")) writer.Write([]byte("3. apple\n")) writer.Write([]byte("4. banana\n")) }() if err := cmd.Run(); err != nil { fmt.Println("could not run command: ", err) }
輸出:
3. apple
Kill 一個子進程
有幾個命令會無限期地運行,或者需要明確的信號才能停止。
例如,如果我們使用 python3 -m http.server 啟動 Web 服務(wù)器或執(zhí)行 sleep 10000,則生成的子進程將運行很長時間(或無限運行)。
要停止這些進程,我們需要從應(yīng)用程序發(fā)送終止信號。我們可以通過向命令添加一個上下文實例來做到這一點。
如果上下文被取消,命令也會終止。
ctx := context.Background() // The context now times out after 1 second // alternately, we can call `cancel()` to terminate immediately ctx, cancel = context.WithTimeout(ctx, 1*time.Second) cmd := exec.CommandContext(ctx, "sleep", "100") out, err := cmd.Output() if err != nil { fmt.Println("could not run command: ", err) } fmt.Println("Output: ", string(out))
這將在 1 秒后給出以下輸出:
could not run command: signal: killed
Output:
當您想要限制運行命令所花費的時間或想要創(chuàng)建回退以防命令未按時返回結(jié)果時,終止子進程很有用。
總結(jié)
到目前為止,我們學(xué)習了多種執(zhí)行 unix shell 命令并與之交互的方法。使用 os/exec 包時需要注意以下幾點:
- 當您想要執(zhí)行通常不會提供太多輸出的簡單命令時,請使用 cmd.Output
- 對于具有連續(xù)或長時間運行輸出的函數(shù),您應(yīng)該使用 cmd.Run 并使用 cmd.Stdout 和 cmd.Stdin 與命令交互
- 在生產(chǎn)應(yīng)用程序中,如果某個進程在給定的時間內(nèi)沒有響應(yīng),那么保持超時并終止該進程是非常有用的。我們可以使用上下文取消發(fā)送終止命令。
如果您想了解更多關(guān)于不同功能和配置選項的信息,可以查看官方文檔頁面。
以上就是在Golang中執(zhí)行Shell命令的教程詳解的詳細內(nèi)容,更多關(guān)于Golang執(zhí)行Shell 命令的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go實現(xiàn)用戶每日限額的方法(例一天只能領(lǐng)三次福利)
這篇文章主要介紹了Go實現(xiàn)用戶每日限額的方法(例一天只能領(lǐng)三次福利)2022-01-01