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

最新文章

  1. 设计模式学习之迭代器模式(Iterator,行为型模式)(17)
  2. js获取浏览器地址
  3. Process启动.exe,当.exe内部抛出异常时,总会弹出一个错误提示框,阻止Process进入结束
  4. jsp自定义标签1
  5. Java 并发编程实战 摘要
  6. linux关机和重启的命令[转]
  7. 基于HBase0.98.13搭建HBase HA分布式集群
  8. asp.net中WebForm.aspx与类文件分离使用
  9. IOS 表视图UITableView 束NSBundle
  10. oracle之case when
  11. Ultra-QuickSort(归并排序+离散化树状数组)
  12. TextBox自定义Mac输入框类
  13. react学习笔记2--练习Demos
  14. struct的匿名用法详解
  15. Cassandra数据模型
  16. python之socket模块详解--小白博客
  17. Notepad++找回Plugin Manager{在v7.50后(包括7.50)不带有插件管理器(Plugin Manager)}
  18. 11G新特性 -- 分区表和增量统计信息
  19. 唯美MACD
  20. tensorflow--variable_scope

热门文章

  1. Android项目执行时报错NoclassDefFoundError
  2. 【Oracle错误集锦】:ORA-00119 & ORA-00132
  3. hadoop(八) - sqoop安装与使用
  4. poj2002 哈希
  5. IBM软件技术峰会归来
  6. Sub Thread to update main Thread (UI) 2
  7. XML获取节点信息值
  8. Python基础班培训视频课程
  9. HRBUST 1819 石子合并问题--圆形版
  10. 学一下gconv, gprof等知识