Bootstrap

golang标准库os/exec使用方法示例



前言

在自定义运维工具开发过程中,有时候为了降低代码复杂度,能用shell脚本或linux命令实现的,我们尽量选择此方式然后通过代码去调用执行,那么 os/exec 这个系统库刚好提供了相应的功能。它提供了一组函数和结构,用于调用外部程序,这些外部程序可以是系统自带的,也可以是用户自定义的。并且包中提供了一组函数,用于执行系统命令,我们可以使用它来执行系统的cmd命令行。
exec包执行外部命令,它将 os.StartProcess 进行包装使得它更容易映射到 stdin 和 stdout,并且利用 pipe 连接i/o。
参考文档: https://pkg.go.dev/os/exec


一、os/exec使用步骤

使用os/exec包执行命令的基本步骤如下:
	1、导入os/exec包

	2、创建一个exec.Cmd实例,指定要执行的命令和参数

	3、调用Cmd实例的Run、Start或Output、CombinedOutput等方法来执行命令
os/exec包提供了多种方法来捕获命令的输出:
	1、CombinedOutput: 
		执行命令并返回标准输出和错误输出合并的切片。

	2、Output:
		 执行命令并返回标准输出的切片。

	3、StdoutPipe和StderrPipe:
		 返回与命令标准输出和错误输出关联的管道。

二、常用方法

1.创建实例常用方法

代码如下(示例):

func Command(name string, arg ...string) *Cmd {}
使用 exec.Command 函数来创建一个 Cmd 结构体,该函数接受两个参数,第一个参数是要执行的命令,第二个
参数是命令行参数,比如 df -Th那么第一个参数就是 df,第二个参数就是 -Th

2.调用实例常用方法

代码如下(示例):

//运行命令不返回标注输出1--阻塞
func (c *Cmd) Run() error {}
使用 Run 函数来执行这个命令,Run 函数会根据我们传入的参数来执行命令,并返回一个 error 类型的结果
注意:
	1、实际Run函数底层调用的是Start()Wait()方法,因此它是阻塞的,只有等待命令执行完成才会返回结果
	2、一个 command 只能使用 Start() 或者 Run() 中的一个启动命令,不能两个同时使用
//运行命令不返回标注输出2--非阻塞
func (c *Cmd) Start() error
使某个命令开始执行,但是并不等到他执行结束,这点和Run命令有区别
注意:
	1、Start方法 要和 Wait方法 一起使用
	2、Start 执行不会等待命令完成,是非阻塞的,Run会阻塞等待命令完成
	3、wait方法会返回命令的返回状态码并在命令返回后释放相关的资源
//运行命令并返回其标准输出--阻塞
func (c *Cmd) Output() ([]byte, error)
// 运行命令,并返回标准输出和标准错误--阻塞
func (c *Cmd) CombinedOutput() ([]byte, error)
注意: 
	Output()CombinedOutput() 不能够同时使用,因为 command 的标准输出只能有一个,同时使用的话,便会报错。
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

3. 示例一、只执行命令,不获取结果

代码如下(示例):

package main

import (
	"fmt"
	"os/exec"
)

func main() {
    //创建实例
	cmd := exec.Command("df", "-Th")
    //调用Run()方法,执行到此处时会阻塞等待结果
	err := cmd.Run()
	if err != nil {
		fmt.Println("Error executing command:", err.Error())
	}
}

4. 示例二、执行命令,并获取结果

代码如下(示例):

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	cmd := exec.Command("ls", "-l", "/tmp/")
    //返回一个[]byte类型和错误
	out, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Printf("combined out:\n%s\n", string(out))
	}
	fmt.Printf("combined out:\n%s\n", string(out))
}

5. 获取进程退出状态码ExitCode

代码如下(示例):

package main

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

func main() {

	cmd := exec.Command("bash", "test.sh")
	var err error = cmd.Run()
	if err != nil {
		log.Fatal(err)
	}

	// 获取命令的退出状态码
	exitCode := cmd.ProcessState.ExitCode()
	fmt.Println("ExitCode:", exitCode)
}

6. 查找二进制文件

代码如下 (示例):

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	f, err := exec.LookPath("ls")
	if err != nil {
		fmt.Println(err)
	}
	// /usr/bin/ls
	fmt.Println(f)
}

7. 执行命令,并区分stdout 和 stderr

代码如下 (示例):

package main

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

func main() {
    // 因为 exec.Command 不会自动展开通配符*,它会将通配符*作为一个普通的字符串传递给外部命令
	// sh -c 会启动一个新的shell并执行命令字符串,使用 shell 来执行命令,这样可以正确处理通配符
	cmd := exec.Command("sh", "-c", "ls -l /var/log/*.log")

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout  // 标准输出
	cmd.Stderr = &stderr  // 标准错误

	err := cmd.Run()
	outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
	fmt.Printf("out:\n%s\nerr:\n%s\n", outStr, errStr)

	if err != nil {
		log.Fatalf("cmd.Run() failed with %s\n", err)
	}
}

8. 使用管道实现多条命令组合

代码如下 (示例):

在shell中我们可以执行grep "INFO" /tmp/test.log |wc -l命令,
但是在代码中如果使用 exec.Command("grep", "INFO", "/tmp/test.log","|wc -l")这个来定义,执行会报错的,因此得使用到管道

package main
import (
    "os"
    "os/exec"
)
func main() {
    c1 := exec.Command("grep", "INFO", "/tmp/test.log")
    c2 := exec.Command("wc", "-l")
    c2.Stdin, _ = c1.StdoutPipe()
    c2.Stdout = os.Stdout
    _ = c2.Start()
    _ = c1.Run()
    _ = c2.Wait()
}

9. 将命令的输出结果重定向到文件中

代码如下 (示例):

package main
import (
    "log"
    "os"
    "os/exec"
)
func main() {
    cmd := exec.Command("ls", "-l", "/tmp/")
    stdout, err := os.OpenFile("stdout.log", os.O_CREATE|os.O_WRONLY, 0600)
    if err != nil {
        log.Fatalln(err)
    }
    defer stdout.Close()
    // 重定向标准输出到文件
    cmd.Stdout = stdout
    // 执行命令
    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }
    if err := cmd.Wait(); err != nil {
        log.Fatal(err)
    }
}

10. 终止进程

代码如下 (示例):

在shell中通过kill -9 pid来实现

package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
)

func main() {
	// 创建并启动新进程
	cmd := exec.Command("sleep", "5")
	err := cmd.Start()
	if err != nil {
		fmt.Println("Failed to start process:", err)
		return
	}

	fmt.Println("New process started. PID:", cmd.Process.Pid)

	// 等待进程完成
	err = cmd.Wait()
	if err != nil {
		fmt.Println("Process finished with error:", err)
		return
	}

	fmt.Println("Process finished successfully.")

	// 获取上述进程pid,然后终止进程
	err = terminateProcess(cmd.Process.Pid)
	if err != nil {
		fmt.Println("Failed to terminate process:", err)
		return
	}

	fmt.Println("Process terminated.")
}

// 终止进程
func terminateProcess(pid int) error {
	process, err := os.FindProcess(pid)
	if err != nil {
		return err
	}

	// 使用系统调用发送终止信号
	err = process.Signal(syscall.SIGTERM)
	if err != nil {
		return err
	}
	return nil
}

总结

以上就是随手整理的os/exec标准库部分方法的使用示例,有空继续补充

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;