目录

go语言中执行命令的几种方式

go语言用来执行一切系统的命令相对python来说还是有点复杂的,执行命令是一个非常常见的需求,如调用一个系统命令,启一个exe等,这里分为几种情况,之后统一总结一下。

  • 只执行命令,不要输出结果
  • 执行命令并且要获取到输出结果
  • 阻塞和异步的执行

以下以ping www.baidu.com 为例依次执行一下各种命令,主要使用标准库中的os/exec

在执行命令的时候,我们主要使用的是os/exec包主的Cmd结构体方法,Cmd的结构体定义如下 Cmd结构体定义

主要的参数有

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Path string
// Args保管命令的参数,包括命令名作为第一个参数;如果为空切片或者nil,相当于无参数命令。
//
// 典型用法下,Path和Args都应被Command函数设定。
Args []string
// Env指定进程的环境,如为nil,则是在当前进程的环境下执行。
Stdin io.Reader
// Stdout和Stderr指定进程的标准输出和标准错误输出。
//
// 如果任一个为nil,Run方法会将对应的文件描述符关联到空设备(os.DevNull)
//
// 如果两个字段相同,同一时间最多有一个线程可以写入。
Stdout io.Writer
Stderr io.Writer

但是我们一般不直接构造Cmd结构体,而是通过exec.Command() 函数返回一个Cmd结构体指针 如 exec.Command(“ping”,“www.baidu.com”) ping为命令,“www.baidu.com” 为参数,在得到*Cmd以后再使用结构体方法Run,Start等方法来真正的执行命令。

只执行命令,不要输出结果

这里的输出结果只是表明命令执行了,但是它具体的输出我们不关心,在这种其实用的挺多的,我们只是想执行命令,在python里我们可以使用os.system() 函数来执行,当然这个是阻塞的执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
>>> import os
>>> r = os.system("ping www.baidu.com")

正在 Ping www.a.shifen.com [39.156.66.18] 具有 32 字节的数据:
来自 39.156.66.18 的回复: 字节=32 时间=6ms TTL=54
来自 39.156.66.18 的回复: 字节=32 时间=28ms TTL=54
来自 39.156.66.18 的回复: 字节=32 时间=6ms TTL=54
来自 39.156.66.18 的回复: 字节=32 时间=7ms TTL=54

39.156.66.18  Ping 统计信息:
    数据包: 已发送 = 4已接收 = 4丢失 = 0 (0% 丢失)
往返行程的估计时间(以毫秒为单位):
    最短 = 6ms最长 = 28ms平均 = 11ms
>>> r
0

这里的r只是获取了该命令的执行结果,是0表示没有错误,但是执行命令的输出如 正在 Ping www.a.shifen.com….. 我们并不关心

执行命令可以使用Run() 或者Start() 方法,Run是阻塞的执行,Start() 是非阻塞的执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
	"fmt"
	"os/exec"
)

func main() {
	command := exec.Command("ping","www.baidu.com")
	err := command.Run()
	if err != nil{
		fmt.Println(err.Error())
	}
}

程序什么也没有输出,但是停顿的一段时间后才退出。如果换成command.Start() 则程序运行起来以后马上就停止了。

如果想要获取到像python那种os.system的执行结果,其实这里的结果应该是ExitError,程序的退出码,应该怎么操作呢?这里exec包里有一个专门的结构体ExitError,使用它的一些方法可以获取到ExitCode,但是想要获取到ExitCode得到得到命令结束,也就是要阻塞的运行,上面使用Run() 方法可以阻塞等待执行结果,使用Start()方法以后,也可以使用Wait()方法来等待执行结束。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
	"fmt"
	"os/exec"
	"syscall"
)

func main() {
	command := exec.Command("ping","www.baidu.com")
	err := command.Start()
	if err != nil{
		fmt.Println(err.Error())
	}
	if err = command.Wait();err!=nil{
		fmt.Println(err.Error())
	}else{
		fmt.Println(command.ProcessState.Pid())
		fmt.Println(command.ProcessState.Sys().(syscall.WaitStatus).ExitCode)
	}
}

通过

1
command.ProcessState.Sys().(syscall.WaitStatus).ExitCode

来获取到命令执行的退出码

执行命令并且要获取到输出结果

这里的输出结果是命令行的标准输出或者错误输出,也就是stdout或者stderr,通过bytes.Buffer来存储

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
	"bytes"
	"fmt"
	"os/exec"
	"syscall"
)

func main() {
	command := exec.Command("ping","www.baidu.com")
	outinfo := bytes.Buffer{}
	command.Stdout = &outinfo
	err := command.Start()
	if err != nil{
		fmt.Println(err.Error())
	}
	if err = command.Wait();err!=nil{
		fmt.Println(err.Error())
	}else{
		fmt.Println(command.ProcessState.Pid())
		fmt.Println(command.ProcessState.Sys().(syscall.WaitStatus).ExitCode)
		fmt.Println(outinfo.String())
	}
}

这里得到的中文输出有乱码 /image/2019/go2.png

这个我查了一下一般都是说是设置一下控制台输出chcp或者使用

1
golang.org/x/text/encoding/simplifiedchinese 

这个包进行转换,我不想使用,这个以后找到方法再说吧。

命令行的输入

有时候进入命令行会等待用户的交互,如输入nslookup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"bytes"
	"fmt"
	"os/exec"
	"strings"
)

func main() {
	var outInfo bytes.Buffer
	cmd := exec.Command("nslookup")
	cmd.Stdin = strings.NewReader("set q=mx\npython.org\nexit\n")
	cmd.Stdout = &outInfo
	cmd.Run()
	fmt.Println(outInfo.String())
}

执行不在环境变量里的命令

像上面这个ping 命令,由于在windows 或者linux中,这个命令是在环境变量里,但是像windows中的copy 命令,它是不在环境变量里,正常情况下你可以在cmd中使用copy 命令,但是如果在go 语言中如果直接像上面那样使用是不行的。 例如使用上面的代码,替换一下copy 命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
	"bytes"
	"fmt"
	"os/exec"
)

func main() {
	cmd := exec.Command("copy","1.txt","2.txt")
	var output  bytes.Buffer
	cmd.Stdout = &output
	e :=cmd.Run()
	if e != nil{
		fmt.Println("run error :" + e.Error())
	}else{
		fmt.Println(output.String())
	}
}

得到的输出结果为

1
run error :exec: "copy": executable file not found in %PATH%

应该使用cmd", "/C" copy 命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
	cmd := exec.Command("cmd","/C","copy","1.txt","2.txt")
	var output  bytes.Buffer
	cmd.Stdout = &output
	e :=cmd.Run()
	if e != nil{
		fmt.Println("run error :" + e.Error())
	}else{
		fmt.Println(output.String())
	}
}

参考文章

golang exec 命令执行

Windows下,在CMD下执行Go出现中文乱码的解决方法

  • 文章标题: go语言中执行命令的几种方式
  • 本文作者: 杨彦星
  • 本文链接: https://www.yangyanxing.com/article/exec_command_in_go.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。