返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

exec

发布于 2024-10-12 19:15:53 字数 4075 浏览 0 评论 0 收藏 0

使用 os.Process 可以创建进程以执行其他程序,不过我们常用的是其封装版本。

cmd

创建 Cmd ,然后选择相关执行方法。

  • Start + Wait
  • Run = Start + Wait
  • Output = Run + stdout
  • CombinedOutput = Run + ( stdout + stderr )

基本输入输出,最直接的做法是 pipe 方法。

  • StdinPipe
  • StdoutPipe
  • StderrPipe

当然,也可以直接设置字段。

type Cmd struct {
    Stdin  io.Reader
    Stdout io.Writer
    Stderr io.Writer
}

需要注意, Wait 方法会清理进程资源。所以获取输出结果,须在此之前。

package main

import (
	"fmt"
	"io"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("ls", "-l", "/usr/local/go/src")
	out, _ := cmd.StdoutPipe()

	if err := cmd.Start(); err != nil {
		log.Fatalln(err)
	}

	b, _ := io.ReadAll(out)
	fmt.Println(string(b))

	cmd.Wait()
	fmt.Println(cmd.ProcessState)
}
func main() {
	out, _ := exec.Command("ls", "-l", "/usr/local/go/src").Output()
	fmt.Println(string(out))
}
func main() {
	cmd := exec.Command("ls", "-l", "/usr/local/go/src")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Fatalln(err)
	}

	fmt.Println(cmd.ProcessState)
}

僵尸进程

主进程须直接或间接调用 Wait 获取子进程的退出状态,否则会导致僵尸(zombie)进程。
除非主进程先终止,由系统 init 进程完成状态检查工作。

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	cmd := exec.Command("ls", "-lh", "/usr/local/go/bin")
	cmd.Start()

	fmt.Println(cmd.Process.Pid)
	fmt.Scanln()
}

/*

主进程退出前,可以用 ps aux 或 top 查看。
子进程 [ls] 状态为 Z,表示僵尸进程。

*/

传递信息

fork 调用不同,默认会执行 exec 操作,重置相关信息。

因此,要向子进程传递信息,须借助 EnvExtraFiles 属性。

子进程从 ExtraFiles 继承的文件描述符总是从 3 + i 开始。
如果有多个文件,可用 Env 传递列表。

package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strconv"
)

func parent() {
    
    // 通过 Args 区分父子进程。
	cmd := exec.Command(os.Args[0], "-child")
	cmd.Stdout = os.Stdout

	// 文件总数。
	files := []string{"main.go", "main_test.go"}
	cmd.Env = append(cmd.Env, fmt.Sprintf("count=%d", len(files)))

	for i := 0; i < len(files); i++ {
		name := files[i]

		// 不能提前关闭。
		file, _ := os.Open(name)
		defer file.Close()     

		// 文件名和文件描述。
		cmd.Env = append(cmd.Env, fmt.Sprintf("%d=%s", 3+i, name))
		cmd.ExtraFiles = append(cmd.ExtraFiles, file)
	}

	// 启动并等待子进程结束。
	if err := cmd.Run(); err != nil {
		log.Fatalln(err)
	}
}

func child() {

	// 获取文件总数。
	count, _ := strconv.Atoi(os.Getenv("count"))

	// 从 3+i 开始,读取文件名和内容。
	for i := 0; i < count; i++ {
		name := os.Getenv(strconv.Itoa(3 + i))

		file := os.NewFile(uintptr(3+i), name)
		defer file.Close()

		b, _ := io.ReadAll(file)
		fmt.Println(name, len(b))
	}
}

func main() {
	if len(os.Args) > 1 {
		fmt.Println("child:", os.Getpid(), os.Getppid())
		child()
		return
	}

	fmt.Println("parent:", os.Getpid())
	parent()
}

/*

parent: 2042

child: 2046 2042
main.go 1162
main_test.go 67

*/

process

很少直接 Process 创建子进程,而是 FindProcess 获取某个已运行进程。

进而向其发送信号,或终止。

  • Kill : 发送 SIGKILL 强制终止信号。
  • Signal : 发送信号。
  • Release : 释放进程资源。
  • Wait : 等待进程终止,释放资源,返回相关状态信息。

如果不是子进程, Wait 可能会返回错误, ProcessState == nil

package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

func main() {
	log.SetFlags(log.Lshortfile)

	b, err := exec.Command("pidof", "top").CombinedOutput()
	if err != nil { log.Fatalln(err) }

	pid, _ := strconv.Atoi(strings.TrimSpace(string(b)))
	p, err := os.FindProcess(pid)
	if err != nil { log.Fatalln(err) }

	p.Kill()
	fmt.Println(p.Wait())
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文