最近在做一個項目的時候,需要使用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: 錯誤訊息ExecNoWait(args ...string) error}
gocommand目前的命令列執行函數都是源於Commander介面,目前該介面定義了3個函數,分別是:執行命令列病返回結果;非同步執行命令列並得到結果;執行命令列並忽略結果.
package gocommandimport ("runtime")// Command的初始化函數func NewCommand() Commander {var cmd Commanderswitch runtime.GOOS {case "linux":cmd = NewLinuxCommand()case "windows":cmd = NewWindowsCommand()default:cmd = NewLinuxCommand()}return cmd}
建立一個Command的實現,並根據當前的作業系統,返回對應的實現函數,目前只實現了Linux和Windows,(Mac留給各位大神(土豪)了),其中LinuxCommand的代碼實現如下:
package gocommandimport ("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()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()if err != nil {panic(err)}err = cmd.Start()if err != nil {panic(err)}pidChan <- cmd.Process.Pidout, err := ioutil.ReadAll(outpip)if err != nil {panic(err)}stdout <- string(out)}()return <-pidChan}// 執行命令列(忽略傳回值)// args: 命令列參數// return: 錯誤訊息func (lc *LinuxCommand) ExecNoWait(args ...string) error {args = append([]string{"-c"}, args...)cmd := exec.Command(os.Getenv("SHELL"), args...)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrcmd.SysProcAttr = &syscall.SysProcAttr{}err := cmd.Run()return err}
Exec函數會在執行命令列後阻塞,直到得到命令的執行結果;ExecAsync函數在內部使用了協程來執行命令列,並通過參數中的chan變數把結果傳遞出去;ExecNoWait會無阻賽地執行命令列.Windows平台上的實作類別似,只是Shell命令換成了cmd.
使用樣本如下:
package mainimport ("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 testbin dev home lib64mnt proc run srv tmp varboot etc lib mediaopt root sbin sys usrPASSok gocommand0.007s
github開源地址:https://github.com/lizongshen/gocommand.