您的位置 首页 知识

Goos_exec使用方式实践 google sheets

Goos/exec使用方式实践 google sheets

目录
  • os/exec包结构体与技巧
  • 使用技巧
  • 创建带有 context 的命令
  • 获取命令的输出
  • 获取组合的标准输出和错误输出
  • 设置标准输出和错误输出
  • 使用标准输入传递数据
  • 设置和使用环境变量
  • 使用管道
  • 使用bash -c执行复杂命令
  • 指定职业目录
  • 捕获退出情形
  • 搜索可执行文件
  • 功能练习
  • 拓展资料

os/exec是 Go 提供的内置包,可以用来执行外部命令或程序。比如,我们的主机上安装了redis-server二进制文件,那么就可以使用os/exec在 Go 程序中启动redis-server提供服务。当然,我们也可以使用os/exec执行lspwd等操作体系内置命令。这篇文章小编将不求内容多么深入,旨在带大家极速入门os/exec的常规使用。

os/exec包结构体与技巧

func LookPath(file string) (string, error)type Cmd func Command(name string, arg …string) Cmd func CommandContext(ctx context.Context, name string, arg …string) Cmd func (c Cmd) CombinedOutput() ([]byte, error) func (c Cmd) Environ() []string func (c Cmd) Output() ([]byte, error) func (c Cmd) Run() error func (c Cmd) Start() error func (c Cmd) StderrPipe() (io.ReadCloser, error) func (c Cmd) StdinPipe() (io.WriteCloser, error) func (c Cmd) StdoutPipe() (io.ReadCloser, error) func (c Cmd) String() string func (c Cmd) Wait() error

Cmd结构体表示一个准备或正在执行的外部命令。

  • 调用函数CommandCommandContext可以构造一个Cmd对象。
  • 调用RunStartOutputCombinedOutput技巧可以运行Cmd对象所代表的命令。
  • 调用Environ技巧可以获取命令执行时的环境变量。
  • 调用StdinPipeStdoutPipeStderrPipe技巧用于获取管道对象。
  • 调用Wait技巧可以阻塞等待命令执行完成。
  • 调用String技巧返回命令的字符串形式。LookPath函数用于搜索可执行文件。

使用技巧

package mainimport (“log””os/exec”)func main() // 创建一个命令 cmd := exec.Command(“echo”, “Hello, World!”)// 执行命令并等待命令完成 err := cmd.Run() // 执行后控制台不会有任何输出 if err != nil log.Fatalf(“Command failed: %v”, err) }}

  • exec.Command函数用于创建一个命令,函数第一个参数是命令的名称,后面跟一个不定常参数作为这个命令的参数,最终会传递给这个命令。
  • Cmd.Run技巧会阻塞等待命令执行完成,默认情况下命令执行后控制台不会有任何输出:

执行程序$ go run main.go 执行完成后没有任何输出

可以在后台运行一个命令:

func main() cmd := exec.Command(“sleep”, “3”)// 执行命令(非阻塞,不会等待命令执行完成)if err := cmd.Start(); err != nil log.Fatalf(“Command start failed: %v”, err) return } fmt.Println(“Command running in the background…”)// 阻塞等待命令完成if err := cmd.Wait(); err != nil log.Fatalf(“Command wait failed: %v”, err) return } log.Println(“Command finished”)}

实际上Run技巧就等于Start+Wait技巧,如下是Run技巧源码的实现:

func (c Cmd) Run() error if err := c.Start(); err != nil return err } return c.Wait()}

创建带有 context 的命令

os/exec还提供了一个exec.CommandContext构造函数可以创建一个带有context的命令。那么我们就可以利用context的特性来控制命令的执行了。

func main() ctx, cancel := context.WithTimeout(context.Background(), 100time.Millisecond) defer cancel() cmd := exec.CommandContext(ctx, “sleep”, “5”) if err := cmd.Run(); err != nil log.Fatalf(“Command failed: %vn”, err) // signal: killed }}

执行示例代码,得到输出如下:

$ go run main.go2025/01/14 23:54:20 Command failed: signal: killedexit status 1

当命令执行超时会收到killed信号自动取消。

获取命令的输出

无论是调用Cmd.Run还是Cmd.Start技巧,默认情况下执行命令后控制台不会得到任何输出。

可以使用Cmd.Output技巧来执行命令,以此来获取命令的标准输出:

func main() // 创建一个命令 cmd := exec.Command(“echo”, “Hello, World!”) // 执行命令,并获取命令的输出,Output 内部会调用 Run 技巧 output, err := cmd.Output() if err != nil log.Fatalf(“Command failed: %v”, err) } fmt.Println(string(output)) // Hello, World!}

执行示例代码,得到输出如下:

$ go run main.goHello, World!

获取组合的标准输出和错误输出

Cmd.CombinedOutput技巧能够在运行命令后,返回其组合的标准输出和标准错误输出:

func main() // 使用一个命令,既产生标准输出,也产生标准错误输出 cmd := exec.Command(“sh”, “-c”, “echo ‘This is stdout’; echo ‘This is stderr’ >&2”)// 获取 标准输出 + 标准错误输出 组合内容 output, err := cmd.CombinedOutput() if err != nil log.Fatalf(“Command execution failed: %v”, err) }// 打印组合输出 fmt.Printf(“Combined Output:n%s”, string(output))}

执行示例代码,得到输出如下:

$ go run main.goCombined Output:This is stdoutThis is stderr

设置标准输出和错误输出

可以利用Cmd对象的StdoutStderr属性,重定向标准输出和标准错误输出到当前进程:

func main() cmd := exec.Command(“ls”, “-l”) // 设置标准输出和标准错误输出到当前进程,执行后可以在控制台看到命令执行的输出 cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil log.Fatalf(“Command failed: %v”, err) }}

这样,使用Cmd.Run执行命令后控制台就能看到命令执行的输出了。

执行示例代码,得到输出如下:

$ go run main.gototal 4824-rw-r–r– 1 jianghushinian staff 12 Jan 4 10:37 demo.logdrwxr-xr-x 3 jianghushinian staff 96 Jan 13 09:41 examples-rwxr-xr-x 1 jianghushinian staff 2453778 Jan 1 15:09 main-rw-r–r– 1 jianghushinian staff 6179 Jan 15 09:13 main.go

使用标准输入传递数据

可以使用grep命令接收stdin的数据,接着在其中搜索包含指定模式的文本行:

func main() cmd := exec.Command(“grep”, “hello”)// 通过标准输入传递数据给命令 cmd.Stdin = bytes.NewBufferString(“hello world!nhi theren”)// 获取标准输出 output, err := cmd.Output() if err != nil log.Fatalf(“Command failed: %v”, err) return } fmt.Println(string(output)) // hello world!}

可以将一个io.Reader对象赋值给Cmd.Stdin属性,来实现将数据通过stdin传递给外部命令。

执行示例代码,得到输出如下:

$ go run main.gohello world!

还可以将打开的文件描述符传给Cmd.Stdin属性:

func main() file, err := os.Open(“demo.log”) // 打开一个文件 if err != nil log.Fatalf(“Open file failed: %vn”, err) return } defer file.Close() cmd := exec.Command(“cat”) cmd.Stdin = file // 将文件作为 cat 的标准输入 cmd.Stdout = os.Stdout // 获取标准输出 if err := cmd.Run(); err != nil log.Fatalf(“Command failed: %v”, err) }}

只要是io.Reader对象即可。

设置和使用环境变量

CmdEnviron技巧可以获取环境变量,Env属性则可以设置环境变量:

func main() cmd := exec.Command(“printenv”, “ENV_VAR”) log.Printf(“ENV: %+vn”, cmd.Environ())// 设置环境变量 cmd.Env = append(cmd.Environ(), “ENV_VAR=HelloWorld”) log.Printf(“ENV: %+vn”, cmd.Environ())// 获取输出 output, err := cmd.Output() if err != nil log.Fatalf(“Command failed: %v”, err) } fmt.Println(string(output)) // HelloWorld}

这段代码输出结局与执行环境相关,此处不演示执行结局了,你可以自行尝试。

不过最终的output输出结局一定是HelloWorld

使用管道

os/exec支持管道功能,Cmd对象提供的StdinPipeStdoutPipeStderrPipe三个技巧用于获取管道对象。故名思义,三者分别对应标准输入、标准输出、标准错误输出的管道对象。

使用示例如下:

func main() // 命令中使用了管道 cmdEcho := exec.Command(“echo”, “hello worldnhi there”) outPipe, err := cmdEcho.StdoutPipe() if err != nil log.Fatalf(“Command failed: %v”, err) }// 注意,这里不能使用 Run 技巧阻塞等待,应该使用非阻塞的 Start 技巧 if err := cmdEcho.Start(); err != nil log.Fatalf(“Command failed: %v”, err) } cmdGrep := exec.Command(“grep”, “hello”) cmdGrep.Stdin = outPipe output, err := cmdGrep.Output() if err != nil log.Fatalf(“Command failed: %v”, err) } fmt.Println(string(output)) // hello world}

开头来说创建一个用于执行echo命令的Cmd对象cmdEcho,并调用它的StdoutPipe技巧获得标准输出管道对象outPipe

接着调用Start技巧非阻塞的方式执行echo命令;

接着创建一个用于执行grep命令的Cmd对象cmdGrep,将cmdEcho的标准输出管道对象赋值给cmdGrep.Stdin作为标准输入,这样,两个命令就通过管道串联起来了;

最终通过cmdGrep.Output技巧拿到cmdGrep命令的标准输出。

执行示例代码,得到输出如下:

$ go run main.gohello world

使用bash -c执行复杂命令

如果你不想使用os/exec提供的管道功能,那么在命令中直接使用管道符|,也可以实现同样功能。

不过此时就需要使用sh -c或者bash -c等 Shell 命令来解析执行更复杂的命令了:

func main() // 命令中使用了管道 cmd := exec.Command(“bash”, “-c”, “echo ‘hello worldnhi there’ | grep hello”) output, err := cmd.Output() if err != nil log.Fatalf(“Command failed: %v”, err) } fmt.Println(string(output)) // hello world}

这段代码中的管道功能同样生效。

指定职业目录

可以通过指定Cmd对象的的Dir属性来指定职业目录:

func main() cmd := exec.Command(“cat”, “demo.log”) cmd.Stdout = os.Stdout // 获取标准输出 cmd.Stderr = os.Stderr // 获取错误输出 // cmd.Dir = “/tmp” // 指定完全目录 cmd.Dir = “.” // 指定相对目录 if err := cmd.Run(); err != nil log.Fatalf(“Command failed: %v”, err) }}

捕获退出情形

上面讲解了很多执行命令相关操作,但其实还有一个很重要的点没有讲到,就是怎样捕获外部命令执行后的退出情形码:

func main() // 查看一个不存在的目录 cmd := exec.Command(“ls”, “/nonexistent”)// 运行命令 err := cmd.Run()// 检查退出情形var exitError exec.ExitErrorif errors.As(err, &exitError) log.Fatalf(“Process PID: %d exit code: %d”, exitError.Pid(), exitError.ExitCode()) // 打印 pid 和退出码 }}

这里执行ls命令来查看一个不存在的目录/nonexistent,程序退出情形码必然不为0

执行示例代码,得到输出如下:

$ go run main.go2025/01/15 23:31:44 Process PID: 78328 exit code: 1exit status 1

搜索可执行文件

最终要介绍的函数就只剩一个LookPath了,它用来搜索可执行文件。

搜索一个存在的命令:

func main() path, err := exec.LookPath(“ls”) if err != nil log.Fatal(“installing ls is in your future”) } fmt.Printf(“ls is available at %sn”, path)}

执行示例代码,得到输出如下:

$ go run main.gols is available at /bin/ls

搜索一个不存在的命令:

func main() path, err := exec.LookPath(“lsx”) if err != nil log.Fatal(err) } fmt.Printf(“ls is available at %sn”, path)}

执行示例代码,得到输出如下:

$ go run main.go2025/01/15 23:37:45 exec: “lsx”: executable file not found in $PATHexit status 1

功能练习

介绍完了os/exec常用的技巧和函数,我们现在来做一个小练习,使用os/exec来执行外部命令ls -l /var/log/.log

示例如下:

func main() cmd := exec.Command(“ls”, “-l”, “/var/log/.log”) output, err := cmd.CombinedOutput() // 获取标准输出和错误输出 if err != nil log.Fatalf(“Command failed: %v”, err) } fmt.Println(string(output))}

执行示例代码,得到输出如下:

$ go run main.go2025/01/16 09:15:52 Command failed: exit status 1exit status 1

执行报错了,这里的错误码为1,但错误信息并不明确。

这个报错其实是由于os/exec默认不支持通配符参数导致的,exec.Command不支持直接在参数中使用 Shell 通配符(如),由于它不会通过 Shell 来解析命令,而是直接调用底层的程序。

要解决这个难题,可以通过显式调用 Shell(例如bashsh),让 Shell 来解析通配符。

比如使用bash -c执行通配符命令ls -l /var/log/.log

func main() // 使用 bash -c 来解析通配符 cmd := exec.Command(“bash”, “-c”, “ls -l /var/log/.log”) output, err := cmd.CombinedOutput() // 获取标准输出和错误输出 if err != nil log.Fatalf(“Command failed: %v”, err) } fmt.Println(string(output))}

执行示例代码,得到输出如下:

$ go run main.go-rw-r–r– 1 root wheel 0 Oct 7 21:20 /var/log/alf.log-rw-r–r– 1 root wheel 11936 Jan 13 11:36 /var/log/fsck_apfs.log-rw-r–r– 1 root wheel 334 Jan 13 11:36 /var/log/fsck_apfs_error.log-rw-r–r– 1 root wheel 19506 Jan 11 18:04 /var/log/fsck_hfs.log-rw-r–r–@ 1 root wheel 21015342 Jan 16 09:02 /var/log/install.log-rw-r–r– 1 root wheel 1502 Nov 5 09:44 /var/log/shutdown_monitor.log-rw-r—–@ 1 root admin 3779 Jan 16 08:59 /var/log/system.log-rw-r—– 1 root admin 187332 Jan 16 09:05 /var/log/wifi.log

顺带提一嘴,我们还可以用 Go 标准库提供的filepath.Glob来手动解析通配符:

func main() // 匹配通配符路径 files, err := filepath.Glob(“/var/log/.log”) if err != nil log.Fatalf(“Glob failed: %v”, err) } if len(files) == 0 log.Println(“No matching files found”) return }// 将匹配到的文件传给 ls 命令 args := append([]string”-l”}, files…) cmd := exec.Command(“ls”, args…) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil log.Fatalf(“Command failed: %v”, err) }}

filepath.Glob函数会返回模式匹配的文件名列表,如果不匹配则返回nil。这样,我们就可以先解析文件名列表,再交给exec.Command来执行ls命令了。

拓展资料

以上为个人经验,希望能给大家一个参考,也希望大家多多支持风君子博客。

无论兄弟们可能感兴趣的文章:

  • Go的os/exec执行超时导致程序死机的解决方案
  • Golang标准库os/exec执行外部命令并获取其输出包代码示例
  • 详解Go语言中用 os/exec 执行命令的五种技巧