在 Golang 中执行 Shell 命令

news2024/9/21 2:37:58

原文标题:Executing Shell Commands in Golang

作者:Soham Kamani

之前自己也写过 os/exec 包执行 Shell 命令的文章,但是没有这篇讲的详细,感兴趣可以看看,点此处。

在本教程中,我们将学习如何在 Golang 中执行shell命令(如 lsmkdirgrep )。我们还将学习如何通过 stdinstdout 传递 I/O 到正在运行的命令,以及管理长时间运行的命令。

如果只是想看代码,可以在 Github 上查看

Exec

我们可以使用官方的 os/exec 包来运行外部命令。

当我们执行 shell 命令时,我们是在 Go 应用程序之外运行代码。为此,我们需要在子进程中运行这些命令。

child-process-thread.drawio.svg

每个命令都作为正在运行的 Go 应用程序中的子进程运行,并公开我们可以用来从进程读取和写入数据的 StdinStdout 属性。

运行基本的 Shell 命令

要运行一个简单的命令并读取其输出,我们可以创建一个新的 *exec.Cmd 实例并运行它。在此示例中,让我们使用 ls 列出当前目录中的文件,并打印代码的输出:

// 创建了一个新的 *Cmd 实例
// 使用 "ls" 命令和 "./" 参数作为参数
cmd := exec.Command("ls", "./")

// 使用 `Output` 方法执行该命令并收集其输出
out, err := cmd.Output()
if err != nil {
  // 如果执行命令时出现错误,则输出错误信息
  fmt.Println("could not run command: ", err)
}
// 否则,输出运行该命令的输出结果
fmt.Println("Output: ", string(out))

由于我在示例仓库中运行此代码,因此它会打印项目根目录中的文件:

> go run shellcommands/main.go

Output:  LICENSE
README.md
go.mod
shellcommands

cmd-output-execution.drawio.svg

请注意,当我们运行 exec 时,我们的应用程序不会生成 shell,而是直接运行给定的命令。这意味着将不会执行任何基于 shell 的处理,例如 glob 模式或扩展。

执行持久运行的命令

前面的示例执行了 ls 命令,该命令立即返回了它的输出。那些输出是连续的,或者需要很长时间才能检索的命令呢?

例如,当我们运行 ping 命令时,我们会定期获得连续输出:

➜  ~ ping google.com
PING google.com (142.250.77.110): 56 data bytes
64 bytes from 142.250.77.110: icmp_seq=0 ttl=116 time=11.397 ms
64 bytes from 142.250.77.110: icmp_seq=1 ttl=116 time=17.646 ms  ## this is received after 1 second
64 bytes from 142.250.77.110: icmp_seq=2 ttl=116 time=10.036 ms  ## this is received after 2 seconds
64 bytes from 142.250.77.110: icmp_seq=3 ttl=116 time=9.656 ms   ## and so on
# ...

如果我们尝试使用 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)
}

这段代码使用 Go 语言的 exec 包来执行 ping 命令并将输出重定向到标准输出流(os.Stdout)。具体来说,它创建了一个命令对象(cmd),该对象包含要执行的命令(“ping"和"google.com”)。然后将命令的标准输出流(cmd.Stdout)设置为应用程序的标准输出流(os.Stdout)。最后,使用 cmd.Run() 方法运行该命令,并等待其完成。如果运行命令时出现错误,将在控制台输出错误信息。

输出结果:

> go run shellcommands/main.go

PING google.com (142.250.195.142): 56 data bytes
64 bytes from 142.250.195.142: icmp_seq=0 ttl=114 time=9.397 ms
64 bytes from 142.250.195.142: icmp_seq=1 ttl=114 time=37.398 ms
64 bytes from 142.250.195.142: icmp_seq=2 ttl=114 time=34.050 ms
64 bytes from 142.250.195.142: icmp_seq=3 ttl=114 time=33.272 ms

# ...
# and so on

通过直接分配 Stdout 属性,我们可以捕获整个命令生命周期的输出,并在收到后立即处理。

cmd-stdout-workflow.drawio.svg

自定义输出写入程序

与使用 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
}

现在我们可以指定一个新的 customWriter 实例作为输出写入器:

cmd.Stdout = customOutput{}

如果我们现在运行应用程序,我们将得到以下输出:

received output:  PING google.com (142.250.195.142): 56 data bytes
64 bytes from 142.250.195.142: icmp_seq=0 ttl=114 time=187.825 ms

received output:  64 bytes from 142.250.195.142: icmp_seq=1 ttl=114 time=19.489 ms

received output:  64 bytes from 142.250.195.142: icmp_seq=2 ttl=114 time=117.676 ms

received output:  64 bytes from 142.250.195.142: icmp_seq=3 ttl=114 time=57.780 ms

使用 STDIN 将输入传递给命令

在前面的示例中,我们在不提供任何输入(或提供有限的输入作为参数)的情况下执行命令。在大多数情况下,输入是通过 STDIN 流给出的。

译注:就是外部给命令,然后去执行

一个著名的例子是 grep 命令,我们可以通过管道从另一个命令输入:

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

在这里,输入通过 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

stdin.drawio.svg

Kill 一个子进程

有几个命令会无限期地运行,或者需要明确的信号才能停止。

例如,如果我们使用 python3 -m http.server 启动 Web 服务器或执行 sleep 10000,则生成的子进程将运行很长时间(或无限运行)。

要停止这些进程,我们需要从应用程序发送终止信号。我们可以通过向命令添加一个上下文实例来做到这一点。

如果上下文被取消,命令也会终止。

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

cmd := exec.CommandContext(ctx, "sleep", "100")

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:  

当您想要限制运行命令所花费的时间或想要创建回退以防命令未按时返回结果时,终止子进程很有用。

总结

到目前为止,我们学习了多种执行 unix shell 命令并与之交互的方法。使用 os/exec 包时需要注意以下几点:

  • 当您想要执行通常不会提供太多输出的简单命令时,请使用 cmd.Output
  • 对于具有连续或长时间运行输出的函数,您应该使用 cmd.Run 并使用 cmd.Stdoutcmd.Stdin 与命令交互
  • 在生产应用程序中,如果某个进程在给定的时间内没有响应,那么保持超时并终止该进程是非常有用的。我们可以使用上下文取消发送终止命令。

如果您想了解更多关于不同功能和配置选项的信息,可以查看官方文档页面。

您可以在 Github 上查看所有示例的工作代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/536358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

融云 WICC 2023 定档!「出海嘉年华」穂城来袭!

集赞获纸质版《作战地图》 阔别一年,通信行业年度盛会 WICC 即将重磅回归。移步【融云全球互联网通信云】回复“地图”免费领 6 月 2 日(周五),融云与白鲸出海联合主办、以“聚势突围,布局全球”为主题的“WICC 社交…

进程线程常见面试题及基础知识

1 进程 我们编写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令,那么这个运行中的程序,就被…

一起内核线程异常占用CPU资源的排查过程

1、软硬件环境 硬件: NXP LS1043A 4核 cortex-A53 软件: linux 5.10.35 2、问题现象 最近有个产品要把内核从4.19升级到 5.10.35版本,产品在内核版本4.19工作正常,升级到5.10.35以后,产品在不接任何外设,…

【基础1】SQL 数据库分类 代码建库、代码修改属性 代码建表 代码修改数据表属性 代码为数据表插入信息 数据的修改与删除

目录 数据库基础 代码建库 数据完整性 代码建表 数据库基础 系统数据库:master、model、tempdb、madb数据库文件的组成:【数据文件可以放在不同的文件组里】 主数据文件:*.mdf 主数据文件只能有一个次要数据文件:*.ndf日志文…

DolphinScheduler 集群模式部署

文章目录 DolphinScheduler 集群模式部署一、集群规划1、前置准备工作2、解压DolphinScheduler 安装包3、创建元数据库及用户 二、配置一键部署脚本1、初始化数据库2、一键部署 DolphinScheduler3、DolphinScheduler 启停命令 DolphinScheduler 集群模式部署 一、集群规划 集…

01.数据结构和算法概述

前言 数据结构是一个古老的课题。他与程序开发息息相关,但是我们日常开发中,好像很少让我们自己设计一个数据结构。只求程序能跑,并不太关注性能。但是它是我们软件开发人员的基本功,也是拉开普通程序员和高级程序员的一个门槛&a…

Selenium浏览器交互原理与应用,玩转Web自动化测试

目录 前言: 浏览器交互: Selenium的实现方式: Selenium WebDriver: WebDriver的等待机制: 总结: Web自动化测试: 前言: Web自动化测试是现代软件开发中必不可少的一个环节,它可以帮助开发人员快速自动…

ChatGPT4通道开放接入基于OPEN AI 平台你的任何APP 可一键接入AI 智能

你一定很好奇什么是 OPEN AI快速开发平台 顾名思义,开放的OPEN AI平台。 基于这个平台你的上层应用,如何 APP,小程序,H5,WEB, 公众号,任何一切终端都可以轻松接入,AI智能应用。 开发初衷 爆肝一周,我开源了ChatGPT 中文版接口&a…

Win11右键菜单选项变成英文了怎么恢复回来?

Win11右键菜单选项变成英文了怎么恢复回来?有用户在使用Win11系统的时候,遇到了右键菜单选项的一些选项变成英文的情况,导致自己的使用受到了影响。那么这个情况怎么去进行解决呢?来看看以下的解决方法吧。 方法一:直接…

零基础学网络安全?一般人我还是劝你算了吧

一、网络安全学习的误区 1.不要试图以编程为基础去学习网络安全 不要以编程为基础再开始学习网络安全,一般来说,学习编程不但学习周期长,且过渡到网络安全用到编程的用到的编程的关键点不多。一般人如果想要把编程学好再开始学习网络安全往…

mathtype公式符号显示不对

文章目录 问题解决方法结果 记录攥写论文遇到的问题及解决方法 问题 使用mathtype编辑公式过后,发现公式显示不对,出现两种问题: 1:部分符号变为方框 2:符号大小异常 例如: 解决方法 第一种&#xff1a…

KDZD5550系列电压击穿试验仪操作说明

一、产品概述 KDZD5550系列电压击穿试验仪根据国家GB1408.1-2006《绝缘材料电气强度试验方法》其作用可称为电气绝缘强度试验仪、介质强度测试仪等。其工作原理是:把一个高于正常工作的电压加在被测设备的绝缘体上,持续一段规定的时间,加在上…

优思学院|DOE试验设计在六西格玛项目的哪个阶段进行?

六西格玛DMAIC是一种用于现有流程改进的方法,其中包括五个阶段:定义阶段(D),测量阶段(M),分析阶段(A),改进阶段(I)和控制阶…

【C++动态内存管理】

目录 一、C/C内存分布二、C中动态内存管理2.1new/delete操作内置类型2.2new/delete操作自定义类型 四、new和delete的实现原理4.1内置类型4.2自定义类型 五、 定位new表达式(placement-new)六、总结 一、C/C内存分布 一张图重温一下C/C内存分布。 二、C中动态内存管理 C中的…

计算物理专题:高维Romberg数值积分方法

有话无话,先上代码,正确与否,先给结论,可信有无,先出文献计算物理,傅哥最强 真计算还得看SCU物拔(不是)(狗头)(骄傲)这种方法的思想是…

Springboot +Flowable,会签、或签简单使用(三)

一.简介 **会签:**在一个流程中的某一个 Task 上,这个 Task 需要多个用户审批,当多个用户全部审批通过,或者多个用户中的某几个用户审批通过,就算通过。 例如:之前的请假流程,假设这个请假流程…

【笔试强训选择题】Day15.习题(错题)解析

作者简介:大家好,我是未央; 博客首页:未央.303 系列专栏:笔试强训选择题 每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!! 文章目录 前言 一、…

Linux Shell 实现一键部署二进制Python

python 前言 Python由荷兰数学和计算机科学研究学会的吉多范罗苏姆于1990年代初设计,作为一门叫做ABC语言的替代品。 Python提供了高效的高级数据结构,还能简单有效地面向对象编程。Python语法和动态类型,以及解释型语言的本质,使…

监控知识体系

从来没讲过运维,因为我觉得运维这种东西不需要太多的知识面,然后我一个做了运维朋友告诉我大错特错,他就是从3K的运维一步步到40K的,甚至笑着说:我现在感觉自己什么都能做。 既然讲,就讲最重要的吧。 监控…

接口自动化测试分层设计与实践总结

本文以笔者当前使用的自动化测试项目为例,浅谈分层设计的思路,不涉及到具体的代码细节和某个框架的实现原理,重点关注在分层前后的使用对比,可能会以一些伪代码为例来说明举例。 接口测试三要素: 参数构造 发起请求&a…