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("%s@%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
Shelled-out Commands In Golang的更多相关文章
- golang的安装
整理了一下,网上关于golang的安装有三种方式(注明一下,我的环境为CentOS-6.x, 64bit) 方式一:yum安装(最简单) rpm -Uvh http://dl.fedoraprojec ...
- [goa]golang微服务框架学习--安装使用
当项目逐渐变大之后,服务增多,开发人员增加,单纯的使用go来写服务会遇到风格不统一,开发效率上的问题. 之前研究go的微服务架构go-kit最让人头疼的就是定义服务之后,还要写很多重复的框架代码, ...
- 把vim当做golang的IDE
开始决定丢弃鼠标,所以准备用vim了. 那么在vim里面如何搭建golang环境呢? git盛行之下,搭建vim环境是如此简单. 而且vim搭建好了之后,基本上跟IDE没有差别. 高亮.自动补全.自动 ...
- golang学习之旅:搭建go语言开发环境
从今天起,将学习go语言.今天翻了一下许式伟前辈写的<Go语言编程>中的简要介绍:Go语言——互联网时代的C语言.前面的序中介绍了Go语言的很多特性,很强大,迫不及待地想要一探究竟,于是便 ...
- PHP和Golang使用Thrift1和Thrift2访问Hbase0.96.2(ubuntu12.04)
目录: 一.Thrift1和Thrift2的简要介绍 1) 写在前面 2) Thrift1和Thrift2的区别 二.Thrift0.9.2的安装 1) 安装依赖插件 2) Thrift0.9.2的 ...
- Golang、Php、Python、Java基于Thrift0.9.1实现跨语言调用
目录: 一.什么是Thrift? 1) Thrift内部框架一瞥 2) 支持的数据传输格式.数据传输方式和服务模型 3) Thrift IDL 二.Thrift的官方网站在哪里? 三.在哪里下载?需要 ...
- supervisor运行golang守护进程
最近在鼓捣golang守护进程的实现,无意发现了supervisor这个有意思的东西.supervisor是一个unix的系统进程管理软件,可以用它来管理apache.nginx等服务,若服务挂了可以 ...
- win下 golang 跨平台编译
mac 下编译其他平台的执行文件方式请参看这篇文章,http://www.cnblogs.com/ghj1976/archive/2013/04/19/3030703.html 本篇文章是win下的 ...
- golang安装卸载 linux+windows+raspberryPI 平台
参考 https://golang.org/doc/install 自ECUG2013洗脑回来,就渴望早点接触Go 听着许式伟和谢孟军的演讲 发现go的网络库的确很强大,高负载利器,语言的一些精简导 ...
随机推荐
- 同一个事务里 查询 已删除可是未提交的数据[bug记录]
前几天犯了个低级错误.在一个事务方法里老是查询不到某条记录,可是debug卡住时,用db工具查.又能查出值. 经过一番折腾,原来是我在同一个事务里 查询 了已删除可是未提交的数据.当然查询不到了! . ...
- 算法导论————KMP
[例题传送门:caioj1177] KMP模版:子串是否出现 [题意]有两个字符串SA和SB,SA是母串,SB是子串,问子串SB是否在母串SA中出现过.如果出现过输出第一次出现的起始位置和结束位置,否 ...
- VS 2015支持C语言和C++程序
先要安装C++的相关支持控件! 然后就可以使用VS编写C++或者C程序了. 默认支持的是C++,将后缀名改为C就是支持C了. 学习数据结构算法之类的,就可以通过VS来学习了. 安装 新建C++项目 C ...
- 简单的字符串压缩--C代码
#include <stdio.h> #include <string.h> bool compress(char *str) { char *p=str,c; ; if(!s ...
- 如何更改AD域安全策略-密码必须符合复杂性要求
通常我们在域系统-管理工具上面是找不到“域安全策略”的,我们只能找到“本地安全策略”,而更改“本地安全策略”是不会对域产生任何的作用的.下面这个步骤教你如何找到“域安全策略” 1.Start(开始)– ...
- POJ 3641 Pseudoprime numbers (miller-rabin 素数判定)
模板题,直接用 /********************* Template ************************/ #include <set> #include < ...
- css line-height详解
行高指的是文本行的基线间的距离(更简单来说,行高是指文字尺寸与行距之间的和). 而基线(Base line),指的是一行字横排时下沿的基础线, 基线并不是汉字的下端沿,而是英文字母x的下端沿,同时还有 ...
- jQuery中四种事件监听的区别
原文链接:点我 我们知道jquery提供了四种事件监听方式,分别是bind.live.delegate.on,下面就分别对这四种事件监听方式分析. 已知有4个列表元素: 列表元素1 列表元素2 列表元 ...
- 今日SGU 5.18
SGU 125 题意:给你一个数组b[i][j],表示i,j的四周有多少个数字大于它的,问你能不能构造出一个a矩形 收获:dfs + 剪枝 一行一行的dfs,然后第一行去枚举0-9,下一行判断当前选 ...
- 【Uva 1336】Fixing the Great Wall
[Link]: [Description] 给你长城上的n个修补点,然后你的位置为x; 你需要依次去这n个点,然后把它们全部修好. 但是修的前后顺序不一样的话,花费不一样. 如果立即把第i个点修好的话 ...