Building Command Line Tools in Go
by Jogendra
Command line interface (CLI) tools are an essential part of any developer. CLI tools are lightweight, less noisy, fun to use and of course they make you more productive. Everyday I try to be more dependent on Terminal (I use iTerm) and get rid of GUI applications, simply because I am more focused using a tool that provide me everything with just some commands/scripts than some fancy GUI application that come with fancy popups, ads, pictures which add noise to my focus.
In this example, we will create a simple (but scalable to any level) bookmark CLI tool in Go using Cobra that can do basic operations like adding a new bookmark, listings, clear etc. I will try to keep it as much detailed as possible.
Why Go?
Go brings a whole set of advantages. Go compiles very quickly into a single binary, works across platforms with a consistent style, and brings a strong development community. Go applications are built into a single self contained binary making installing Go applications trivial. Easy concurrency model makes Go easy to progress with multiple tasks at the same time. Concurrency is an integral part of Go (Selling point of Go :P), supported by goroutines, channels. Go also provides backward compatibility. The list is long, let’s move on to the next part.
If you aren’t familiar with Go, you simply go through Go Tour for basic understandings.
Go is simply great to build CLI tools. Go might be the fastest and perhaps the most fun way to automate tasks. With Go, you can develop cross-platform command-line tools that are fast and reliable. There are many good libraries out there for building CLI tools and one of such library is Cobra, which we will using in this example project.
Before we start
Before we proceed any further, install Go on your machine, if you have not already installed so. For this, you can follow the installation procedure on the official Golang website. It is good to have recent version of Go on your machine. You can simply execute go version
to check Go version installed on your machine.
Setting up environment variables
Environment variables, are variables in your system that describe your environment. They are executed and added to your machine environment before you access/use them. During the Go installation, some Go specific environment variables are set to configure behavior of Go tools on your machine. You can check Go environment variables by running go env
.
In your .zshrc
or .bashrc
file, append your system’s $PATH so that we can invoke bookmark
command from anywhere.
export PATH=${PATH}:$HOME/go/bin
PATH=${PATH}:path/to/folder
is an interesting thing of unix, I will keep it for some other blog post to explain it in details.
Restart your terminal once to source them into your machine environment or just source ~/.zshrc
.
Playing with Cobra
Cobra is widely popular Go package and many projects use it for building CLI tools. Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files. Many of the most widely used Go projects are built using Cobra such as Kubernetes, Hugo, Docker (distribution), GitHub CLI and many more big names.
Cobra is built on a structure of commands, arguments and flags. Commands represent actions, Args are things and Flags are modifiers for those actions. APPNAME is the name of your tool.
APPNAME COMMAND ARG --FLAG
For example:
git clone URL --bare
Cobra provides a lot of feature, you can check them out here.
Installing Cobra
You can simply use go get
to install the latest version of Cobra. This command will install the cobra generator executable along with the library and its dependencies.
go get -u github.com/spf13/cobra/cobra
The cobra binary is now in the bin/
(usually ~/go/bin/cobra
) directory, which is itself in your PATH
, so it can be used directly from anywhere. You can run cobra help
or just cobra
to get more familiar with it.
Let’s get started
First step to generate a basic organizational structure that cobra follows. Cobra provides its own program that will create your application and add any commands you want. It’s the easiest way to incorporate Cobra into your application. Run this in your newly created project directory:
cobra init --pkg-name bookmark
Here bookmark is our tool name. This will generate basic structure which will look like:
├── LICENSE
├── cmd
│ └── root.go
└── main.go
A package named cmd is created which contains all of your commands files. main.go
is the entry point of your tool, it export cmd package and run Execute()
func from cmd/root.go
which execute the root command (base command of your tool, bookmark
in this example).
rootCmd
represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{
Use: "bookmark",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines
and likely contains examples and usage of using
your application.`,
}
Here is how main.go
look like:
package main
import "bookmark/cmd"
func main() {
cmd.Execute()
}
We are importing bookmark/cmd
inside our main.go
. Let’s create bookmark
module before building and using it.
go mod init bookmark
It will create go.mod
file which look like:
module bookmark
go 1.14
require (
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.7.0
)
Now, let’s just build this module with:
go build
This build a binary for bookmark
module and generate go.sum
for the same.
Run the command below to install the module so that we can run bookmark
command.
go install bookmark
Now, if you run bookmark
inside your terminal, it will show the details about the tool, available commands, flags etc. You can notice that it already has help
command, Cobra created that for us.
A Command Line tool to manage all of your bookmarks at one place.
Usage:
bookmark [command]
Available Commands:
help Help about any command
Flags:
--config string config file (default is $HOME/.bookmark.yaml)
-h, --help help for bookmark
-t, --toggle Help message for toggle
Use "bookmark [command] --help" for more information about a command.
Here config file is simple configuration file which will help you eliminate providing a bunch of repeated information in flags over and over. You can check out more about it here.
Creating our first command
We are done with initial setups. Now, let’s create the first command of our tool. With cobra add
, you can generate new command.
Cobra add <command>
We will create a command called insert
which we will use to add a new bookmark to the bookmarks list.
Cobra add insert
This will create a new go file insert.go
to cmd with the initial format. We will simply store bookmarks into a text file located at ~/.bookmarks/bookmarks.txt
.
package cmd
import (
"os"
"os/exec"
"github.com/spf13/cobra"
)
// insertCmd represents the insert command
var insertCmd = &cobra.Command{
Use: "insert",
Short: "Add a new bookmark to bookmarks list",
Long: `When you run this command with a link or text,
it add that link/text to your bookmarks list`,
Run: func(cmd *cobra.Command, args []string) {
homeDir, _ := os.UserHomeDir()
bookmarkFile := homeDir + "/.bookmarks/bookmarks.txt"
if _, err := os.Stat(bookmarkFile);
os.IsNotExist(err) {
os.Mkdir(homeDir + "/.bookmarks", 0700)
os.Create(homeDir + "/.bookmarks/bookmarks.txt")
}
f, _ := os.OpenFile(bookmarkFile,
os.O_APPEND|os.O_WRONLY,
os.ModeAppend)
f.WriteString(args[0] + "\n")
f.Close()
},
}
func init() {
rootCmd.AddCommand(insertCmd)
}
Every command will have a kind of similar structure to this. The Run
inside the insertCmd
is called when you run bookmark insert
so we have implemented very simple functionality inside it. It simply checks if ~/.bookmarks/bookmarks.txt
exist, if not, create one otherwise add this new bookmark text/link (which we will provide in argument) to the database which is simply a text file.
Now, you can use insert
command like this:
bookmark insert "this is my first bookmark, i love this tool"
Woah! You just created your first command successfully. Easy?
Adding more commands
We will add couple of more commands to our tool. The procedure is same as what we followed in creating our first command.
cobra add list
cobra add clearall
We will use our newly created list
command to display all the bookmarks in our bookmark database and clearall
to clear all the bookmarks from databse. You can check out cmd/list.go
and cmd/clearall.go
files.
Now, if you run bookmark help
in terminal, it will show newly added commands too. Make sure you run go install bookmark
to see new changes.
Implementing Subcommands
In this part, we will add a subcommand last
to our bookmark list
command. This subcommand will display the last most added bookmark. The cobra command for creating subcommand is like below:
cobra add <subcommand> -p <parent command>
Let’s add last
subcommand to list
with:
cobra add last -p listCmd
listCmd
is internal representation of list
command.
Our implementation for last
subcommand look like this in cmd/last.go
file:
package cmd
import (
"fmt"
"os"
"bufio"
"github.com/spf13/cobra"
)
// lastCmd represents the last command
var lastCmd = &cobra.Command{
Use: "last",
Short: "Show the last most bookmark added",
Long: `Show the last most bookmark added from the bookmarks list`,
Run: func(cmd *cobra.Command, args []string) {
homeDir, _ := os.UserHomeDir()
if _, err := os.Stat(homeDir + "/.bookmarks/bookmarks.txt");
os.IsNotExist(err) {
fmt.Println("Your bookmarks list is empty")
return
}
f, _ := os.Open(homeDir + "/.bookmarks/bookmarks.txt")
defer f.Close()
scanner := bufio.NewScanner(f)
var lastLine string
for scanner.Scan() {
lastLine = scanner.Text()
}
fmt.Println(">", lastLine)
},
}
func init() {
listCmd.AddCommand(lastCmd)
}
Now, if we run bookmark list last
, it will display the last bookmark added. Don’t forget to rebuild binary with go install bookmark
before running command. You can check newly added subcommand on list
by running bookmark list --help
The last
subcommand is considered a command line argument to the list
command. Similarly, you can add subcommands to other commands and to subcommands itself.
Next, you can implement/add flags to your commands. It is very easy to working with flags in Cobra. You can follow their Readme/docs on flags. What we done is very basic, you can do lot with Cobra.
Source Code Sturcture
After implementing commands and subcommands, project structure looks like below:
.
├── LICENSE
├── README.md
├── cmd
│ ├── clearall.go
│ ├── insert.go
│ ├── last.go
│ ├── list.go
│ └── root.go
├── go.mod
├── go.sum
└── main.go
1 directory, 10 files
Run go install bookmark
to rebuild binary.
Now, if you run bookmark help
in terminal, it will look like this:
Full source code: jogendra/bookmark-cli
Wrapping up
So now you have successfully made a user-friendly CLI tool which can perform basic operations. You can check out the full source code at jogendra/bookmark-cli. You have seen how easy and scalable it is to make a CLI tool in Go using Cobra. Go is simply great language choice to build CLI tools and Cobra is undoubtly first choice. Main aim is to create basic understanding. I will keep updating it with more basic operations if it requires. Thanks for reading. Looking forword to see what you are building.
Subscribe via RSS