How to Create a CLI in Go?

Introduction

The term CLI stands for Command Line Interface, which is a method of interacting with a computer program through text-based commands entered in a terminal or console. It allows users to perform various tasks by typing specific commands and parameters instead of using a graphical user interface (GUI).

While CLI help improves efficiency, automation, and flexibility, they are typically lightweight and require fewer system resources as compared to GUI applications. However, it is the responsibility of the developer to ensure that the CLI is easy to use, provides help documentation, and also has autocomplete features.

In this article, we will be creating a simple, easy-to-use CLI in Go using the Cobra package. Let's get started!

Project Setup

I would assume you have a working Go environment on your system; if not, you can download and install it from the official page.

Now, let's start by creating a directory called greeter. Then change into the directory and initialize the Go module using the go mod init command.

~/workspace
? mkdir greeter

~/workspace
? cd greeter

~/workspace/greeter
? go mod init "greeter"
go: creating new go.mod: module greeter

~/workspace/greeter via ?? v1.20.2
? ls -l
total 8
-rw-r--r--  1 gaurav  root  24 Jun 14 23:55 go.mod

Open the directory in your favorite code editor because it's time we write some code. I prefer using VIM or VS Code.

Code

We will be creating a basic CLI named greeter using the Cobra library. The CLI has two commands: the root command, which simply prints a welcome message, and the "greet" command, which takes an argument (a name) and greets the person by their name.

Start by creating a file called main.go in the current directory and follow along. We start by importing the required packages.

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

We import the fmt package for printing messages, the os package for handling operating system-related functionality, and the cobra package, which is a popular library for building command-line interfaces in Go. Note that the first two packages are available in the Go standard library, while Cobra is an external package that we need to get before we can use it.

In order to get the package, switch back to the terminal and execute the go-get command shown below.

~/workspace/greeter via ?? v1.20.2
? go get "github.com/spf13/cobra"
go: added github.com/inconshreveable/mousetrap v1.1.0
go: added github.com/spf13/cobra v1.7.0
go: added github.com/spf13/pflag v1.0.5

Inside the main function, we create the root command using cobra.Command.

rootCmd := &cobra.Command{
	Use:   "greeter",
	Short: "A basic CLI example",
	Long:  "A basic CLI example using Cobra",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Welcome to Greeter!")
	},
}

We set the Use field to specify the name of our CLI tool (greeter). The Short field provides a brief description, and the Long field provides a more detailed description. The Run field contains a function that will be executed when this command is invoked. In this case, it simply prints a welcome message.

Next, we define a subcommand named greet:

greetCmd := &cobra.Command{
	Use:   "greet",
	Short: "Greet someone",
	Long:  "Greet someone by their name",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Printf("Hello, %s!\n", args[0])
	},
}

Similar to the root command, we provide a name (greet), a short description, and a long description for the subcommand. The Args field specifies that this command requires exactly one argument. The Run field contains a function that will be executed when this command is invoked. It retrieves the provided name argument and greets the person using fmt.Printf.

We add the greetCmd as a subcommand to the root command.

rootCmd.AddCommand(greetCmd)

This line connects the greetCmd as a subcommand to the rootCmd.

Finally, we execute the root command and handle any errors:

if err := rootCmd.Execute(); err != nil {
    fmt.Println(err)
    os.Exit(1)
}

The Execute method runs the CLI and processes the provided command-line arguments. If there's an error during execution, it will be printed, and the program will exit with a non-zero status code.

Here is the complete code.

package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

func main() {
	rootCmd := &cobra.Command{
		Use:   "greeter",
		Short: "A basic CLI example",
		Long:  "A basic CLI example using Cobra",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Welcome to Greeter!")
		},
	}

	greetCmd := &cobra.Command{
		Use:   "greet",
		Short: "Greet someone",
		Long:  "Greet someone by their name",
		Args:  cobra.ExactArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Hello, %s!\n", args[0])
		},
	}

	rootCmd.AddCommand(greetCmd)

	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

Build & Run

We can now build our CLI binary by executing the following command.

~/workspace/greeter via ?? v1.20.2 
? go build -o greeter main.go

~/workspace/greeter via ?? v1.20.2
? ls -l
total 8520
-rw-r--r--  1 gaurav  root      180 Jun 15 00:10 go.mod
-rw-r--r--  1 gaurav  root      896 Jun 15 00:10 go.sum
-rwxr-xr-x  1 gaurav  root  4346976 Jun 15 00:27 greeter
-rw-r--r--  1 gaurav  root      651 Jun 15 00:09 main.go

Now, if we run the CLI without any subcommand, we get the welcome message.

~/workspace/greeter via ?? v1.20.2
? ./greeter
Welcome to Greeter!

A good CLI always has good documentation. We can check the documentation generated for our CLI as well.

~/workspace/greeter via ?? v1.20.2 
? ./greeter --help
A basic CLI example using Cobra

Usage:
  greeter [flags]
  greeter [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  greet       Greet someone
  help        Help about any command

Flags:
  -h, --help   help for greeter

Use "greeter [command] --help" for more information about a command.

As a final test, let's execute the greet subcommand as well.

~/workspace/greeter via ?? v1.20.2 
? ./greeter greet Gaurav
Hello, Gaurav!

Conclusion

In this article, we have created a simple yet well-documented CLI in Go using Cobra. While creating a CLI is somewhat easy, writing unit tests for them is not. In the next article, we will conduct unit tests for our newly created CLI.


Similar Articles