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 | 外部命令 |
| Error | LookPath 错误 |
| 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