文章目录
前言
在自定义运维工具开发过程中,有时候为了降低代码复杂度,能用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标准库部分方法的使用示例,有空继续补充