go 如何调用shell命令

全文转载自Go每日一库之调用外部命令的几种姿势

import (
    "log"
    "os/exec"
)

func main() {
    cmd := exec.Command("cal")
    err := cmd.Run()
    if err != nil {
        log.Fatalf("cmd.Run() failed: %v\n", err)
    }
}

实际运行程序,你会发现什么也没有发生,哈哈。事实上,使用os/exec执行命令,标准输出和标准错误默认会被丢弃。

那么怎么显示输入和输出呢?
exec.Cmd对象有两个字段StdoutStderr,类型皆为io.Writer。我们可以将任意实现了io.Writer接口的类型实例赋给这两个字段,继而实现标准输出和标准错误的重定向。
io.Writer接口在 Go 标准库和第三方库中随处可见,例如*os.File*bytes.Buffernet.Conn。所以我们可以将命令的输出重定向到文件、内存缓存甚至发送到网络中。

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

func main() {
    cmd := exec.Command("cal")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    err := cmd.Run()
    if err != nil {
        log.Fatalf("cmd.Run() failed: %v\n", err)
    }
}
image.png
将命令结果输出到多个地方

有时,我们希望能输出到文件和网络,同时保存到内存对象。使用go提供的io.MultiWriter可以很容易实现这个需求。io.MultiWriter很方便地将多个io.Writer转为一个io.Writer

func cal(w http.ResponseWriter, r *http.Request) {
    year := r.URL.Query().Get("year")
    month := r.URL.Query().Get("month")

    f, _ := os.OpenFile("out.txt", os.O_CREATE|os.O_WRONLY, os.ModePerm)
    buf := bytes.NewBuffer(nil)
    mw := io.MultiWriter(w, f, buf)

    cmd := exec.Command("cal", month, year)
    cmd.Stdout = mw
    cmd.Stderr = mw

    err := cmd.Run()
    if err != nil {
      log.Fatalf("cmd.Run() failed: %v\n", err)
    }

    fmt.Println(buf.String())
}
保存输入和输出

在日常编码中,我们通常需要运行命令,返回输出。exec.Cmd对象提供了一个便捷方法:CombinedOutput()。该方法运行命令,将输出内容以一个字节切片返回便于后续处理。所以,上面获取输出的程序可以简化为:

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

func main() {
    cmd := exec.Command("cal")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    err := cmd.Run()
    if err != nil {
        log.Fatalf("cmd.Run() failed: %v\n", err)
    }
}

CombinedOutput()方法的实现很简单,先将标准输出和标准错误重定向到*bytes.Buffer对象,然后运行程序,最后返回该对象中的字节切片:

func (c *Cmd) CombinedOutput() ([]byte, error) {
    if c.Stdout != nil {
      return nil, errors.New("exec: Stdout already set")
    }
    if c.Stderr != nil {
      return nil, errors.New("exec: Stderr already set")
    }
    var b bytes.Buffer
    c.Stdout = &b
    c.Stderr = &b
    err := c.Run()
    return b.Bytes(), err
}
分别获取输入和输出
func main() {
    cmd := exec.Command("cal", "15", "2012")
    var stdout, stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr
    err := cmd.Run()
    if err != nil {
        log.Fatalf("cmd.Run() failed: %v\n", err)
    }

    fmt.Printf("output:\n%s\nerror:\n%s\n", stdout.String(), stderr.String())
}
从标准输入

exec.Cmd对象有一个类型为io.Reader的字段Stdin。命令运行时会从这个io.Reader读取输入。
先来看一个最简单的例子:

func main() {
    cmd := exec.Command("cat")
    cmd.Stdin = bytes.NewBufferString("hello\nworld")
    cmd.Stdout = os.Stdout
    err := cmd.Run()
    if err != nil {
        log.Fatalf("cmd.Run() failed: %v\n", err)
    }
}

如果不带参数运行cat命令,则进入交互模式,cat按行读取输入,并且原样发送到输出。

查看命令是否存在

一般在运行命令之前,我们通过希望能检查要运行的命令是否存在,如果存在则直接运行,否则提示用户安装此命令。os/exec包提供了函数LookPath可以获取命令所在目录,如果命令不存在,则返回一个error。

func main() {
    path, err := exec.LookPath("ls")
    if err != nil {
      fmt.Printf("no cmd ls: %v\n", err)
    } else {
      fmt.Printf("find ls in path:%s\n", path)
    }
}
环境变量

TODO


参考资料
1、https://mp.weixin.qq.com/s/dG58Dtml2FfU_aANJCgkfQ

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容