go语言执行系统命令 - 标准库os/exec详解

exec包

对os.StartProcess的包装,方便重新映射标准输入输出,连接io到管道等。
exec包不调用系统shell,并且不支持shell通配符,或其他的扩展,管道,重定向等。如果需要这些功能,直接调用shell就可以,注意避免危险的输入,或者使用path/filepath包中的glob函数。如果需要扩展环境变量,使用os包的ExpandEnv
以下示例都是用Unix系统,可能无法在windows上运行。

func LookPath

func LookPath(file string) (string, error)
在环境变量搜索可执行文件,如果文件包含斜杠则直接尝试,不搜索。结果可能是绝对路径或相对于当前目录的相对路径。

package main

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

func main() {
	path, err := exec.LookPath("ls")
	if err != nil {
		log.Fatal("ls not found")
	}
	fmt.Printf("ls is available at %s\n", path)
}

结果:ls is available at /bin/ls

type cmd

type Cmd struct {
    // 要运行的命令路径,如/bin/ls
    Path string
    // 命令参数
    Args []string
    // 环境变量,键值对形式
    // 如果键重复,则最后一个生效
    // 如果没有设置Env,则使用当前进程的环境变量
    Env []string
    // 指定工作目录,如果为空则为当前目录
    Dir string
    // 指定标准输入
    // 如果为空则从os.DevNull读取
    // 如果是*os.File,则读取该文件
    // 默认情况,会有一个单独的goroutine从标准输入读取数据并通过管道传递给cmd。
    // Wait不会停止,知道goroutine停止复制,到达标准输入结束(EOF或读取错误)
    Stdin io.Reader
    // 指定标准输出和标准输入
    // 如果为空,Run的时候连接到os.DevNull
    // 如果是*os.File,则连接到该文件
    Stdout io.Writer
    Stderr io.Writer
    // ExtraFiles specifies additional open files to be inherited by the
    // new process. It does not include standard input, standard output, or
    // standard error. If non-nil, entry i becomes file descriptor 3+i.
    // Windows不支持
    ExtraFiles []*os.File
    // 可选的特定于操作系统的属性SysProcAttr holds optional, operating system-specific attributes.
    // Run把它作为os.ProcAttr的Sys字段传递给os.StartProcess
    SysProcAttr *syscall.SysProcAttr
    // 进程启动后的*os.Process对象
    Process *os.Process
    // 包含已退出的进程信息,可在调用Wait或者Run后获得
    ProcessState *os.ProcessState
}

创建

func Command

func Command(name string, arg ...string) *Cmd
仅设置cmd的path和args
如果name不包含路径分隔符,则调用LookPath查找完整路径
arg不应包含命令本身
设置命令执行时的环境变量

package main

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

func main() {
	cmd := exec.Command("ls", "-l")
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Env = append(os.Environ(),
		"FOO=duplicate_value", // 重复被忽略
		"FOO=actual_value",    // 实际被使用
	)
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Out: %q\n", out.String())
}
func CommandContext

func CommandContext(ctx context.Context, name string, arg ...string) *Cmd
包含上下文的*Cmd,如果上下文在命令完成之前完成,则提供的上下文通过os.Process.Kill终止进程
常用于为命令设置超时

package main

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

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()
	cmd := exec.CommandContext(ctx, "sleep", "5")

	if err := cmd.Run(); err != nil {
		fmt.Println(cmd.ProcessState)
	}
}

执行结果:signal: killed

方法

func (*Cmd) CombinedOutput

func (c *Cmd) CombinedOutput() ([]byte, error)
运行命令并返回组合到一起的标准输出和标准错误

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

执行结果:stdout stderr
命令解释:echo stdout;输出stdout到标准输出;1>&2重定向标准输出到标准错误;输出stderr到标准错误

func (*Cmd) Output

func (c *Cmd) Output() ([]byte, error)
运行命令并返回标准输出

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

命令运行成功,err为空;
命令运行失败,返回退出码为1
上述命令修改为exec.Command("date", "-h").Output(),则返回exit status 1

func (*Cmd) Run

func (c *Cmd) Run() error
运行命令,并等待,返回是否成功

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
启动执行命令,但不等待,如果启动成功返回,会设置c.Process字段
一旦命令接触,Wait方法将返回退出代码并释放资源,也就是通过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 (*Cmd) String

func (c *Cmd) String() string
返回人类可读的C描述,仅用于输出,不适合作为shell输入

func (*Cmd) Wait

func (c *Cmd) Wait() error
等待命令退出,等待所有标准输入输出错误复制完成,必须通过start启动

示例

读取输出,一次性/缓冲区按行

stdout, err := cmd.StdoutPipe()
//读取所有输出
bytes, err := ioutil.ReadAll(stdout)
if err != nil {
	fmt.Println("ReadAll Stdout:", err.Error())
	return
}
//使用带缓冲的读取器
outputBuf := bufio.NewReader(stdout)
for {
	//一次获取一行,_ 获取当前行是否被读完
	output, _, err := outputBuf.ReadLine()
	if err != nil {
		// 判断是否到文件的结尾了否则出错
		if err.Error() != "EOF" {
			fmt.Printf("Error :%s\n", err)
		}
		return
	}
	fmt.Printf("%s\n", string(output))
}

简单的交互shell

package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	for {
		fmt.Print("> ")
		// Read the keyboad input.
		input, err := reader.ReadString('\n')
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
		}

		// Handle the execution of the input.
		if err = execInput(input); err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
	}
}

func execInput(input string) error {
	// Remove the newline character.
	input = strings.TrimSuffix(input, "\n")

	// Prepare the command to execute.
	cmd := exec.Command(input)

	// Set the correct output device.
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout

	// Execute the command and return the error.
	return cmd.Run()
}

命令组合,管道连接命令输入输出

package main

import (
	"os"
	"os/exec"
)

func main() {
	c1 := exec.Command("grep", "Accepted", "/var/log/auth.log")
	c2 := exec.Command("wc", "-l")
	c2.Stdin, _ = c1.StdoutPipe()
	c2.Stdout = os.Stdout
	_ = c2.Start()
	_ = c1.Run()
	_ = c2.Wait()
}

参考

Golang 调用 Linux 命令
Package exec
Golang - Execute command
[译] 使用 Go 语言编写一个简单的 SHELL

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苏打呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值