Shelled-out Commands In Golang

http://nathanleclaire.com/blog/2014/12/29/shelled-out-commands-in-golang/

Shelled-out Commands In Golang

The Nate Shells Out

In a perfect world we would have beautifully designed APIs and bindings for everything that we could possibly desire and that includes things which we might want to invoke the shell to do (e.g. run imagemagick commands, invoke git, invoke docker etc.). But especially with burgeoning languages such as Go, it’s not as likely that such a module exists (or that it’s easy to use, robust, well-tested, etc.) as it is with a more mature language such as Python. So, we might become shellouts.

What do you mean?

Go allows you to invoke commands directly from the language using some primitives defined in the os/exec package. It’s not as easy as it can be in, say, Ruby where you just backtick the command and read the output into a variable, but it’s not too bad. The basic usage is through the Cmd struct and you can invoke commands and do a variety of things with their results.

exec.Command() takes a command and its arguments as arguments and returns a Cmd struct. You can then call Run on that struct to actually run the command and wait for its results to get back. This can be condensed into a single line for brevity using Go’s multi-statement if style. Consider the following example where we can use Go to execute an imagemagick command to half the size of an image:

package main

import (
	"fmt"
	"os"
	"os/exec"
)

func main() {
	cmd := "convert"
	args := []string{"-resize", "50%", "foo.jpg", "foo.half.jpg"}
	if err := exec.Command(cmd, args...).Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println("Successfully halved image in size")
}

Cool, running shell commands in Go isn’t too bad. But what if we want to get the output, to display it or parse some information out of it? We can use Cmd struct’s Output method to get a byte slice. This is trivially convertable to a string if that is what you’re after, too.

package main

import (
	"fmt"
	"os"
	"os/exec"
)

func main() {
	var (
		cmdOut []byte
		err    error
	)
	cmdName := "git"
	cmdArgs := []string{"rev-parse", "--verify", "HEAD"}
	if cmdOut, err = exec.Command(cmdName, cmdArgs...).Output(); err != nil {
		fmt.Fprintln(os.Stderr, "There was an error running git rev-parse command: ", err)
		os.Exit(1)
	}
	sha := string(cmdOut)
	firstSix := sha[:6]
	fmt.Println("The first six chars of the SHA at HEAD in this repo are", firstSix)
}

Now show me something really cool.

OK, let’s look at an example of streaming the output of a command line-by-line for transformation. There are a variety of reasons why you might want to do this. You may want to append some logging output on the front of the line, which is the use case I will demonstrate here. You may want to apply some sort of transformation on the output as it is coming in. You may simply want to parse out the bits you are interested in and discard the rest, and it’s just a more natural fit to do so line-by-line instead of in one big string or byte slice. Or, you may want to just see the output of a long-running command as it comes in.

package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
)

func main() {
	// docker build current directory
	cmdName := "docker"
	cmdArgs := []string{"build", "."}

	cmd := exec.Command(cmdName, cmdArgs...)
	cmdReader, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for Cmd", err)
		os.Exit(1)
	}

	scanner := bufio.NewScanner(cmdReader)
	go func() {
		for scanner.Scan() {
			fmt.Printf("docker build out | %s\n", scanner.Text())
		}
	}()

	err = cmd.Start()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Error starting Cmd", err)
		os.Exit(1)
	}

	err = cmd.Wait()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Error waiting for Cmd", err)
		os.Exit(1)
	}
}

Come on, you can do better than that.

OK, how about writing an agnostic function to execute shell commands on a remote computer? With ssh and Cmd you can do it.

We could make a simple struct called SSHCommander where you pass user and server IP. Then you invoke Command to run commands over SSH! If your keys are in alignment, it will work.

package main

import (
	"fmt"
	"os"
	"os/exec"
)

type SSHCommander struct {
	User string
	IP   string
}

func (s *SSHCommander) Command(cmd ...string) *exec.Cmd {
	arg := append(
		[]string{
			fmt.Sprintf("%[email protected]%s", s.User, s.IP),
		},
		cmd...,
	)
	return exec.Command("ssh", arg...)
}

func main() {
	commander := SSHCommander{"root", "50.112.213.24"}

	cmd := []string{
		"apt-get",
		"install",
		"-y",
		"jq",
		"golang-go",
		"nginx",
	}

	// am I doing this automation thing right?
	if err := commander.Command(cmd...); err != nil {
		fmt.Fprintln(os.Stderr, "There was an error running SSH command: ", err)
		os.Exit(1)
	}
}

I stole this idea from the work we’ve been doing lately on Docker machine. Good times.

What’s the downside?

I’m glad you asked. There are a few notable downsides. One, it’s pretty hacky and inelegant to do this. Ideally one would have clearly defined APIs or bindings to use that would mitigate the need to shell out commands. Maintaining code which shells out commands will be a maintainability headache (commands often fail in opaque ways) and will be harder to grok for newcomers to the codebase (or yourself after a break) due to its lack of concision and clarity.

It definitely breaks cross-platform compatibility and repeatability. If the user doesn’t have the program you’re expecting, or doesn’t have it named correctly, etc., you’re hosed. Additionally, it won’t end well to make assumptions that this program will be run in a UNIX shell if you eventually want a cross-platform Go binary: so be careful about pipes and the like.

However, it’s pretty fun when it works. So just be prepared to accept the consequences if you do it.

Fin

That’s all: have fun doing shelly things in Go-land everyone.

And until next time, stay sassy Internet.

  • Nathan
时间: 2024-10-11 12:13:16

Shelled-out Commands In Golang的相关文章

Go语言(golang)开源项目大全

转http://www.open-open.com/lib/view/open1396063913278.html内容目录Astronomy构建工具缓存云计算命令行选项解析器命令行工具压缩配置文件解析器控制台用户界面加密数据处理数据结构数据库和存储开发工具分布式/网格计算文档编辑器Encodings and Character SetsGamesGISGo ImplementationsGraphics and AudioGUIs and Widget ToolkitsHardwareLangu

golang学习之旅:搭建go语言开发环境

从今天起,将学习go语言.今天翻了一下许式伟前辈写的<Go语言编程>中的简要介绍:Go语言——云计算时代的C语言.前面的序中介绍了Go语言的很多特性,很强大,迫不及待地想要一探究竟,于是便问道Go语言.很幸运地发现了无闻大师已录制了一套针对新手的Go语言入门教程,深表感谢!教程在这儿. 官方网址是:https://golang.org/(呃,很无辜地被屏蔽了,办法看这里) 这是官网首页的介绍.The Go Programming Language Go is an open source pr

Golang、Php、Python、Java基于Thrift0.9.1实现跨语言调用

目录: 一.什么是Thrift? 1) Thrift内部框架一瞥 2) 支持的数据传输格式.数据传输方式和服务模型 3) Thrift IDL 二.Thrift的官方网站在哪里? 三.在哪里下载?需要哪些组件的支持? 四.如何安装? 五.Golang.Java.Python.PHP之间通过Thrift实现跨语言调用 1) Golang 客户端和服务端的实现及交互 2) python 客户端的实现与golang 服务端的交互 3) php 客户端的实现与golang 服务端的交互 4) java

Golang官方依赖管理工具:dep

在这里声明一下,百度或者google看到的godep不是我这篇博文说的dep,那它们是什么关系呢?按照Peter Bourgon博文来说,它们的作者都有相同的人,但是一个是dep是官方版本,godep是第三方工具.我今天介绍的是dep,之前也有介绍过glide,有兴趣的可以到Golang依赖管理工具:glide从入门到精通使用看看. 现在还有一个疑问是为什么官方现在要支持依赖管理了呢?我个人认为有如下原因(勿喷,如果不同或者遗漏欢迎留言补充): 第三方依赖管理很多,虽然很好用,但是很少可以兼容的

把vim当做golang的IDE

开始决定丢弃鼠标,所以准备用vim了. 那么在vim里面如何搭建golang环境呢? git盛行之下,搭建vim环境是如此简单. 而且vim搭建好了之后,基本上跟IDE没有差别. 高亮.自动补全.自动格式化.查看定义跳转.语法检测等等等等, 简直是不要不要的: Improved Syntax highlighting with items such as Functions, Operators, Methods. Auto completion support via gocode Bette

在树莓派上构建Golang及Redis环境

今天翻了翻旧资料,发现点东西.之前公司服务器还没就位(初创公司没办法)时. 正讨论服务器的事,有同事拿出了他的Raspberry Pi. 我一想,也行.Go本来就支持多平台.Redis更是C语言写的,应当不成问题. 最后,虽然中间走了点弯路,不过还好,能跑起来. Golang: 1. 从官网去下载 1.4.2的源码包. 不要直接从github下载或用1.5版本的源码.这两个在Pi上都会有问题. 2. 执行下面命令即可. [email protected] ~/go1.4.2/src $ sudo

Golang使用pprof和qcachegrind进行性能监控

Golang为我们提供了非常方便的性能测试工具pprof,使用pprof可以非常方便地对Go程序的运行效率进行监测.本文讲述如何使用pprof对Go程序进行性能测试,并使用qcachegrind查看性能测试的输出文件. 载入pprof模块 想要对一个Go程序进行pprof监测,第一步是在main函数所在的模块中添加 net/http/pprof 模块.import后面的“_”是一定要加上的. import _ "net/http/pprof" 运行HTTP服务器 如果你的程序不是一个W

golang第一篇

介绍 Go是Google开发的一种编译型,可平行化,并具有垃圾回收功能的编程语言. 罗布·派克(Rob Pike),罗伯特·格瑞史莫(Robert Griesemer),及肯·汤普逊(unix的创造者)于2007年9月开始设计Go语言,稍后Ian Lance Taylor, Russ Cox加入项目中.Go语言是基于Inferno操作系统所开发的.Go语言于2009年11月正式宣布推出,成为开放源代码项目,并在Linux及Mac OS X平台上进行了实现,后追加Windows系统下的实现. 描述

[golang]内存不断增长bytes.makeSlice

golang写的一个图片服务器,在批量下载压缩时候发现内存不断增长.... 幸好golang自带内存占用日志结合分析工具可以方便看到内存分布. 详细可参考: http://blog.golang.org/profiling-go-programs 可以实时统计CPU\内存信息. 这里主要说一下内存怎么搞.CPU分析的参考之前的一篇文章. //需要包含这个pprof包 import "runtime/pprof" //这里接收内存统计信息保存文件 var memprofile = fla