go 外部程序

admin
admin 2023年09月25日
  • 在其它设备中阅读本文章

一、syscall 包的 Exec 函数

func Exec(argv0 string, argv []string, envv []string) (err error)

执行指定的程序,但是并不创建新的进程,只在当前进程空间内执行,即替换当前进程的执行内容,重用同一个进程号 PID

  • argv0:可执行文件的路径,绝对路径(/path/executable)或相对路径(./repath/executable), 注意:不会自动从 PATH 下面去搜索可执行文件
  • argv:参数列表,参数的第一个是程序名称(其实可以填任意值,指定替换进程后的名称),后面才是正常的参数列表
  • envv:环境变量列表,如果没有传,是不会自动继承当前进程的环境变量的

执行此函数后,当前进程会被替换,所以此函数调用后面的代码不会被执行,其他协程也会被关闭

package main

import "fmt"
import "syscall"

func main() {
    binary := "/bin/sleep"

    args := []string{"sleep", "3"}

    if err := syscall.Exec(binary, args, []string{}); err != nil {
        panic(err)
    }
    fmt.Println("11111")
}

执行上面这个程序将会:卡顿 3 秒后结束,同时并不会输出字符串“11111”

二、os 包的 StartProcess 函数

func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error)

使用 fork&exec 的方式产生一个子进程来运行指定的程序,是父子进程关系。子进程是以守护进程的方式运行

  • name:可执行文件的路径,绝对路径或相对路径
  • argv:参数列表,参数的第一个是程序名称,后面才是正常的参数列表
  • attr:进程描述对象,包括工作目录环境变量共享文件等等

可以通过返回的 Process 对象控制子进程:

  • Kill:杀死进程
  • Release:释放进程资源
  • Signal:向进程发送信号
  • Wait:等待子进程退出,回收子进程,防止出现僵尸进程

当前程序调起的进程视为子进程,如果父进程先于子进程结束,那么子进程成为孤儿进程,被初始化进程接管(pid=1),如果子进程先于父进程结束,而父进程并没有处理子进程结束的信号,那么子进程将死不瞑目,成为僵尸进程,无法被 kill 掉,除非父进程结束了(僵尸进程会随之消失);所以在某些场景下,是需要回收子进程的,也就是调用 Wait 方法。它的作用是:如果子进程结束,那么子进程会有exit codestderrstdout等信息,操作系统就会给父进程发送 SIGCHLD 信号,父进程需要阻塞等待,并处理这个信号

package main

import "fmt"
import "os"

func main() {
    binary := "/bin/sleep"

    args := []string{"sleep", "3"}

    process, err := os.StartProcess(binary, args, &os.ProcAttr{})
    if err != nil {
        panic(err)
    }
    fmt.Println("11111")
    if _, err = process.Wait(); err != nil {
        panic(err)
    }
    fmt.Println("22222")
}

执行上面这个程序将会:先输出字符串“11111”,卡顿 3 秒后输出字符串“22222”

三、os/exec 包的 Command 函数

func Command(name string, arg ...string) *Cmd

创建一个的命令对象,是os.StartProcess的高级别封装,程序并没有运行,需要手动启动它

  • name:可执行文件的路径,绝对路径或相对路径
  • arg:参数列表

只会设置 Cmd 对象的 Path 和 Args 字段,创建成功后可以设置对象字段来自定义命令,可以调用其方法来控制程序状态:

  • Run:运行程序直到结束
  • Output:运行程序直到结束,同时返回其标准输出
  • Start:启动程序,不会等待程序结束
  • Wait:等待程序执行结束,必须和 Start 一起使用

除了调用函数创建Cmd对象,也可以手动构建

package main

import "fmt"
import "os/exec"

func main() {
    cmd := exec.Command("sleep", "3")

    if err := cmd.Run(); err != nil {
        panic(err)
    }
    fmt.Println("11111")
}

执行上面这个程序将会:卡顿 3 秒,最后输出字符串“11111”

启动程序的参数带有*号时,会认为是字符串,失去通配符功能,可以使用类似命令/bin/bash -c "ls /var/*"来间接启动程序