Golang应用执行Shell命令实战

作者:梦想画家 时间:2024-05-22 10:29:20 

本文学习如何在Golang程序中执行Shell命令(如,ls,mkdir或grep),如何通过stdin和stdout传入I/O给正在运行的命令,同时管理长时间运行的命令。为了更好的理解,针对不同场景由浅入深提供几个示例进行说明,希望你能轻松理解。

exec包

使用官方os/exec包可以执行外部命令,当你执行shell命令,是需要在Go应用的外部运行代码,因此需要这些命令在子进程中运行。如下图所示:

Golang应用执行Shell命令实战

每个命令在Go应用中作为子进程运行,并暴露stdin和stdout属性,我们可以使用它们读写进程数据。

运行基本Shell命令

运行简单命令并从它的输出中读取数据,通过创建*exec.Cmd实例实现。在下面示例中,使用ls列出当前目录下的文件,并从代码中打印其输出:

// create a new *Cmd instance
// here we pass the command as the first argument and the arguments to pass to the command as the
// remaining arguments in the function
cmd := exec.Command("ls", "./")

// The `Output` method executes the command and
// collects the output, returning its value
out, err := cmd.Output()
if err != nil {
 // if there was any error, print it here
 fmt.Println("could not run command: ", err)
}
// otherwise, print the output from running the command
fmt.Println("Output: ", string(out))

因为在当前目录下运行程序,因此输出项目根目录下文件:

> go run shellcommands/main.go

Output:  LICENSE
README.md
command.go

Golang应用执行Shell命令实战

当运行exec,程序没有产生shell,而是直接运行给定命令,这意味着不会进行任何基于shell的处理,比如glob模式或扩展。举例,当运行ls ./*.md命令,并不会如我们在那个shell中运行命令一样输出readme.md

执行长时间运行命令

前面示例执行ls命令立刻返回结果,但当命令输出是连续的、或需要很长时间执行时会怎样呢?举例,运行ping命令,会周期性获得连续结果:

ping www.baidu.com
PING www.a.shifen.com (36.152.44.95) 56(84) bytes of data.
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=1 ttl=128 time=11.1 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=2 ttl=128 time=58.8 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=3 ttl=128 time=28.2 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=4 ttl=128 time=11.1 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=5 ttl=128 time=11.5 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=6 ttl=128 time=53.6 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=7 ttl=128 time=10.2 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=8 ttl=128 time=10.4 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=9 ttl=128 time=15.8 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=10 ttl=128 time=16.5 ms
64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=11 ttl=128 time=10.9 ms
^C64 bytes from 36.152.44.95: icmp_seq=12 ttl=128 time=9.92 ms

如果尝试使用cmd.Output执行这类命令,则不会获得任何结果,因为Output方法等待命令执行结束,而ping无限期执行。因此需要自定义Stdout属性去读取连续输出:

cmd := exec.Command("ping", "google.com")

// pipe the commands output to the applications
// standard output
cmd.Stdout = os.Stdout

// Run still runs the command and waits for completion
// but the output is instantly piped to Stdout
if err := cmd.Run(); err != nil {
 fmt.Println("could not run command: ", err)
}

再次运行程序,输出结果于Shell中执行类似。

通过直接分配Stdout属性,我们可以在整个命令生命周期中捕获输出,并在接收到输出后立即对其进行处理。进程间io交互如下图所示:

Golang应用执行Shell命令实战

自定义写输出

代替使用os.Stdout,还能通过实现io.Writer接口创建自定义写输出。

下面自定义代码在每个输出块前增加"received output: "前缀:

type customOutput struct{}

func (c customOutput) Write(p []byte) (int, error) {
fmt.Println("received output: ", string(p))
return len(p), nil
}

现在给命令输出赋值自定义写输出实例:

cmd.Stdout = customOutput{}

再次运行程序,会获得下面的输出。

使用Stdin给命令传递输入

前面示例没有给命令任何输入(或提供有限输入作为参数),大多数场景中通过Stdin流传递输入信息。典型的示例为grep命令,可以通过管道从一个命令串给另一个命令:

➜  ~ echo "1. pear\n2. grapes\n3. apple\n4. banana\n" | grep apple
3. apple

这里echo的输出作为stdin传给grep,输入一组水果,通过grep过滤仅输出apple.

*Cmd实例提供了输入流用于写入,下面实例使用它传递输入给grep子进程:

cmd := exec.Command("grep", "apple")

// Create a new pipe, which gives us a reader/writer pair
reader, writer := io.Pipe()
// assign the reader to Stdin for the command
cmd.Stdin = reader
// the output is printed to the console
cmd.Stdout = os.Stdout

go func() {
 defer writer.Close()
 // the writer is connected to the reader via the pipe
 // so all data written here is passed on to the commands
 // standard input
 writer.Write([]byte("1. pear\n"))
 writer.Write([]byte("2. grapes\n"))
 writer.Write([]byte("3. apple\n"))
 writer.Write([]byte("4. banana\n"))
}()

if err := cmd.Run(); err != nil {
 fmt.Println("could not run command: ", err)
}

输出结果:

3. apple

Golang应用执行Shell命令实战

结束子进程

有一些命令无限期运行,需要能够显示信号去结束。举例,如果使用python3 -m http.server运行web服务或sleep 10000,则子进程会运行很长时间或无限期运行。

要停止进程,需要从应用中发送kill信号,可以通过给命令增加上下文实例实现。如果上下文取消,则命令也会终止执行:

ctx := context.Background()
// The context now times out after 1 second
// alternately, we can call `cancel()` to terminate immediately
ctx, _ = context.WithTimeout(ctx, 1*time.Second)

// sleep 10 second
cmd := exec.CommandContext(ctx, "sleep", "10")

out, err := cmd.Output()
if err != nil {
 fmt.Println("could not run command: ", err)
}
fmt.Println("Output: ", string(out))

运行程序,1秒后输出结果:

could not run command:  signal: killed
Output:  

当需要在有限时间内运行命令或在一定时间内命令没有返回结果则执行备用逻辑。

  • 当您希望执行通常不提供太多输出的简单命令时使用cmd.Output

  • 对于具有连续或长时间输出的函数应使用cmd.Run,并通过cmd.Stdout和cmd.Stdin与之交互

  • 在生产场景中,如果进程在给定时间内没有响应,须有超时并结束功能,可以使用取消上下文发送终止命令

来源:https://blog.csdn.net/neweastsun/article/details/128762535

标签:Golang,执行,Shell命令
0
投稿

猜你喜欢

  • PHP引用符&的用法详细解析

    2023-10-17 17:25:53
  • 利用Django模版生成树状结构实例代码

    2023-11-10 16:41:11
  • python互斥锁、加锁、同步机制、异步通信知识总结

    2023-10-08 21:17:16
  • python3操作微信itchat实现发送图片

    2022-03-26 11:42:52
  • python中编写config文件并及时更新的方法

    2021-08-01 05:44:39
  • Django实现jquery select2带搜索的下拉框

    2022-04-20 18:50:58
  • PHP中array_slice函数用法实例详解

    2023-06-20 20:18:04
  • 你是真正的用户体验设计者吗? Ⅰ

    2008-03-20 13:42:00
  • Python自动化测试PO模型封装过程详解

    2023-08-23 18:59:49
  • python基于隐马尔可夫模型实现中文拼音输入

    2023-05-08 21:58:00
  • Centos环境部署django项目的全过程(永久复用)

    2021-06-17 00:37:28
  • MySql如何获取相邻数据

    2024-01-16 04:36:53
  • 栈和队列数据结构的基本概念及其相关的Python实现

    2022-03-14 23:18:28
  • 浅析jQuery对select操作小结(遍历option,操作option)

    2024-04-22 12:59:32
  • Go语言的type func()用法详解

    2024-02-21 12:50:51
  • 如何使用ASP来读写注册表

    2007-09-20 13:08:00
  • 用python 绘制茎叶图和复合饼图

    2023-08-04 10:34:54
  • Python模拟百度自动输入搜索功能的实例

    2023-12-04 18:17:53
  • Python自动化爬取天眼查数据的实现

    2021-01-10 23:28:27
  • python简单实现基数排序算法

    2023-11-10 06:27:27
  • asp之家 网络编程 m.aspxhome.com