1. 概述
golang
下的 os/exec
包執行外部命令包執行外部命令。它包裝了 os.StartProcess
函數以便更容易的修正輸入和輸出,使用管道連接I/O,以及作其它的一些調整。
與 C
語言或者其他語言中的“系統”庫調用不同, os/exec
包並不調用系統 shell
,也不展開任何 glob
(正則匹配)模式,也不處理通常由 shell
完成的其他擴展、管道或重定向。
2. 相關函數
2.1 Variables
var ErrNotFound = errors.New("executable file not found in $PATH")
如果路徑搜索沒有找到可執行文件時,就會返回本錯誤。
2.2 type Error
type Error struct {
Name string
Err error
}
Error類型記錄執行失敗的程序名和失敗的原因。對應的方法如下:
func (e *Error) Error() string
2.3 type ExitError
type ExitError struct {
*os.ProcessState
}
ExitError
報告某個命令的一次未成功的返回。對應的方法如下:
func (e *ExitError) Error() string
2.4 func LookPath
func LookPath(file string) (string, error)
2.5 type cmd
func Command(name string, arg ...string) *Cmd
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
func (c *Cmd) Run() error
func (c *Cmd) Start() error
func (c *Cmd) Wait() error
func (c *Cmd) Output() ([]byte, error)
func (c *Cmd) CombinedOutput() ([]byte, error)
3. 函數詳解
3.1 func LookPath
函數定義
func LookPath(file string) (string, error)
在環境變量 PATH
指定的目錄中搜索可執行文件,如 file
中有斜槓,則直接根據絕對路徑或者相對本目錄的相對路徑去查找。返回完整路徑或者相對於當前目錄的一個相對路徑。
默認在系統的環境變量裏查找給定的可執行命令文件。
使用示例:
package main
import (
"fmt"
"os/exec"
)
func main() {
f, err := exec.LookPath("pwd")
if err != nil {
fmt.Println("Not find the cmd")
}
fmt.Printf("Binary file path is %s", f) // /bin/pwd
}
3.2 Cmd 結構體
Cmd
代表一個正在準備或者在執行中的外部命令。
type Cmd struct {
// Path是將要執行的命令的路徑。
//
// 該字段不能爲空,如爲相對路徑會相對於Dir字段。
Path string
// Args保管命令的參數,包括命令名作爲第一個參數;如果爲空切片或者nil,相當於無參數命令。
//
// 典型用法下,Path和Args都應被Command函數設定。
Args []string
// Env指定進程的環境,如爲nil,則是在當前進程的環境下執行。
Env []string
// Dir指定命令的工作目錄。如爲空字符串,會在調用者的進程當前目錄下執行。
Dir string
// Stdin指定進程的標準輸入,如爲nil,進程會從空設備讀取(os.DevNull)
Stdin io.Reader
// Stdout和Stderr指定進程的標準輸出和標準錯誤輸出。
//
// 如果任一個爲nil,Run方法會將對應的文件描述符關聯到空設備(os.DevNull)
//
// 如果兩個字段相同,同一時間最多有一個線程可以寫入。
Stdout io.Writer
Stderr io.Writer
// ExtraFiles指定額外被新進程繼承的已打開文件流,不包括標準輸入、標準輸出、標準錯誤輸出。
// 如果本字段非nil,entry i會變成文件描述符3+i。
//
// BUG: 在OS X 10.6系統中,子進程可能會繼承不期望的文件描述符。
// http://golang.org/issue/2603
ExtraFiles []*os.File
// SysProcAttr保管可選的、各操作系統特定的sys執行屬性。
// Run方法會將它作爲os.ProcAttr的Sys字段傳遞給os.StartProcess函數。
SysProcAttr *syscall.SysProcAttr
// Process是底層的,只執行一次的進程。
Process *os.Process
// ProcessState包含一個已經存在的進程的信息,只有在調用Wait或Run後纔可用。
ProcessState *os.ProcessState
// 內含隱藏或非導出字段
}
注: exec
在執行調用系統命令時,會先對需要執行的操作進行一次封裝,然後再執行。封裝後的命令對象具有以上 struct
屬性。而封裝方式即使用下邊的 Command
函數。
3.3 func Command
func Command(name string, arg ...string) *Cmd
函數返回一個 *Cmd
,用於使用給出的參數執行 name
指定的程序。返回值只設定了 Path
和 Args
兩個參數。
如果 name
不含路徑分隔符,將使用 LookPath
獲取完整路徑;否則直接使用 name
。參數 arg
不應包含命令名。
使用示例:
func main() {
cmd := exec.Command("go", "version")
fmt.Printf("cmd.Path is %s, cmd.Args is %s", cmd.Path, cmd.Args)
// cmd.Path is /usr/local/go/bin/go, cmd.Args is [go version]
}
注意:以上操作只會將命令進行封裝,相當於告訴系統將進行哪些操作,但是執行時無法獲取相關信息。所以說Cmd
代表一個正在準備或者在執行中的外部命令。
3.4 func (*Cmd) Run
func (c *Cmd) Run() error
Run
執行 c
包含的命令,並阻塞直到完成。
- 如果命令成功執行,
stdin
、stdout
、stderr
的轉交沒有問題,並且返回狀態碼爲 0,方法的返回值爲nil
; - 如果命令沒有執行或者執行失敗,會返回
*ExitError
類型的錯誤;否則返回的error
可能是表示I/O
問題。
使用示例:
func main() {
cmd := exec.Command("go", "version")
fmt.Printf("cmd.Path is %s, cmd.Args is %s", cmd.Path, cmd.Args)
err := cmd.Run()
if err != nil {
fmt.Println("cmd run failed")
}
}
3.5 func (*Cmd) Start
func (c *Cmd) Start() error
Start
開始執行 c
包含的命令,但並不會等待該命令完成即返回。 Wait
方法會返回命令的返回狀態碼並在命令返回後釋放相關的資源。
3.6 func (*Cmd) Wait
func (c *Cmd) Wait() error
Wait
會阻塞直到該命令執行完成,該命令必須是被 Start
方法開始執行的。
- 如果命令成功執行,
stdin
、stdout
、stderr
的轉交沒有問題,並且返回狀態碼爲 0,方法的返回值爲nil
; - 如果命令沒有執行或者執行失敗,會返回
*ExitError
類型的錯誤;否則返回的error
可能是表示I/O
問題。Wait
方法會在命令返回後釋放相關的資源。
使用示例:
func main() {
cmd := exec.Command("go", "version")
fmt.Printf("cmd.Path is %s, cmd.Args is %s", cmd.Path, cmd.Args)
err := cmd.Start()
if err != nil {
fmt.Println("cmd run failed")
}
err = cmd.Wait()
if err != nil {
fmt.Println("cmd wait failed")
}
}
3.7 Start 和 Run 的區別
使用示例:
func main() {
cmd := exec.Command("sleep", "5")
// err := cmd.Run() 執行 Run 會立即阻塞等待 5 秒種
err := cmd.Start()
if err != nil {
fmt.Println("cmd run failed")
}
fmt.Println("waiting for cmd ...")
// 執行 Start() 上面的打印會先執行,然後在 Wait() 方法阻塞等待 5s
err = cmd.Wait()
if err != nil {
fmt.Println("cmd wait failed")
}
}
注意:
一個命令只能使用 Start()
或者 Run()
中的一個啓動命令,不能兩個同時使用。
3.8 func CombinedOutput
func (c *Cmd) CombinedOutput() ([]byte, error)
執行命令並返回標準輸出和錯誤輸出合併的切片。
3.9 func Output
func (c *Cmd) Output() ([]byte, error)
執行命令並返回標準輸出的切片。
使用示例:
func main() {
cmd := exec.Command("ls", "-a", "-l")
// err := cmd.Start()
// if err != nil {
// fmt.Println("cmd run failed")
// }
// fmt.Println("waiting for cmd ...")
// err = cmd.Wait()
// if err != nil {
// fmt.Println("cmd wait failed")
// }
out, err := cmd.Output()
if err != nil {
fmt.Println("cmd Output failed ", err)
}
fmt.Printf("out is %s", out)
}
注意:
Output()
和CombinedOutput()
不能夠同時使用,因爲command
的標準輸出只能有一個,同時使用的話便會定義了兩個,便會報錯;Command
和Output
之間不需要再增加Start
、Wait
方法,否則也會報錯;
3.10 func (*Cmd) StdinPipe
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
StdinPipe
方法返回一個在命令 Start
後與命令標準輸入關聯的管道。 Wait
方法獲知命令結束後會關閉這個管道。必要時調用者可以調用 Close
方法來強行關閉管道,例如命令在輸入關閉後纔會執行返回時需要顯式關閉管道。
3.11 func (*Cmd) StdoutPipe
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
StdoutPipe
方法返回一個在命令 Start
後與命令標準輸出關聯的管道。 Wait
方法獲知命令結束後會關閉這個管道,一般不需要顯式的關閉該管道。但是在從管道讀取完全部數據之前調用 Wait
是錯誤的;同樣使用 StdoutPipe
方法時調用 Run
函數也是錯誤的。
3.12 func (*Cmd) StderrPipe
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
StderrPipe
方法返回一個在命令 Start
後與命令標準錯誤輸出關聯的管道。 Wait
方法獲知命令結束後會關閉這個管道,一般不需要顯式的關閉該管道。但是在從管道讀取完全部數據之前調用 Wait
是錯誤的;同樣使用 StderrPipe
方法時調用 Run
函數也是錯誤的。
參考資料: