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的网络库的确很强大,高负载利器,语言的一些精简导 ...
随机推荐
- QlikView随意改变图例的位置
组里面花了大价钱请人设计了一套UI的solution,只是是以站点思路设计的报表样式,可是该报表UI设计团队本身因为没有QlikView的背景,因此设计出来的报表不知道能不能再QlikView中实现, ...
- 1sting
You will be given a string which only contains ‘1’; You can merge two adjacent ‘1’ to be ‘2’, or lea ...
- DevExpress Report打印边距越界问题
DevExpress Report Print的时候,出现这样的问题:one or more margins are set outside the printable area of the pa ...
- vue抽取公共方法———方法一
方法一:Vue插件 1.概述 作用:满足vue之外的需求,特定场景的需求 比如说,让你在每个单页面组件里,都可以调用某个方法(公共方法),或者共享某个变量等 2.使用方法 [声明插件]- [写插件]- ...
- 【Linux下用户和组管理】
创建用户--useradd . 命令格式:useradd [参数] 用户名 useradd也可写成adduser . 参数如下 -u 指定UID号 -d 指定宿主目录 -e 指定生效时间 -g 指定基 ...
- 解决MyEclipse中安装或升级ADT之后SDK Target无法显示的问题
故障现象,在MyEclipse里面安装完最新的android sdk和ADT之后,无法新建项目,Build Target为空,显示一直在loading.即如下面图里面显示的,Target Na ...
- UVA 11642 Fire!
Fire! Time Limit: 1000ms Memory Limit: 131072KB This problem will be judged on UVA. Original ID: 116 ...
- 洛谷—— P1328 生活大爆炸版石头剪刀布
https://www.luogu.org/problem/show?pid=1328 题目描述 石头剪刀布是常见的猜拳游戏:石头胜剪刀,剪刀胜布,布胜石头.如果两个人出拳一样,则不分胜负.在< ...
- WebCollector爬取百度搜索引擎样例
使用WebCollector来爬取百度搜索引擎依照关键字搜索的结果页面,解析规则可能会随百度搜索的改版而失效. 代码例如以下: package com.wjd.baidukey.crawler; im ...
- androidclient和站点数据交互的实现(基于Http协议获取数据方法)
androidclient一般不直接訪问站点数据库,而是像浏览器一样发送get或者post请求.然后站点返回client能理解的数据格式,client解析这些数据.显示在界面上.经常使用的数据格式是x ...