Joseph Jude

Writing command line programs in Golang

2018.06.20 / code / golang /

We will write a command-line calculator using Golang in this tutorial.

Command Line Program in Golang

I have been using Golang for writing web applications. It is equally good for writing command line applications. Some of the reasons are:

  1. Easy to learn. The language itself can be learned quickly. Go through Go By Example and you will know the language. You can learn it quickly in a day or two.
  2. Easy to read. Go is highly opinionated. There are standard tools to format the code. Since the language itself is small, there is only so much to remember. These two points make Golang code easy to read.
  3. Easy to develop. Since the language is procedural, it is easy to conceptualize and write applications.
  4. Easy to distribute. You can create a single binary file and distribute. If you want to cross-compile, you can do that too.

Installation

If you are on Mac, then the easiest way is to install via homebrew. Follow my step-by-step guide to install Golang on Mac via brew.

You can also install it via docker. Follow my step-by-step guide to install docker and then Golang within docker. This also installs dep, the package management tool for Golang.

If these two methods doesn’t work, then you can follow the installation guide from Golang homepage.

Let us build a calc

Our goal in this tutorial is to build a simple command-line based calculator. In this post, we will provide only one operator, add. If you input add 4 4, it will return 8.

You can download the code from my gitlab repository.

Let us start

Golang uses dep as a dependency management tool. Here is a detailed guide on using dep.

Initialize the project with: dep init. This will create a directory structure like this:

.
├── Gopkg.lock
├── Gopkg.toml
├── README.md
└── vendor

Create a skeleton main.go like this:

package main

import "fmt"

func main() {
	fmt.Println("Cool cli")
}

We will use the cli package. Install it via:

dep ensure --add gopkg.in/urfave/cli.v1

dep will install urfave/cli into your vendor directory.

Let us start with a simple skeleton. Change your main.go to this:

package main

import (
	"log"
	"os"

	cli "gopkg.in/urfave/cli.v1"
)

func main() {
	app := cli.NewApp()
	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

Here we initialize a new cli application and then run with any arguments that we provide. We are not doing anything with the arguments as of now.

You can run this application with: go run main.go. It will output this:

NAME:
   main - A new cli application

USAGE:
   main [global options] command [command options] [arguments...]

VERSION:
   0.0.0

COMMANDS:
     help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help
   --version, -v  print the version

Help them know your tool

Let us add some helpful tips. We name the application and add usage text.

package main

import (
	"log"
	"os"

	cli "gopkg.in/urfave/cli.v1"
)

func main() {
	app := cli.NewApp()
	app.Name = "Calc"
	app.Usage = "A calculator tool"
	app.Description = "For support, contact ..email.."
	app.UsageText = "calc <op> <num1> <num2>"
	app.Version = "1.0.0"
	app.Authors = []cli.Author{
		cli.Author{
			Name: "Joseph Jude",
		},
	}
	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

Now if you run it with go run main.go, it will show this:

NAME:
   Calc - A calculator tool

USAGE:
   calc <op> <num1> <num2>

VERSION:
   1.0.0

DESCRIPTION:
   For support, contact ..email..

AUTHOR:
   Joseph Jude

COMMANDS:
     help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help
   --version, -v  print the version

Adding numbers

Let us add a command - add. cli stores all commands in app.commands. The signature of the function is func(c *cli.Context) error {}.

For this command, we have to get two arguments. For now, let us just print these arguments.


func main() {
	app := cli.NewApp()

	app.Commands = []cli.Command{
		{
			Name:    "add",
			Usage:   "add two numbers",
			Action: func(c *cli.Context) error {
				fmt.Printf("You sent %q and %q", c.Args().Get(0), c.Args().Get(1))
				return nil
			},
		},
	}

	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}

}

If you run it with go run main.go add 4 4, it will just print these numbers.

These arguments are obtained as a string. Let us convert them into integers, add them, and print out the result. Modify the code segment as this:

app.Commands = []cli.Command{
		{
			Name:    "add",
			Aliases: []string{"a"},
			Usage:   "add two numbers",
			Action: func(c *cli.Context) error {
				a, _ := strconv.Atoi(c.Args().Get(0))
				b, _ := strconv.Atoi(c.Args().Get(1))
				fmt.Printf("%v\n", a+b)
				return nil
			},
		},
	}

If you run it now with go run main.go add 4 4, it will output 8.

Further enhancements

You can learn further by adding more commands like multiplication, division, and subtraction.

This program doesn’t handle any errors. What if the user doesn’t enter two arguments? What if the user enters a string as an argument? You check for these and print out an error message.

Further Reading


Share this post on

Twitter | | |
Sign up for my weekly newsletter

I will send blog updates to this email. You can unsubscribe at any time using the link in those emails.