全文转载自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
对象有两个字段Stdout
和Stderr
,类型皆为io.Writer
。我们可以将任意实现了io.Writer
接口的类型实例赋给这两个字段,继而实现标准输出和标准错误的重定向。
io.Writer
接口在 Go 标准库和第三方库中随处可见,例如*os.File
、*bytes.Buffer
、net.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)
}
}
将命令结果输出到多个地方
有时,我们希望能输出到文件和网络,同时保存到内存对象。使用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