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的网络库的确很强大,高负载利器,语言的一些精简导 ...
随机推荐
- hadoop集群中动态添加新的DataNode节点
集群中现有的计算能力不足,须要另外加入新的节点时,使用例如以下方法就能动态添加新的节点: 1.在新的节点上安装hadoop程序,一定要控制好版本号,能够从集群上其它机器cp一份改动也行 2.把name ...
- Create the Data Access Layer
https://docs.microsoft.com/en-us/aspnet/web-forms/overview/getting-started/getting-started-with-aspn ...
- LSTM入门学习——本质上就是比RNN的隐藏层公式稍微复杂了一点点而已
LSTM入门学习 摘自:http://blog.csdn.net/hjimce/article/details/51234311 下面先给出LSTM的网络结构图: 看到网络结构图好像很复杂的样子,其实 ...
- Docker -- 系统整洁之道 -- 1
在上文Docker – 系统整洁之道 – 0中已经对Docker是什么,安装Docker以及怎么运行一个简单的容器有了初步了解,这篇文章介绍Docker的一些命令和Docker镜像的使用及操作. 一些 ...
- Container详解
Container是一个拥有绘制.定位.调整大小的widget. padding和margin padding和margin分别设置Container的内边距和外边距.可取值包括下面四个: EdgeI ...
- jquery.base64.js
(function($) { $.base64 = function(options) { var defaults = { data:"", type:0, unicode:tr ...
- Centos7最小化安装后再安装图形界面
1. yum -y groupinstall "X Window System" 2. yum -y groupinstall "GNOME Desktop" ...
- TCP简单说(下)
本文在Creative Commons许可证下发布 TCP的RTT算法 从前面的TCP重传机制我们知道Timeout的设置对于重传非常重要. 设长了,重发就慢,丢了老半天才重发,没有效率,性能差: 设 ...
- CentOS下部署巡风步骤详解
本博客已经迁移到新的网站,www.je2se.com,请大家移步关注,互相交流,共同成长 巡风Centos 6.5部署指南 基础环境要求: Python2.7+ 安装Centos相关依赖 # Cent ...
- headset-监听有线耳机插拔
今天在做项目的时候,需要对耳机的插拔事件进行监听,所以就写了如下的一个小demo,对耳机监听事件进行验证.直接看代码 package com.example.alert; import android ...