Go-os/exec
Overview
包exec用于运行外部命令。它封装了os.StartProcess,使得重新映射stdin和stdout、通过管道连接I/O以及其他调整变得更加容易。
与C和其他语言中的”system”库调用不同,os/exec包故意不调用系统shell,也不扩展任何glob模式或处理其他通常由shell执行的扩展、管道或重定向。该包的行为更类似于C的”exec”函数族。要扩展glob模式,可以直接调用shell,并注意转义任何危险的输入,或者使用path/filepath包的Glob函数。要扩展环境变量,请使用os包的ExpandEnv。
请注意,此包中的示例假定为Unix系统。它们可能无法在Windows上运行,也不会在golang.org和godoc.org使用的Go Playground中运行。
func LookPath
func LookPath(file string) (string, error)
LookPath函数在PATH环境变量所指定的目录中搜索名为file的可执行文件。如果file包含斜杠,那么直接尝试该文件,而不会查询PATH。否则,如果成功,结果将是绝对路径。
在较旧版本的Go中,LookPath可能返回相对于当前目录的路径。从Go 1.19版本开始,LookPath将改为返回该路径以及一个满足errors.Is(err, ErrDot)的错误。有关更多详细信息,请参阅包文档。
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
path, err := exec.LookPath("fortune")
if err != nil {
log.Fatal("installing fortune is in your future")
}
fmt.Printf("fortune is available at %s\n", path)
}
func Command
func Command(name string, arg ...string) *Cmd
Command函数返回一个Cmd结构体,用于执行具有给定参数的指定程序。
它仅设置返回结构的Path和Args字段。
如果name不包含路径分隔符,Command会使用LookPath尽可能地将name解析为完整路径。否则,它会直接使用name作为Path。
返回的Cmd的Args字段是由命令名称后跟arg的元素构成的,因此arg不应包括命令名称本身。例如,Command(“echo”, “hello”).Args[0]始终是名称,而不是可能已解析的Path。
在Windows上,进程会接收整个命令行作为一个单独的字符串并进行自己的解析。Command使用与使用CommandLineToArgvW的应用程序兼容的算法(这是最常见的方式)来组合并引用Args成为一个命令行字符串。值得注意的是,msiexec.exe和cmd.exe(以及所有批处理文件)有不同
import (
"fmt"
"log"
"os/exec"
"strings"
)
func main() {
cmd := exec.Command("tr", "a-z", "A-Z")
cmd.Stdin = strings.NewReader("some input")
var out strings.Builder
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("in all caps: %q\n", out.String())
}
package main
import (
"log"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("prog")
cmd.Env = append(os.Environ(),
"FOO=duplicate_value", // ignored
"FOO=actual_value", // this value is used
)
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
func CommandContext
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd
CommandContext类似于Command,但包含了一个上下文。
提供的上下文用于在命令自行完成之前,如果上下文变为done状态,中断进程(通过调用cmd.Cancel或os.Process.Kill)。
CommandContext将命令的Cancel函数设置为在其Process上调用Kill方法,并保持其WaitDelay未设置。调用者可以通过在启动命令之前修改这些字段来更改取消行为。
package main
import (
"context"
"os/exec"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
// This will fail after 100 milliseconds. The 5 second sleep
// will be interrupted.
}
}
func (*Cmd) CombinedOutput
func (c *Cmd) CombinedOutput() ([]byte, error)
CombinedOutput运行命令并返回其标准输出和标准错误的组合。
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", stdoutStderr)
}
func (*Cmd) Environ
func (c *Cmd) Environ() []string
Environ返回当前配置下命令将要运行的环境的副本。
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("pwd")
// Set Dir before calling cmd.Environ so that it will include an
// updated PWD variable (on platforms where that is used).
cmd.Dir = ".."
cmd.Env = append(cmd.Environ(), "POSIXLY_CORRECT=1")
out, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
func (*Cmd) Output
func (c *Cmd) Output() ([]byte, error)
Output运行命令并返回其标准输出。任何返回的错误通常都是*ExitError类型。如果c.Stderr为nil,Output会填充ExitError.Stderr。
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}
func (*Cmd) Run
func (c *Cmd) Run() error
Run启动指定的命令并等待其完成。
如果命令运行,没有在复制stdin、stdout和stderr时出现问题,并且以零退出状态退出,则返回的错误为nil。
如果命令启动但未成功完成,错误类型为*ExitError。其他情况可能返回其他错误类型。
如果调用goroutine使用runtime.LockOSThread锁定了操作系统线程并修改了任何可继承的操作系统级线程状态(例如Linux或Plan 9名称空间),新进程将继承调用者的线程状态。
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sleep", "1")
log.Printf("Running command and waiting for it to finish...")
err := cmd.Run()
log.Printf("Command finished with error: %v", err)
}
func (*Cmd) Start
func (c *Cmd) Start() error
Start启动指定的命令,但不等待其完成。
如果Start成功返回,c.Process字段将被设置。
在成功调用Start之后,必须调用Wait方法以释放关联的系统资源。
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sleep", "5")
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)
}
func (*Cmd) StderrPipe
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
StderrPipe返回一个管道,当命令启动时,该管道将连接到命令的标准错误。
Wait将在看到命令退出后关闭管道,因此大多数调用者无需自行关闭管道。因此,在管道的所有读取完成之前调用Wait是不正确的。出于同样的原因,在使用StderrPipe时使用Run也是不正确的。请参阅StdoutPipe示例以获取惯用用法。
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
stderr, err := cmd.StderrPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
slurp, _ := io.ReadAll(stderr)
fmt.Printf("%s\n", slurp)
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
}
func (*Cmd) StdinPipe
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
StdinPipe返回一个管道,当命令启动时,该管道将连接到命令的标准输入。在Wait看到命令退出后,管道将自动关闭。调用者只需调用Close来强制更早关闭管道。例如,如果正在运行的命令在标准输入关闭之前不会退出,调用者必须关闭管道。
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
func (*Cmd) StdoutPipe
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
StdoutPipe返回一个管道,当命令启动时,该管道将连接到命令的标准输出。
Wait将在看到命令退出后关闭管道,因此大多数调用者无需自行关闭管道。因此,在管道的所有读取完成之前调用Wait是不正确的。出于同样的原因,在使用StdoutPipe时调用Run也是不正确的。请参阅示例以获取惯用用法。
package main
import (
"encoding/json"
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
var person struct {
Name string
Age int
}
if err := json.NewDecoder(stdout).Decode(&person); err != nil {
log.Fatal(err)
}
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
fmt.Printf("%s is %d years old\n", person.Name, person.Age)
}