golang封裝一個執(zhí)行命令行的函數(shù)(return?stderr/stdout/exitcode)示例代碼
啟動shell來執(zhí)行命令行
在 Go 語言中,您可以使用 os/exec
包來執(zhí)行外部命令,不通過調(diào)用 shell,并且能夠獲得進(jìn)程的退出碼、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出。下面是一個封裝好的函數(shù) runCommand
,它符合您的需求:
package main import ( "bytes" "fmt" "os/exec" ) // runCommand 函數(shù)接受一個命令行字符串,執(zhí)行它并返回退出碼、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出 func runCommand(cmdline string) (exitCode int, stdout string, stderr string, err error) { // 解析命令行并準(zhǔn)備執(zhí)行 cmd := exec.Command("sh", "-c", cmdline) var outBuffer, errBuffer bytes.Buffer cmd.Stdout = &outBuffer cmd.Stderr = &errBuffer // 執(zhí)行命令 err = cmd.Run() stdout = outBuffer.String() stderr = errBuffer.String() // 獲取退出碼。如果命令執(zhí)行出錯,嘗試獲取退出碼 if err != nil { if exitError, ok := err.(*exec.ExitError); ok { exitCode = exitError.ExitCode() } else { // 如果出現(xiàn)非 ExitError 類型錯誤,如執(zhí)行文件不存在或無執(zhí)行權(quán)限 return 0, stdout, stderr, err } } else { // 命令成功執(zhí)行,退出碼為 0 exitCode = 0 } return exitCode, stdout, stderr, nil } func main() { // 測試 runCommand 函數(shù) exitCode, stdout, stderr, err := runCommand("ls -a") if err != nil { fmt.Printf("命令執(zhí)行出錯: %v\n", err) } fmt.Printf("退出碼: %d\n標(biāo)準(zhǔn)輸出: %s\n標(biāo)準(zhǔn)錯誤輸出: %s\n", exitCode, stdout, stderr) }
這里的 runCommand
函數(shù)接受一個命令行字符串,執(zhí)行這條命令,并返回相關(guān)運(yùn)行結(jié)果。這個函數(shù)使用 exec.Command
來避免調(diào)用 shell。我們通過將命令傳給 sh
的 -c
參數(shù),間接地執(zhí)行命令,但繞過了可執(zhí)行文件的正常尋找機(jī)制來防止 shell 注入問題。
結(jié)果輸出會分別寫入到 bytes.Buffer
,這樣您就可以讀取并將其轉(zhuǎn)換為字符串。如果存在錯誤,且錯誤為 *exec.ExitError
類型,那么說明命令返回了非零退出碼,這時我們可以獲取并返回這個退出碼;如果是其他類型的錯誤,則直接返回錯誤。
這個函數(shù)的返回值包含了進(jìn)程的退出碼,標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出的字符串表示,以及一個可能的錯誤值。如果命令成功執(zhí)行(即便其退出碼非零),則錯誤值為 nil
。如果函數(shù)中出現(xiàn)了 Go 運(yùn)行時錯誤,比如命令不存在或無法啟動,那么會返回這個錯誤。
不啟動shell的方式
確實,前面的示例通過調(diào)用 sh
間接運(yùn)行命令,依然會啟動 shell。若要避免這種情況,我們會直接分解命令行字符串為命令名和參數(shù),然后不通過 shell 直接執(zhí)行這個命令。以下是經(jīng)過修改的 runCommand
函數(shù),它將使用 strings.Fields
來解析傳入的命令行字符串,并且不會啟動 shell。
package main import ( "bytes" "fmt" "os/exec" "strings" ) // runCommand 函數(shù)接受一個命令行字符串,執(zhí)行它(無 shell 啟動)并返回退出碼、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出 func runCommand(cmdline string) (exitCode int, stdout string, stderr string, err error) { // 使用 strings.Fields 解析命令行,分離出命令和參數(shù) args := strings.Fields(cmdline) if len(args) == 0 { return 0, "", "", fmt.Errorf("no command provided") } cmdName := args[0] cmdArgs := args[1:] // 準(zhǔn)備執(zhí)行不經(jīng)過 shell 的命令 cmd := exec.Command(cmdName, cmdArgs...) var outBuffer, errBuffer bytes.Buffer cmd.Stdout = &outBuffer cmd.Stderr = &errBuffer // 執(zhí)行命令 err = cmd.Run() stdout = outBuffer.String() stderr = errBuffer.String() // 獲取退出碼。如果命令執(zhí)行出錯,嘗試獲取退出碼 if exitErr, ok := err.(*exec.ExitError); ok { err = nil // 清理錯誤,我們已經(jīng)獲取了退出碼 exitCode = exitErr.ExitCode() } else if err != nil { // 如果出現(xiàn)非 ExitError 類型錯誤,如執(zhí)行文件不存在或無執(zhí)行權(quán)限 exitCode = -1 } else { // 命令成功執(zhí)行,退出碼為 0 exitCode = 0 } return exitCode, stdout, stderr, err } func main() { // 測試 runCommand 函數(shù) exitCode, stdout, stderr, err := runCommand("ls -a -l -h") if err != nil { fmt.Printf("命令執(zhí)行出錯: %v\n", err) } fmt.Printf("退出碼: %d\n標(biāo)準(zhǔn)輸出: %s\n標(biāo)準(zhǔn)錯誤輸出: %s\n", exitCode, stdout, stderr) }
請注意,這個函數(shù)假設(shè)命令行參數(shù)不含有引號封閉的參數(shù)或其他需要 shell 解析的元素(如通配符 *
或重定向 >
)。它簡單地把空白符作為參數(shù)分隔符,這是一個限制。如果你需要解析復(fù)雜的命令行字符串(包含引號或特殊字符),那么你需要一個更強(qiáng)大的解析器,或者需要以某種方式預(yù)處理命令行字符串。
因為直接執(zhí)行命令而不經(jīng)由 shell,原本在 shell 中利用的特性(如通配符展開、環(huán)境變量替換等)將不再可用。確保你的命令行字符串直接傳遞給系統(tǒng)調(diào)用是安全的。如果需要 shell 特性,那么你可能還需要調(diào)用 shell,但這正是你想要避免的。
關(guān)于類型斷言的表達(dá)式err.(*exec.ExitError)
在 Go 語言中, err.(*exec.ExitError)
是一個類型斷言的表達(dá)式。類型斷言用于檢查變量 err
是否為特定類型,或者能否轉(zhuǎn)換為特定類型。在這種情況下,類型斷言檢查 err
是否可以被斷言為 *exec.ExitError
類型。
exec.ExitError
是 os/exec
包中的一個結(jié)構(gòu)體類型,它實現(xiàn)了 error
接口,并包含了退出錯誤的相關(guān)信息。當(dāng)一個外部命令運(yùn)行結(jié)束并返回一個非零退出狀態(tài)時,os/exec
包的 Run
或 Wait
函數(shù)會返回一個 error
類型的值,這通常就是一個 *exec.ExitError
類型的實例。
一個類型斷言通常具有以下兩種形式:
- x.(T):這種形式用于斷言變量
x
是否為類型T
。如果斷言成功,就會得到x
的類型T
的值。如果失敗,則會產(chǎn)生一個運(yùn)行時錯誤。因此,使用這種形式時必須非常確定x
能夠斷言為類型T
。 - x, ok := x.(T):這種類型斷言形式比較安全,因為如果斷言失敗,它不會拋出錯誤,而是將
ok
的值設(shè)為false
,同時x
的值會被設(shè)為類型T
的零值。在你不確定x
的類型是否為T
或者你想要安全地檢查類型時,應(yīng)該使用這種形式。
在提供的代碼示例中,我們使用了第二種形式的類型斷言:
if exitErr, ok := err.(*exec.ExitError); ok { exitCode = exitErr.ExitCode() // ... }
這里,我們嘗試將 error
值 err
斷言為類型 *exec.ExitError
。如果斷言成功(表示外部命令執(zhí)行失敗,并返回了一個非零退出代碼),變量 ok
的值將為 true
,exitErr
將為 *exec.ExitError
類型,我們可以通過它的 ExitCode()
方法獲取實際的退出代碼。如果斷言失敗(err
不是 *exec.ExitError
類型),那么 ok
為 false
,這通常表示 err
是另一種類型的錯誤或 nil
。
到此這篇關(guān)于golang封裝一個執(zhí)行命令行的函數(shù)(return stderr/stdout/exitcode)的文章就介紹到這了,更多相關(guān)golang執(zhí)行命令行的函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
gin自定義中間件解決requestBody不可重讀(請求體取值)
這篇文章主要介紹了gin自定義中間件解決requestBody不可重讀,確??刂破髂軌颢@取請求體值,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10詳解Go中如何進(jìn)行進(jìn)行內(nèi)存優(yōu)化和垃圾收集器管理
這篇文章主要為大家詳細(xì)介紹了Go中如何進(jìn)行進(jìn)行內(nèi)存優(yōu)化和垃圾收集器管理,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以了解下2023-11-11golang使用sync.singleflight解決熱點緩存穿透問題
在go的sync包中,有一個singleflight包,里面有一個?singleflight.go文件,代碼加注釋,一共200行出頭,通過?singleflight可以很容易實現(xiàn)緩存和去重的效果,避免重復(fù)計算,接下來我們就給大家詳細(xì)介紹一下sync.singleflight如何解決熱點緩存穿透問題2023-07-07Go語言基礎(chǔ)switch條件語句基本用法及示例詳解
這篇文章主要為大家介紹了Go語言基礎(chǔ)switch條件語句基本用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11