博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
golang调用外部程序,创建进程,守护进程,shell命令
阅读量:3989 次
发布时间:2019-05-24

本文共 6660 字,大约阅读时间需要 22 分钟。

一、syscall.Exec()

函数原型

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

函数说明

Exec invokes the execve(2) system call.

此方法会将在当前进程空间里,用新的程序覆盖掉当前程序,并执行新的程序,它们依然在同一个进程里,只是进程的内容发生了变化。

main11.go

package mainimport (	"fmt"	"syscall")func main() {
// 打印当前进程号 fmt.Println(syscall.Getpid())}

go build main11.go 得到 main11 可执行文件

main10.go

package mainimport (	"fmt"	"os"	"os/exec"	"syscall")func main() {
fmt.Println("11111") fmt.Println(syscall.Getpid()) f3() // 后面的依然属于当前进程的内容,不会被执行到 fmt.Println("22222")}func f3() {
// LookPath获得绝对地址;参数可以是绝对路径或相对路径。 // binary 应该是一个可执行文件,对于全局的命令,也需要找到其具体的命令文件的位置。 binary, lookErr := exec.LookPath("./main11") if lookErr != nil {
panic(lookErr) } // 不需要参数 args := []string{
""} // 使用当前进程的环境变量 env := os.Environ() // 执行,并进入新的程序 execErr := syscall.Exec(binary, args, env) if execErr != nil {
panic(execErr) } // 后面的依然属于当前进程的内容,不会被执行到}

go run main10.go

输出

11111
4758
4758

可见它们是在同一个进程中执行的。

二、os.StartProcess()

函数原型

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

函数说明

StartProcess starts a new process with the program, arguments and attributes specified by name, argv and attr. The argv slice will become os.Args in the new process, so it normally starts with the program name.

If the calling goroutine has locked the operating system thread with runtime.LockOSThread and modified any inheritable OS-level thread state (for example, Linux or Plan 9 name spaces), the new process will inherit the caller’s thread state.

StartProcess is a low-level interface. The os/exec package provides higher-level interfaces.

If there is an error, it will be of type *PathError.

此方法将使用 fork & exec 的方式产生一个子进程来运行新的程序,是父子进程关系。

子进程是以守护进程的方式运行。

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

func (*Process) Kill 杀死进程
func (*Process) Release 释放进程资源
func (*Process) Signal 向进程发送信号
func (*Process) Wait 等待子进程退出,回收子进程

main11.go

package mainimport (	"fmt"	"os"	"strconv")func main() {
f, err := os.OpenFile("note.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm) if err != nil {
panic(err) } f.WriteString("parent: " + strconv.Itoa(os.Getppid())) f.WriteString("\n") f.WriteString("son: " + strconv.Itoa(os.Getpid())) fmt.Println() f.Close()}

go build main11.go 得到 main11 可执行文件。

main10.go

package mainimport (	"fmt"	"os"	"os/exec"	"syscall"	"time")func main() {
fmt.Println("11111") fmt.Println(syscall.Getpid()) f4() fmt.Println("22222") time.Sleep(time.Second * 10)}func f4() {
binary, lookErr := exec.LookPath("./main11") if lookErr != nil {
panic(lookErr) } args := []string{
""} pro, err := os.StartProcess(binary, args, &os.ProcAttr{
}) if err != nil {
fmt.Println(err) return } fmt.Println("son ", pro.Pid)}

打印信息

11111
5989
son 5994
22222

日志信息

parent: 5989
son: 5994

三、os/exec 包

此包是对 StartProcess 的封装,并提供更高级的功能,推荐使用此方法。

并且可以通过 cmd.Process 对象控制子进程。

依然使用上面的 main11 可执行程序。

main10.go

package mainimport (	"fmt"	"os/exec"	"syscall"	"time")func main() {
fmt.Println("11111") fmt.Println(syscall.Getpid()) f2() fmt.Println("22222") time.Sleep(time.Second * 10)}func f2() {
cmd := exec.Command("./main11") err := cmd.Start() if err != nil {
panic(err) } fmt.Println("son ", cmd.Process.Pid)}

os/exec 文档参考 https://golang.google.cn/pkg/os/exec/

四、go程序执行shell命令

首先要理解前面讲的三种方式的效果是,golang程序在服务器上,启动新的进程来运行别的程序,形如 [program arg1 arg2 ...]

但是如果我们想带上额外的功能,比如管道过滤 ps -ef | grep xxx ,输出重定向 ls -l >> note.log 等操作,使用go程序实现就比较麻烦。

而我们在 xshell 等类似工具上运行的命令,本质上是 ssh 终端,我们首先要连接上 sshd 服务,然后被分配一个 ssh 子进程来处理我们后面发出的命令,比如你在终端输入一条命令 xxxxx,实际上在服务器上是 sh -c xxxxx,基于此,我们可以调用 sh 程序,传入一个参数 -c,参数值就是全部的命令字符串。

func main(){
c := "ls -l >> note.log" cmd := exec.Command("sh", "-c", c) err := cmd.Run() if err != nil {
fmt.Println(err) }}

exit status 127 表示命令不存在

ll 不是命令,是 ls -l 的别名,你发出的ll命令,终端会将其转换为ls -l命令,如果使用go程序发送ll命令,就会报127错误码。所以,并不是所有在终端上能运行的命令,使用go程序都能调用成功,极少部分会失败,要具体分析。

五、关于进程参数 args

关于命令 ls -l >> note.log,看下面两个例子

func f2() {
c := "-c ls -l >> note.log" cmd := exec.Command("sh", strings.Split(c, " ")...) fmt.Println(cmd.String()) // /usr/bin/sh -c ls -l >> note.log err := cmd.Run() if err != nil {
fmt.Println(err) }}func f1() {
c := "ls -l >> note.log" cmd := exec.Command("sh", "-c", c) fmt.Println(cmd.String()) // /usr/bin/sh -c ls -l >> note.log err := cmd.Run() if err != nil {
fmt.Println(err) }}

虽然最终打印的命令字符串是一样的,都是 /usr/bin/sh -c ls -l >> note.log,但是 f1 成功, f2 失败,因为在这个场景下 sh 只有一个参数 -c ,而 f2 的写法意味着向 sh 程序传入多个参数,它会将多余的舍弃,就变成了 sh -c ls

所以args的写法还是要规范,比如将 f2 缓存 f3。

func f3() {
c := []string{
"-c", "ls -l >> note.log"} cmd := exec.Command("sh", c...) fmt.Println(cmd.String()) // /usr/bin/sh -c ls -l >> note.log err := cmd.Run() if err != nil {
fmt.Println(err) }}

六、cmd 对象注释

type Cmd struct {
Path string   // 运行命令的路径,绝对路径或者相对路径 Args []string  // 命令参数 Env []string // 进程环境,如果环境为空,则使用当前进程的环境 Dir string   // 指定command的工作目录,如果dir为空,则comman在调用进程所在当前目录中运行 Stdin io.Reader // 标准输入,如果stdin是nil的话,进程从null device中读取(os.DevNull),stdin也可以时一个 // 文件,否则的话则在运行过程中再开一个goroutine去/读取标准输入 Stdout io.Writer // 标准输出 Stderr io.Writer // 错误输出,如果这两个(Stdout和Stderr)为空的话,则command运行时将响应的文件描述符连接到 // os.DevNull ExtraFiles []*os.File // 打开的文件描述符切片,可为进程添加fd,比如 socket SysProcAttr *syscall.SysProcAttr // 系统的进程属性 Process *os.Process // Process是底层进程,只启动一次,就是 os.StartProcess 返回的进程对象 ProcessState *os.ProcessState  // ProcessState包含一个退出进程的信息,当进程调用Wait或者Run时便会产生该信息. }

Output() 和 CombinedOutput() 不能够同时使用,因为command的标准输出只能有一个,同时使用的话便会定义了两个,便会报错。

func (c *Cmd) Run() error

开始指定命令并且等待他执行结束,如果命令能够成功执行完毕,则返回nil,否则的话边会产生错误。

func (c *Cmd) Start() error 

使某个命令开始执行,但是并不等到他执行结束,这点和Run命令有区别.然后使用Wait方法等待命令执行完毕并且释放响应的资源。

一个command只能使用Start()或者Run()中的一个启动命令,不能两个同时使用。

func (c *Cmd) StderrPipe() (io.ReadCloser, error)  

StderrPipe返回一个pipe,这个管道连接到command的标准错误,当command命令退出时,Wait将关闭这些pipe。

func (c *Cmd) StdinPipe() (io.WriteCloser, error) 

StdinPipe返回一个连接到command标准输入的管道pipe。

func (c *Cmd) StdoutPipe() (io.ReadCloser, error)

StdoutPipe返回一个连接到command标准输出的管道pipe。

func (c *Cmd) Wait() error 

Wait等待command退出,他必须和Start一起使用,如果命令能够顺利执行完并顺利退出则返回nil,否则的话便会返回error,其中Wait会是放掉所有与cmd命令相关的资源。

守护进程

由以上可知,golang程序开启守护进程就很容易了。

var daemonFlag = "-d"args := os.Argsdaemon := falsefor k, v := range args {
if v == daemonFlag {
daemon = true args[k] = "" }}if daemon {
util.Daemonize(args...)} else {
// 运行你的程序 ......}func Daemonize(args ...string) {
var arg []string if len(args) > 1 {
arg = args[1:] } cmd := exec.Command(args[0], arg...) cmd.Env = os.Environ() cmd.Start()}

只需要在加上 -d 即可实现后台运行。

例如:./voteapi serve -d

转载地址:http://xcaui.baihongyu.com/

你可能感兴趣的文章
[LeetCode By Python]171. Excel Sheet Column Number
查看>>
[LeetCode By Python]172. Factorial Trailing Zeroes
查看>>
[LeetCode By MYSQL] Combine Two Tables
查看>>
Mac删除文件&文件夹
查看>>
python jieba分词模块的基本用法
查看>>
[CCF BY C++]2017.12 最小差值
查看>>
[CCF BY C++]2017-12 游戏
查看>>
《Fluent Python》第三章Dictionaries and Sets
查看>>
如何打开ipynb文件
查看>>
[Leetcode BY python ]190. Reverse Bits
查看>>
面试---刷牛客算法题
查看>>
Android下调用收发短信邮件等(转载)
查看>>
Android中电池信息(Battery information)的取得
查看>>
SVN客户端命令详解
查看>>
Android/Linux 内存监视
查看>>
Linux系统信息查看
查看>>
用find命令查找最近修改过的文件
查看>>
Android2.1消息应用(Messaging)源码学习笔记
查看>>
在android上运行native可执行程序
查看>>
Phone双模修改涉及文件列表
查看>>