最近在做一個項目的時候,需要使用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.
