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的更多相关文章

  1. golang的安装

    整理了一下,网上关于golang的安装有三种方式(注明一下,我的环境为CentOS-6.x, 64bit) 方式一:yum安装(最简单) rpm -Uvh http://dl.fedoraprojec ...

  2. [goa]golang微服务框架学习--安装使用

      当项目逐渐变大之后,服务增多,开发人员增加,单纯的使用go来写服务会遇到风格不统一,开发效率上的问题. 之前研究go的微服务架构go-kit最让人头疼的就是定义服务之后,还要写很多重复的框架代码, ...

  3. 把vim当做golang的IDE

    开始决定丢弃鼠标,所以准备用vim了. 那么在vim里面如何搭建golang环境呢? git盛行之下,搭建vim环境是如此简单. 而且vim搭建好了之后,基本上跟IDE没有差别. 高亮.自动补全.自动 ...

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

    从今天起,将学习go语言.今天翻了一下许式伟前辈写的<Go语言编程>中的简要介绍:Go语言——互联网时代的C语言.前面的序中介绍了Go语言的很多特性,很强大,迫不及待地想要一探究竟,于是便 ...

  5. PHP和Golang使用Thrift1和Thrift2访问Hbase0.96.2(ubuntu12.04)

    目录: 一.Thrift1和Thrift2的简要介绍 1) 写在前面 2) Thrift1和Thrift2的区别  二.Thrift0.9.2的安装 1) 安装依赖插件 2) Thrift0.9.2的 ...

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

    目录: 一.什么是Thrift? 1) Thrift内部框架一瞥 2) 支持的数据传输格式.数据传输方式和服务模型 3) Thrift IDL 二.Thrift的官方网站在哪里? 三.在哪里下载?需要 ...

  7. supervisor运行golang守护进程

    最近在鼓捣golang守护进程的实现,无意发现了supervisor这个有意思的东西.supervisor是一个unix的系统进程管理软件,可以用它来管理apache.nginx等服务,若服务挂了可以 ...

  8. win下 golang 跨平台编译

    mac 下编译其他平台的执行文件方式请参看这篇文章,http://www.cnblogs.com/ghj1976/archive/2013/04/19/3030703.html  本篇文章是win下的 ...

  9. golang安装卸载 linux+windows+raspberryPI 平台

    参考  https://golang.org/doc/install 自ECUG2013洗脑回来,就渴望早点接触Go 听着许式伟和谢孟军的演讲 发现go的网络库的确很强大,高负载利器,语言的一些精简导 ...

随机推荐

  1. java中string与json互相转化

    在Java中socket数据传输时,数据类型往往比較难选择.可能要考虑带宽.跨语言.版本号的兼容等问题. 比較常见的做法有两种:一是把对象包装成JSON字符串传输,二是採用java对象的序列化和反序列 ...

  2. JVM-ClassLoader装载class的流程

    在JVM中,有三种默认的类加载器,分别为Bootstrap ClassLoader,Extension CLassLoader以及App ClassLoader.其中,Bootstrap Classl ...

  3. 负载均衡-lvs

    常用的负载均衡技术比较DNS 轮询DNS本身的机制不再赘述,这里主要看一看基于DNS的负载均衡,其大致原理很清楚,DNS系统本身支持同一个域名映射到多个ip (A记录),例如 这样每次向DNS系统询问 ...

  4. 快速select算法的实现

    代码来自: http://blog.csdn.net/v_JULY_v 算法思想: // Quick_select.cpp : 定义控制台应用程序的入口点. // #include "std ...

  5. Android使用token维持登陆状态的方法

    什么是token token(令牌)是一串唯一的字符串,通常由服务端生成,在注册完成时返回给客户端,用来标识此用户,客户端将此字符串存储在本地.在以后的网络请求时,客户端先查询本地的token,如果有 ...

  6. Atcoder B - Moderate Differences

    http://agc017.contest.atcoder.jp/tasks/agc017_b B - Moderate Differences Time limit : 2sec / Memory ...

  7. JS的解析与执行过程—全局预处理阶段之全局词法环境对象

    问题:有如下代码 var a = 1; function pop() { alert(a); var a = 5; } pop();//执行结果,弹出undefined 这段代码的执行结果为undef ...

  8. angularjs 页面缓存及动态刷新解决方案

    一.准备工作 框架:angularjs ui组件库:ionic1 二.页面缓存cache 路由设置cache参数,true为缓存,false为不缓存,代码如下: angular.module('app ...

  9. Metro界面的真正意义

    昨天去客户那给安装防火墙和交换机,因为客户和我们公司签订了维保的合同,然后我们公司两个人去了客户那跟客户沟通也去顺路去做巡检. 客户之前跟我们公司采购了一台DELL的PC服务器,预装了win serv ...

  10. python note #3

    Hello, guys! I found it pretty difficult to get my content according to my key words. So in this not ...