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())
}
}

这里得到的中文输出有乱码

这个我查了一下一般都是说是设置一下控制台输出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出现中文乱码的解决方法