Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Go os/exec 包详解

概述

os/exec 包用于运行外部命令。它封装了 os.StartProcess 使其更容易重映射 stdin 和 stdout、通过管道连接 I/O 以及进行其他调整。

重要说明

  • ✓ 运行外部命令
  • ✓ 封装 os.StartProcess
  • ✓ 支持 stdin/stdout/stderr 管道
  • ✓ Go 1.0+ 引入
  • ✓ 不调用系统 shell
  • ✓ 不展开 glob 模式或环境变量
  • ✓ Go 1.19+ 增强安全性(ErrDot)

与 system 调用的区别: 与 C 和其他语言的“system“库调用不同,os/exec 包故意不调用系统 shell,也不展开任何 glob 模式或处理 shell 通常完成的其他展开、管道或重定向。该包的行为更像 C 的“exec“函数家族。

安全说明: 自 Go 1.19 起,该包不会解析相对于当前目录的可执行文件。如果查找结果为 ./go(Unix)或 .\go.exe(Windows),将返回满足 errors.Is(err, ErrDot) 的错误。

包导入

import (
    "os/exec"
)

基本使用

1. 运行简单命令

package main

import (
    "fmt"
    "os/exec"
    "log"
)

func main() {
    cmd := exec.Command("echo", "Hello, World!")
    output, err := cmd.Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("输出:%s\n", string(output))
}

运行结果:

输出:Hello, World!

2. 运行命令并获取输出

package main

import (
    "fmt"
    "os/exec"
    "log"
)

func main() {
    // 运行 ls 命令
    cmd := exec.Command("ls", "-l")
    output, err := cmd.Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", string(output))
}

3. 运行命令并等待完成

package main

import (
    "os/exec"
    "log"
)

func main() {
    cmd := exec.Command("sleep", "2")
    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("命令执行完成")
}

一、变量

ErrDot

var ErrDot = errors.New("cannot run executable found relative to current directory")

ErrDot 表示路径查找解析到当前目录中的可执行文件,原因是 ‘.’ 在路径中(隐式或显式)。

说明:

  • Go 1.19+ 引入
  • 防止从当前目录运行可执行文件的安全措施
  • 使用 errors.Is(err, ErrDot) 检测,不要使用 err == ErrDot

示例:

path, err := exec.LookPath("prog")
if errors.Is(err, exec.ErrDot) {
    // 找到了 ./prog 或 .\prog.exe
    // 可以选择忽略此错误或处理
    err = nil
}
if err != nil {
    log.Fatal(err)
}

ErrNotFound

var ErrNotFound = errors.New("executable file not found in $PATH")

ErrNotFound 是路径搜索找不到可执行文件时返回的错误。

示例:

path, err := exec.LookPath("nonexistent")
if err == exec.ErrNotFound {
    fmt.Println("命令不存在")
}

ErrWaitDelay

var ErrWaitDelay = errors.New("os/exec: WaitDelay expired before I/O complete")

ErrWaitDelay 在命令以成功状态码退出但其输出管道在命令的 WaitDelay 过期之前未关闭时由 Cmd.Wait 返回。


二、类型(按 a-z 排序)

Cmd

Cmd 表示正在准备或运行的外部命令。

重要: Cmd 在调用 Run、Output 或 CombinedOutput 方法后不能重用。

type Cmd struct {
    // 命令的路径
    Path string
    
    // 命令的参数(包括命令名)
    Args []string
    
    // 环境变量
    Env []string
    
    // 工作目录
    Dir string
    
    // 标准输入
    Stdin io.Reader
    
    // 标准输出
    Stdout io.Writer
    
    // 标准错误
    Stderr io.Writer
    
    // 退出延迟(Go 1.21+)
    WaitDelay time.Duration
    
    // 包含隐藏或未导出的字段
}

字段说明:

  • Path - 可执行文件的路径
  • Args - 命令行参数(包括命令名)
  • Env - 环境变量(格式:“KEY=value”)
  • Dir - 工作目录
  • Stdin - 标准输入
  • Stdout - 标准输出
  • Stderr - 标准错误
  • WaitDelay - 等待 I/O 完成的超时时间(Go 1.21+)

Command

func Command(name string, arg ...string) *Cmd

Command 返回执行指定程序和参数的 Cmd 结构。

参数:

  • name - 命令名
  • arg - 命令参数(不包括命令名本身)

返回值:

  • *Cmd - 命令对象

示例:

// 运行 echo 命令
cmd := exec.Command("echo", "hello", "world")

// 运行带参数的命令
cmd := exec.Command("ls", "-l", "-a")

// Args[0] 总是命令名
fmt.Println(cmd.Args) // ["echo", "hello", "world"]

说明:

  • 如果 name 不包含路径分隔符,使用 LookPath 解析
  • Args[0] 总是 name,而不是可能解析的 Path
  • 在 Windows 上,进程接收整个命令行作为单个字符串

CommandContext

func CommandContext(ctx context.Context, name string, arg ...string) *Cmd

CommandContext 类似于 Command,但包含 context。

参数:

  • ctx - 上下文
  • name - 命令名
  • arg - 命令参数

返回值:

  • *Cmd - 命令对象

示例:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, "sleep", "10")
err := cmd.Run()
if err != nil {
    if ctx.Err() == context.DeadlineExceeded {
        fmt.Println("命令超时")
    }
}

说明:

  • 如果 context 在命令完成前变为 done,则中断进程
  • 设置 Cancel 函数调用 Kill 方法
  • 不设置 WaitDelay

Cmd.CombinedOutput

func (c *Cmd) CombinedOutput() ([]byte, error)

CombinedOutput 运行命令并返回其合并的标准输出和标准错误。

返回值:

  • []byte - 合并的输出
  • error - 错误

示例:

cmd := exec.Command("sh", "-c", "echo stdout; echo stderr >&2")
output, err := cmd.CombinedOutput()
if err != nil {
    log.Fatal(err)
}
fmt.Printf("输出:%s\n", string(output))
// 输出包含 stdout 和 stderr

Cmd.Environ

func (c *Cmd) Environ() []string

Environ 返回命令运行环境的副本(当前配置的状态)。

返回值:

  • []string - 环境变量列表

示例:

cmd := exec.Command("env")
cmd.Env = []string{
    "PATH=/usr/bin",
    "HOME=/home/user",
    "MYVAR=value",
}

env := cmd.Environ()
for _, e := range env {
    fmt.Println(e)
}

Cmd.Output

func (c *Cmd) Output() ([]byte, error)

Output 运行命令并返回其标准输出。

返回值:

  • []byte - 标准输出
  • error - 错误(通常是 *ExitError)

示例:

cmd := exec.Command("ls", "-l")
output, err := cmd.Output()
if err != nil {
    if exitErr, ok := err.(*exec.ExitError); ok {
        fmt.Printf("命令失败:%s\n", exitErr.Stderr)
    }
    log.Fatal(err)
}
fmt.Printf("%s\n", string(output))

说明:

  • 如果 c.Stderr 为 nil 且返回 *ExitError,Output 会填充 Stderr 字段

Cmd.Run

func (c *Cmd) Run() error

Run 启动指定命令并等待其完成。

返回值:

  • error - 错误(nil 表示成功)

示例:

cmd := exec.Command("sleep", "2")
err := cmd.Run()
if err != nil {
    log.Fatal(err)
}
fmt.Println("命令执行成功")

说明:

  • 如果命令运行正常、无 stdin/stdout/stderr 复制问题且退出状态为 0,则返回 nil
  • 如果命令启动但未成功完成,错误为 *ExitError

Cmd.Start

func (c *Cmd) Start() error

Start 启动指定命令但不等待其完成。

返回值:

  • error - 启动错误

示例:

cmd := exec.Command("sleep", "5")
err := cmd.Start()
if err != nil {
    log.Fatal(err)
}

fmt.Println("命令已启动,PID:", cmd.Process.Pid)

// 必须调用 Wait 释放资源
err = cmd.Wait()

说明:

  • 如果 Start 成功返回,c.Process 字段将被设置
  • 成功调用 Start 后必须调用 Wait 释放相关系统资源

Cmd.StderrPipe

func (c *Cmd) StderrPipe() (io.ReadCloser, error)

StderrPipe 返回一个管道,命令启动时将连接到命令的标准错误。

返回值:

  • io.ReadCloser - 读取管道
  • error - 错误

示例:

cmd := exec.Command("sh", "-c", "echo error >&2")

stderr, err := cmd.StderrPipe()
if err != nil {
    log.Fatal(err)
}

if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

data, err := io.ReadAll(stderr)
if err != nil {
    log.Fatal(err)
}

if err := cmd.Wait(); err != nil {
    log.Fatal(err)
}

fmt.Printf("stderr: %s\n", string(data))

说明:

  • Cmd.Wait 将在看到命令退出后关闭管道
  • 不应在使用 StderrPipe 时使用 Cmd.Run

Cmd.StdinPipe

func (c *Cmd) StdinPipe() (io.WriteCloser, error)

StdinPipe 返回一个管道,命令启动时将连接到命令的标准输入。

返回值:

  • io.WriteCloser - 写入管道
  • error - 错误

示例:

cmd := exec.Command("cat") // cat 会回显输入

stdin, err := cmd.StdinPipe()
if err != nil {
    log.Fatal(err)
}

if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

// 写入输入
stdin.Write([]byte("hello"))
stdin.Close() // 关闭输入以让 cat 退出

if err := cmd.Wait(); err != nil {
    log.Fatal(err)
}

说明:

  • 管道在 Cmd.Wait 看到命令退出后自动关闭
  • 调用者只需调用 Close 强制管道更早关闭

Cmd.StdoutPipe

func (c *Cmd) StdoutPipe() (io.ReadCloser, error)

StdoutPipe 返回一个管道,命令启动时将连接到命令的标准输出。

返回值:

  • io.ReadCloser - 读取管道
  • error - 错误

示例:

cmd := exec.Command("echo", "hello")

stdout, err := cmd.StdoutPipe()
if err != nil {
    log.Fatal(err)
}

if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

data, err := io.ReadAll(stdout)
if err != nil {
    log.Fatal(err)
}

if err := cmd.Wait(); err != nil {
    log.Fatal(err)
}

fmt.Printf("输出:%s\n", string(data))

说明:

  • Cmd.Wait 将在看到命令退出后关闭管道
  • 不应在使用 StdoutPipe 时使用 Cmd.Run

Cmd.String

func (c *Cmd) String() string

String 返回 c 的人类可读描述。

返回值:

  • string - 命令描述

示例:

cmd := exec.Command("ls", "-l", "-a")
fmt.Println(cmd.String())
// 输出:ls -l -a

说明:

  • 仅用于调试
  • 不适合用作 shell 输入
  • 输出可能因 Go 版本而异

Cmd.Wait

func (c *Cmd) Wait() error

Wait 等待命令退出并完成 stdin/stdout/stderr 的任何复制。

返回值:

  • error - 错误

示例:

cmd := exec.Command("sleep", "2")
if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

// 做一些其他事情...

// 等待命令完成
err := cmd.Wait()
if err != nil {
    log.Fatal(err)
}

说明:

  • 命令必须已通过 Cmd.Start 启动
  • 如果命令运行正常、无复制问题且退出状态为 0,则返回 nil
  • Wait 释放与 Cmd 关联的所有资源

Error

Error 由 LookPath 在无法将文件分类为可执行文件时返回。

type Error struct {
    Name string
    Err  error
}

Error.Error

func (e *Error) Error() string

Error 返回错误的字符串表示。

Error.Unwrap

func (e *Error) Unwrap() error

Unwrap 返回内部错误,支持 errors.Is 和 errors.As。

ExitError

ExitError 报告命令的未成功退出。

type ExitError struct {
    *os.ProcessState
    
    // Stderr 包含命令的标准错误输出(如果有)
    Stderr []byte
}

字段说明:

  • ProcessState - 进程状态信息
  • Stderr - 标准错误输出

ExitError.Error

func (e *ExitError) Error() string

Error 返回错误的字符串表示。

示例:

cmd := exec.Command("false") // 返回退出码 1 的命令
err := cmd.Run()
if err != nil {
    if exitErr, ok := err.(*exec.ExitError); ok {
        fmt.Printf("命令失败:%s\n", exitErr.Error())
        fmt.Printf("退出码:%d\n", exitErr.ExitCode())
        fmt.Printf("stderr: %s\n", string(exitErr.Stderr))
    }
}

三、函数(按 a-z 排序)

LookPath

func LookPath(file string) (string, error)

LookPath 在 PATH 环境变量命名的目录中搜索名为 file 的可执行文件。

参数:

  • file - 要查找的可执行文件名

返回值:

  • string - 可执行文件的绝对路径
  • error - 错误(如果找不到)

示例:

// 查找 go 命令
path, err := exec.LookPath("go")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("go 命令路径:%s\n", path)

// 查找当前目录的命令(会返回 ErrDot)
path, err = exec.LookPath("myapp")
if errors.Is(err, exec.ErrDot) {
    // 找到了 ./myapp
    err = nil // 可以选择忽略
}

说明:

  • 如果 file 包含斜杠,直接尝试而不咨询 PATH
  • 成功时返回绝对路径
  • Go 1.19+ 不会返回相对于当前目录的路径

四、典型示例

示例 1:运行命令并捕获输出

package main

import (
    "fmt"
    "os/exec"
    "log"
)

func main() {
    cmd := exec.Command("date")
    output, err := cmd.Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("当前时间:%s\n", string(output))
}

示例 2:带超时的命令执行

package main

import (
    "context"
    "fmt"
    "log"
    "os/exec"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    cmd := exec.CommandContext(ctx, "sleep", "5")
    err := cmd.Run()
    
    if err != nil {
        if ctx.Err() == context.DeadlineExceeded {
            fmt.Println("命令执行超时")
        } else {
            log.Fatal(err)
        }
    } else {
        fmt.Println("命令执行成功")
    }
}

示例 3:设置环境变量

package main

import (
    "fmt"
    "os/exec"
    "log"
)

func main() {
    cmd := exec.Command("env")
    cmd.Env = []string{
        "PATH=/usr/bin",
        "HOME=/home/user",
        "MYVAR=hello",
    }
    
    output, err := cmd.Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", string(output))
}

示例 4:设置工作目录

package main

import (
    "fmt"
    "os/exec"
    "log"
)

func main() {
    cmd := exec.Command("pwd")
    cmd.Dir = "/tmp"
    
    output, err := cmd.Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("工作目录:%s\n", string(output))
}

示例 5:管道输入到命令

package main

import (
    "fmt"
    "os/exec"
    "strings"
    "log"
)

func main() {
    cmd := exec.Command("wc", "-l")
    cmd.Stdin = strings.NewReader("line1\nline2\nline3\n")
    
    output, err := cmd.Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("行数:%s", string(output))
}

运行结果:

行数:3

示例 6:同时捕获 stdout 和 stderr

package main

import (
    "bytes"
    "fmt"
    "os/exec"
    "log"
)

func main() {
    cmd := exec.Command("sh", "-c", "echo stdout; echo stderr >&2")
    
    var stdout, stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr
    
    err := cmd.Run()
    if err != nil {
        log.Printf("命令失败:%v\n", err)
    }
    
    fmt.Printf("stdout: %s\n", stdout.String())
    fmt.Printf("stderr: %s\n", stderr.String())
}

示例 7:使用 StdoutPipe 流式读取输出

package main

import (
    "bufio"
    "fmt"
    "io"
    "os/exec"
    "log"
)

func main() {
    cmd := exec.Command("seq", "1", "10")
    
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }
    
    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }
    
    reader := bufio.NewReader(stdout)
    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            if err == io.EOF {
                break
            }
            log.Fatal(err)
        }
        fmt.Printf("收到:%s", line)
    }
    
    if err := cmd.Wait(); err != nil {
        log.Fatal(err)
    }
}

示例 8:查找命令路径

package main

import (
    "fmt"
    "os/exec"
    "errors"
)

func main() {
    commands := []string{"go", "git", "python3", "nonexistent"}
    
    for _, cmd := range commands {
        path, err := exec.LookPath(cmd)
        if errors.Is(err, exec.ErrDot) {
            fmt.Printf("%s: 在当前目录(已忽略)\n", cmd)
            err = nil
        }
        if err != nil {
            fmt.Printf("%s: 未找到\n", cmd)
        } else {
            fmt.Printf("%s: %s\n", cmd, path)
        }
    }
}

运行结果:

go: /usr/bin/go
git: /usr/bin/git
python3: /usr/bin/python3
nonexistent: 未找到

示例 9:运行命令并检查退出码

package main

import (
    "fmt"
    "os/exec"
    "log"
)

func main() {
    cmd := exec.Command("false") // 返回退出码 1
    err := cmd.Run()
    
    if err != nil {
        if exitErr, ok := err.(*exec.ExitError); ok {
            fmt.Printf("命令失败\n")
            fmt.Printf("退出码:%d\n", exitErr.ExitCode())
            fmt.Printf("错误信息:%s\n", exitErr.Error())
        } else {
            log.Fatal(err)
        }
    } else {
        fmt.Println("命令成功")
    }
}

运行结果:

命令失败
退出码:1
错误信息:exit status 1

示例 10:并发运行多个命令

package main

import (
    "fmt"
    "os/exec"
    "sync"
    "log"
)

func runCommand(name string, wg *sync.WaitGroup) {
    defer wg.Done()
    
    cmd := exec.Command("echo", "Hello from", name)
    output, err := cmd.Output()
    if err != nil {
        log.Printf("%s 失败:%v\n", name, err)
        return
    }
    fmt.Printf("%s: %s", name, string(output))
}

func main() {
    var wg sync.WaitGroup
    
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go runCommand(fmt.Sprintf("Goroutine %d", i), &wg)
    }
    
    wg.Wait()
    fmt.Println("所有命令完成")
}

五、最佳实践

1. 使用 CommandContext 控制超时

// ✓ 推荐 - 使用 CommandContext
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "long-running-command")
err := cmd.Run()

// ✗ 不推荐 - 手动控制超时
cmd := exec.Command("long-running-command")
// 难以控制执行时间

2. 总是检查错误

// ✓ 正确
output, err := cmd.Output()
if err != nil {
    if exitErr, ok := err.(*exec.ExitError); ok {
        log.Printf("命令失败:%s", exitErr.Stderr)
    }
    log.Fatal(err)
}

// ✗ 错误 - 忽略错误
output, _ := cmd.Output() // 可能输出为空

3. 使用管道时正确关闭

// ✓ 正确
stdin, err := cmd.StdinPipe()
if err != nil {
    log.Fatal(err)
}
stdin.Write(data)
stdin.Close() // 必须关闭
cmd.Wait()

// ✗ 错误 - 忘记关闭
stdin, _ := cmd.StdinPipe()
stdin.Write(data)
// 忘记 Close(),命令可能永远等待

4. 不要重用 Cmd

// ✓ 正确 - 创建新 Cmd
cmd1 := exec.Command("echo", "hello")
cmd1.Run()

cmd2 := exec.Command("echo", "world")
cmd2.Run()

// ✗ 错误 - 重用 Cmd
cmd := exec.Command("echo", "hello")
cmd.Run()
cmd.Run() // 错误:Cmd 不能重用

5. 设置环境变量

// ✓ 正确 - 显式设置环境变量
cmd := exec.Command("myapp")
cmd.Env = []string{
    "PATH=/usr/bin",
    "HOME=/home/user",
}

// ✗ 错误 - 依赖系统环境
cmd := exec.Command("myapp")
// 可能因环境不同而行为不同

6. 使用 CombinedOutput 调试

// ✓ 调试时使用 CombinedOutput
output, err := cmd.CombinedOutput()
if err != nil {
    log.Printf("输出和错误:%s\n", string(output))
}

// 生产环境使用单独的输出
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()

六、与其他包配合

1. 与 context 包配合

import (
    "context"
    "os/exec"
    "time"
)

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, "sleep", "10")
err := cmd.Run()

2. 与 bytes 包配合

import (
    "bytes"
    "os/exec"
)

var stdout, stderr bytes.Buffer
cmd := exec.Command("myapp")
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()

3. 与 io 包配合

import (
    "io"
    "os/exec"
)

cmd := exec.Command("cat")
cmd.Stdin = strings.NewReader("hello")
output, err := cmd.Output()

4. 与 os 包配合

import (
    "os"
    "os/exec"
)

// 重定向到文件
file, _ := os.Create("output.txt")
cmd := exec.Command("ls")
cmd.Stdout = file
cmd.Run()
file.Close()

七、快速参考

变量总览

变量说明
ErrDot当前目录可执行文件错误
ErrNotFound未找到可执行文件
ErrWaitDelay等待 I/O 超时

类型总览

类型说明
Cmd外部命令
ErrorLookPath 错误
ExitError命令退出错误

函数总览

函数说明
Command创建命令
CommandContext创建带上下文的命令
LookPath查找可执行文件

Cmd 方法

方法说明
CombinedOutput运行并返回合并输出
Environ获取环境变量
Output运行并返回标准输出
Run运行并等待完成
Start启动命令
StderrPipe获取 stderr 管道
StdinPipe获取 stdin 管道
StdoutPipe获取 stdout 管道
String命令描述
Wait等待命令完成

常用命令模式

模式方法
运行并获取输出Output()
运行并获取所有输出CombinedOutput()
运行并等待Run()
后台运行Start() + Wait()
流式读取StdoutPipe() + Start() + Wait()
写入输入StdinPipe() + Start() + Wait()
带超时CommandContext() + Run()

八、注意事项

1. 不调用 shell

// ✓ 正确 - 直接传递参数
cmd := exec.Command("ls", "-l", "-a")

// ✗ 错误 - 不会展开 glob
cmd := exec.Command("ls", "*.go") // 不会展开

// 如果需要 shell 功能
cmd := exec.Command("sh", "-c", "ls *.go")

2. Cmd 不能重用

cmd := exec.Command("echo", "hello")
cmd.Run()
cmd.Run() // 错误!Cmd 不能重用

// 正确做法
exec.Command("echo", "hello").Run()
exec.Command("echo", "hello").Run()

3. Start 后必须调用 Wait

// ✓ 正确
cmd.Start()
// ... 做一些事情 ...
cmd.Wait()

// ✗ 错误 - 资源泄漏
cmd.Start()
// 忘记调用 Wait()

4. 管道使用时机

// ✓ 正确 - 使用管道时不调用 Run
stdout, _ := cmd.StdoutPipe()
cmd.Start()
io.ReadAll(stdout)
cmd.Wait()

// ✗ 错误 - 管道与 Run 一起使用
stdout, _ := cmd.StdoutPipe()
cmd.Run() // 错误!

5. 安全性考虑

// ✓ 安全 - 参数分开传递
userInput := "hello"
cmd := exec.Command("echo", userInput)

// ✗ 危险 - 拼接命令
userInput := "hello; rm -rf /"
cmd := exec.Command("sh", "-c", "echo "+userInput)

6. ErrDot 处理

// ✓ 正确处理 ErrDot
path, err := exec.LookPath("myapp")
if errors.Is(err, exec.ErrDot) {
    // 可以选择忽略或处理
    err = nil
}
if err != nil {
    log.Fatal(err)
}

// ✗ 错误 - 直接比较
if err == exec.ErrDot { // 可能不工作
    // ...
}

7. 环境变量继承

// 默认继承父进程环境
cmd := exec.Command("env")
// cmd.Env 为 nil,继承所有环境变量

// 显式设置环境
cmd.Env = []string{"PATH=/usr/bin", "HOME=/home/user"}
// 不继承任何环境变量

8. Windows 特殊性

// Windows 上,进程接收整个命令行作为单个字符串
// Command 会自动引用和转义参数
cmd := exec.Command("cmd", "/c", "echo", "hello world")
// 正确传递为:cmd /c "echo hello world"

最后更新: 2026-04-05
Go 版本: Go 1.0+(Go 1.19+ 增强安全性)
包文档: https://pkg.go.dev/os/exec
相关包: os, context, io, bytes
安全参考: https://go.dev/blog/path-security