Golang应用执行Shell命令实战
作者:梦想画家 发布时间:2024-05-22 10:29:20
本文学习如何在Golang程序中执行Shell命令(如,ls,mkdir或grep),如何通过stdin和stdout传入I/O给正在运行的命令,同时管理长时间运行的命令。为了更好的理解,针对不同场景由浅入深提供几个示例进行说明,希望你能轻松理解。
exec包
使用官方os/exec包可以执行外部命令,当你执行shell命令,是需要在Go应用的外部运行代码,因此需要这些命令在子进程中运行。如下图所示:
每个命令在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
当运行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交互如下图所示:
自定义写输出
代替使用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
结束子进程
有一些命令无限期运行,需要能够显示信号去结束。举例,如果使用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


猜你喜欢
- 表单介绍说到表单,在HTML中表单的创建时通过<form>标签实现的,在<form>标签内部,字段通过使用<i
- 浏览带有下拉菜单的网页时,我们经常会注意到当更改显示器分辨率时,其下拉菜单的位置并没有改变,这也是我们设计网页时容易忽略的一个问题,其实通过
- 在极坐标中,圆的表示方式为:x=x0+rcosθy=y0+rsinθ圆心为(x0,y0),r为半径,θ为旋转度数,值范围为0-359如果给定
- 背景:不久前,设计实现了京东api的功能,发现如果换了其它快递再重新设计,岂不是会浪费太多的时间,所以选个第三方提供的快递API是最为合理的
- 译者按:在iOS HIG已经强大经典了N年之后,Android终于推出了一套比较系统的HIG(大概是为了配合Android 4.0 Ice
- 前言哈喽铁汁们~新年到了,很多小伙伴都会买上一本日历。现在各种主题各种式样的日历有很多,不过你有没有想过自己定制一套专属的个性化电子日历呢?
- 什么是 go-cachego-cache 是一个轻量级的基于内存的 K-V 储存组件,内部实现了一个线程安全的 map[strin
- 前言:集合这种数据类型和我们数学中所学的集合很是相似,数学中堆积和的操作也有交集,并集和差集操作,python集合也是一样。一、交集操作1.
- 基于python3+OpenCV的人脸和眼睛识别,供大家参考,具体内容如下一、OpenCV人脸检测的xml文件下载人脸检测和眼睛检测要用到h
- 本文实例为大家分享了python实现网页自动签到功能的具体代码,供大家参考,具体内容如下第1步、环境准备(用的chrome浏览器)1.安装s
- 本文研究的主要是django在接受post请求时显示403forbidden时的处理方法,具体代码如下。最近在做一个项目需要用到Django
- 三遍记忆,让你记住海量素材的准确位置和用途,提高其可用性.这仅仅是一个示意图.在以往的日子里,我做到了只要脑袋里冒出一个想法,立刻就能知道我
- numpy中矩阵选取子集或者以条件选取子集,用mask是一种很好的方法简单来说就是用bool类型的indice矩阵去选择,mask = np
- 今天我想讲一讲关于Elasticsearch的索引建立,当然提前是你已经安装部署好Elasticsearch。ok,先来介绍一下Elatic
- 一、普通进度条import sysimport time# 普通进度条# 在代码迭代运行中进行统计计算,并使用格式化字符串输出代码运行进度d
- 此文仅当学习笔记用.这个实例是在Python环境下如何爬取弹出窗口的内容,有些时候我们要在页面中通过点击,然后在弹出窗口中才有我们要的信息,
- 同步的方法基本与多线程相同。1) Lock当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突。import multiproc
- 在python中,通常通过dict和zip组合来构建键值对。比如:aid = [i for i in range(10)]name = [[
- 安装redis服务1 下载redis cd /usr/local/ 进入安装目录 wget http://downl
- python最值与下标最大值的下标winner = np.argmax(scores)多个最大值的下标(np.argwhere返回数组中非0