最近在做一个项目的时候,需要使用golang来调用操作系统中的命令行,来执行shell命令或者直接调用第三方程序,这其中自然就用到了golang自带的exec.Command.
但是如果直接使用原生exec.Command会造成大量的重复代码,网上搜了一圈又没有找到对exec.Command相应的封装包,索性自己封装了一个,取名为gocommand.目前支持Linux和Windows,欢迎各位大神在github上提交代码补充其他平台的实现.
下面介绍一下gocommand库的实现思路:
package gocommand
// 命令行接口
type Commander interface {
// 执行命令行并返回结果
// args: 命令行参数
// return: 进程的pid, 命令行结果, 错误消息
Exec(args ...string) (int, string, error)
// 异步执行命令行并通过channel返回结果
// stdout: chan结果
// args: 命令行参数
// return: 进程的pid
// exception: 协程内的命令行发生错误时,会panic异常
ExecAsync(stdout chan string, args ...string) int
// 执行命令行(忽略返回值)
// args: 命令行参数
// return: 错误消息
ExecIgnoreResult(args ...string) error
}
gocommand目前的命令行执行函数都是源于Commander接口,目前该接口定义了3个函数,分别是:执行命令行病返回结果;异步执行命令行并得到结果;执行命令行并忽略结果.
package gocommand
import (
"runtime"
)
// Command的初始化函数
func NewCommand() Commander {
var cmd Commander
switch runtime.GOOS {
case "linux":
cmd = NewLinuxCommand()
case "windows":
cmd = NewWindowsCommand()
default:
cmd = NewLinuxCommand()
}
return cmd
}
创建一个Command的实现,并根据当前的操作系统,返回对应的实现函数,目前只实现了Linux和Windows,(Mac留给各位大神(土豪)了),其中LinuxCommand的代码实现如下:
package gocommand
import (
"io/ioutil"
"os"
"os/exec"
"syscall"
)
// LinuxCommand结构体
type LinuxCommand struct {
}
// LinuxCommand的初始化函数
func NewLinuxCommand() *LinuxCommand {
return &LinuxCommand{}
}
// 执行命令行并返回结果
// args: 命令行参数
// return: 进程的pid, 命令行结果, 错误消息
func (lc *LinuxCommand) Exec(args ...string) (int, string, error) {
args = append([]string{"-c"}, args...)
cmd := exec.Command(os.Getenv("SHELL"), args...)
cmd.SysProcAttr = &syscall.SysProcAttr{}
outpip, err := cmd.StdoutPipe()
defer outpip.Close()
if err != nil {
return 0, "", err
}
err = cmd.Start()
if err != nil {
return 0, "", err
}
out, err := ioutil.ReadAll(outpip)
if err != nil {
return 0, "", err
}
return cmd.Process.Pid, string(out), nil
}
// 异步执行命令行并通过channel返回结果
// stdout: chan结果
// args: 命令行参数
// return: 进程的pid
// exception: 协程内的命令行发生错误时,会panic异常
func (lc *LinuxCommand) ExecAsync(stdout chan string, args ...string) int {
var pidChan = make(chan int, 1)
go func() {
args = append([]string{"-c"}, args...)
cmd := exec.Command(os.Getenv("SHELL"), args...)
cmd.SysProcAttr = &syscall.SysProcAttr{}
outpip, err := cmd.StdoutPipe()
defer outpip.Close()
if err != nil {
panic(err)
}
err = cmd.Start()
if err != nil {
panic(err)
}
pidChan <- cmd.Process.Pid
out, err := ioutil.ReadAll(outpip)
if err != nil {
panic(err)
}
stdout <- string(out)
}()
return <-pidChan
}
// 执行命令行(忽略返回值)
// args: 命令行参数
// return: 错误消息
func (lc *LinuxCommand) ExecIgnoreResult(args ...string) error {
args = append([]string{"-c"}, args...)
cmd := exec.Command(os.Getenv("SHELL"), args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &syscall.SysProcAttr{}
err := cmd.Run()
return err
}
Exec函数会在执行命令行后阻塞,直到得到命令的执行结果;ExecAsync函数在内部使用了协程来执行命令行,并通过参数中的chan变量把结果传递出去;ExecNoWait会无阻赛地执行命令行.Windows平台上的实现类似,只是Shell命令换成了cmd.
使用示例如下:
package main
import (
"log"
"github.com/lizongshen/gocommand"
)
func main() {
_, out, err := gocommand.NewCommand().Exec("ls /")
if err != nil {
log.Panic(err)
}
log.Println(out)
}
代码的单元测试情况:
[lizongshen@localhost gocommand]$ go test
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
PASS
ok gocommand 0.007s
github开源地址:
https://github.com/lizongshen/gocommand.