其实在之前的项目中,就有运用到调用linux中的命令,例如,借鉴falcon,里面agent更新文件是调用的wget去获取server端目录下的agenttar包。

基础任务调用

按道理来讲,官方库os/exec已经将这个操作封装得很好了,我们所需要做的,也就是调用API进行操作即可,这里插入一些关于API调用的流程问题。

这是一个基础的调用命令的使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
var (
cmd *exec.Cmd
output []byte
err error
)

// 生成Cmd
cmd = exec.Command("/bin/bash", "-c", "echo 1;echo2;")

// 执行了命令, 捕获了子进程的输出( pipe )
if output, err = cmd.CombinedOutput(); err != nil {
fmt.Println(err)
return
}

// 打印子进程的输出
fmt.Println(string(output))
}

其实,它借用到了Linux的进程的相关函数操作,如图:

任务执行原理

也就是说,golang在调用fork,其与子进程通过管道进行了相应的连接,这是linux的基础知识——进程间通信(感觉回到了嵌入式)。

也就是相当于go写入一个command交给子进程去运行,然后结果被子进程输入管道,go会去读取。

系统调用阶段会使用到:

1
2
3
4
pipe():创建2个文件描述符,fd[0]可读,fd[1]可写
fork():创建子进程,fd[1]被继承到子进程
dup2():重定向子进程stdout/stderr到fd[1]
exec():在当前进程内,加载并执行二进制程序

这种调用其实是非常占用空间的,子进程会共享父进程的代码空间,数据空间则互相独立,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同。也就是这个的消耗资源也就是比协程消耗得更多的,基本相当于线程对协程而言了。

强制结束任务

在实际使用中,我们需要控制任务的生命周期,有时候,我们并不需要任务继续执行下去了,这时候,我们就要使点手段来结束掉任务。

可以使用如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main

import (
"context"
"fmt"
"os/exec"
"time"
)

type result struct {
err error
output []byte
}

func main() {
// 执行1个cmd, 让它在一个协程里去执行, 让它执行2秒: sleep 2; echo hello;
// 1秒的时候, 我们杀死cmd
var (
ctx context.Context
cancelFunc context.CancelFunc
cmd *exec.Cmd
resultChan chan *result
res *result
)

// 创建了一个结果队列
resultChan = make(chan *result, 1000)

// context: chan byte
// cancelFunc: close(chan byte)

ctx, cancelFunc = context.WithCancel(context.TODO())

go func() {
var (
output []byte
err error
)
//cmd = exec.CommandContext(ctx, "/bin/bash", "-c", "sleep 2;echo hello;")
cmd = exec.CommandContext(ctx, `D:\Git\bin\bash.exe`, "-c", "sleep 2;echo hello;")

// 执行任务, 捕获输出
output, err = cmd.CombinedOutput()

// 把任务输出结果, 传给main协程
resultChan <- &result{
err: err,
output: output,
}
}()

// 继续往下走
time.Sleep(1 * time.Second)

// 取消上下文
cancelFunc()

// 在main协程里, 等待子协程的退出,并打印任务执行结果
res = <-resultChan

// 打印任务执行结果
fmt.Println(res.err, string(res.output))
}

注意:若你在windows下运行该命令,运行得到的结果是(go1.11,go1.12,这俩版本linux不会出现这种情况):

1
exit status 1  hello

也就是说程序虽然被kill掉了,但命令还是执行了。

博客:golang的CommandContext取消不退出问题,介绍的是在go1.10中,Linux下运行命令无法终止的情况(go1.11已被修复),但博客中有提到一个issue,该地址里有介绍到windows相关,我未深究这个问题的解决,毕竟我没在windows下的使用环境(大概都是运行在linux下的吧)。

这里还是提一下这个问题,望了解。