Go-os/exec

来自泡泡学习笔记
BrainBs讨论 | 贡献2024年2月20日 (二) 09:24的版本 (创建页面,内容为“ <span id="overview"></span> == Overview == 包exec用于运行外部命令。它封装了os.StartProcess,使得重新映射stdin和stdout、通过管道连接I/O以及其他调整变得更加容易。 与C和其他语言中的”system”库调用不同,os/exec包故意不调用系统shell,也不扩展任何glob模式或处理其他通常由shell执行的扩展、管道或重定向。该包的行为更类似于C的”exec”函数族。要扩展glob模…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

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)
}