basher

package
v1.11.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2015 License: MIT, Apache-2.0 Imports: 9 Imported by: 0

README

go-basher

A Go library for creating Bash environments, exporting Go functions in them as Bash functions, and running commands in that Bash environment. Combined with a tool like go-bindata, you can write programs that are part written in Go and part written in Bash that can be distributed as standalone binaries.

Build Status GoDoc

Using go-basher

Here we have a simple Go program that defines a reverse function, creates a Bash environment sourcing main.bash and then runs main in that environment.

package main

import (
	"os"
	"io/ioutil"
	"log"
	"strings"

	"github.com/progrium/go-basher"
)

func reverse(args []string) int {
	bytes, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		log.Fatal(err)
	}
	runes := []rune(strings.Trim(string(bytes), "\n"))
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	println(string(runes))
	return 0
}

func main() {
	bash, _ := basher.NewContext("/bin/bash", false)
	bash.ExportFunc("reverse", reverse)
	bash.HandleFuncs(os.Args)

	bash.Source("main.bash", nil)
	status, err := bash.Run("main", os.Args[1:])
	if err != nil {
		log.Fatal(err)
	}
	os.Exit(status)
}

Here is our main.bash file, the actual heart of the program:

main() {
	echo "Hello world" | reverse
}

Using go-basher with go-bindata

You can bundle your Bash scripts into your Go binary using go-bindata. First install go-bindata:

$ go get github.com/jteeuwen/go-bindata/...

Now put all your Bash scripts in a directory called bash. The above example program would mean you'd have a bash/main.bash file. Run go-bindata on the directory:

$ go-bindata bash

This will produce a bindata.go file that includes all of your Bash scripts. It includes a function called Asset that behaves like ioutil.ReadFile for files in your bindata.go. You can just pass this into the Source function when sourcing files. From the above example program, you'd use this line instead:

bash.Source("bash/main.bash", Asset)

If you get ambitious you could also use go-bindata to pack a static version of the Bash binary into your Go binary. Crazy, but cool?

Motivation

Go is a great compiled systems language, but it can still be faster to write and glue existing commands together in Bash. However, there are operations you wouldn't want to do in Bash that are straightforward in Go, for example, writing and reading structured data formats. By allowing them to work together, you can use each where they are strongest.

Take a common task like making an HTTP request for JSON data. Parsing JSON is easy in Go, but without depending on a tool like jq it is not even worth trying in Bash. And some formats like YAML don't even have a good jq equivalent. Whereas making an HTTP request in Go in the simplest case is going to be 6+ lines, as opposed to Bash where you can use curl in one line. If we write our JSON parser in Go and fetch the HTTP doc with curl, we can express printing a field from a remote JSON object in one line:

curl -s https://api.github.com/users/progrium | parse-user-field email

In this case, the command parse-user-field is an app specific function defined in your Go program.

Why would this ever be worth it? I can think of several basic cases:

  1. you're writing a program in Bash that involves some complex functionality that should be in Go
  2. you're writing a CLI tool in Go but, to start, prototyping would be quicker in Bash
  3. you're writing a program in Bash and want it to be easier to distribute, like a Go binary

License

BSD

Documentation

Overview

Package basher provides an API for running and integrating with Bash from Go

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Application

func Application(
	funcs map[string]func([]string),
	scripts []string,
	loader func(string) ([]byte, error),
	copyEnv bool)

Application sets up a common entrypoint for a Bash application that uses exported Go functions. It uses the DEBUG environment variable to set debug on the Context, and SHELL for the Bash binary if it includes the string "bash". You can pass a loader function to use for the sourced files, and a boolean for whether or not the environment should be copied into the Context process.

Types

type Context

type Context struct {
	sync.Mutex

	// Debug simply leaves the generated BASH_ENV file produced
	// from each Run call of this Context for debugging.
	Debug bool

	// BashPath is the path to the Bash executable to be used by Run
	BashPath string

	// SelfPath is set by NewContext to be the current executable path.
	// It's used to call back into the calling Go process to run exported
	// functions.
	SelfPath string

	// The io.Reader given to Bash for STDIN
	Stdin io.Reader

	// The io.Writer given to Bash for STDOUT
	Stdout io.Writer

	// The io.Writer given to Bash for STDERR
	Stderr io.Writer
	// contains filtered or unexported fields
}

A Context is an instance of a Bash interpreter and environment, including sourced scripts, environment variables, and embedded Go functions

func NewContext

func NewContext(bashpath string, debug bool) (*Context, error)

Creates and initializes a new Context that will use the given Bash executable. The debug mode will leave the produced temporary BASH_ENV file for inspection.

func (*Context) CopyEnv

func (c *Context) CopyEnv()

Copies the current environment variables into the Context

func (*Context) Export

func (c *Context) Export(name string, value string)

Adds an environment variable to the Context

func (*Context) ExportFunc

func (c *Context) ExportFunc(name string, fn func([]string))

Registers a function with the Context that will produce a Bash function in the environment that calls back into your executable triggering the function defined as fn.

func (*Context) HandleFuncs

func (c *Context) HandleFuncs(args []string) bool

Expects your os.Args to parse and handle any callbacks to Go functions registered with ExportFunc. You normally call this at the beginning of your program. If a registered function is found and handled, HandleFuncs will exit with the appropriate exit code for you.

func (*Context) Run

func (c *Context) Run(command string, args []string) (int, error)

Runs a command in Bash from this Context. With each call, a temporary file is generated used as BASH_ENV when calling Bash that includes all variables, sourced scripts, and exported functions from the Context. Standard I/O by default is attached to the calling process I/O. You can change this by setting the Stdout, Stderr, Stdin variables of the Context.

func (*Context) Source

func (c *Context) Source(filepath string, loader func(string) ([]byte, error)) error

Adds a shell script to the Context environment. The loader argument can be nil which means it will use ioutil.Readfile and load from disk, but it exists so you can use the Asset function produced by go-bindata when including script files in your Go binary. Calls to Source adds files to the environment in order.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL