go 外部程序
一、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 code
,stderr
,stdout
等信息,操作系统就会给父进程发送 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/*"
来间接启动程序