June 26, 2020     6min read

How to make a CLI application in go

CLI or Command Line Interfacec is a kind of program that user interacts with entirely through a terminal and shell.

In this article we want to go through a simple CLI application written in golang.

Install go

Linux

download the latest version of the source code here

unarchive the source

tar -C /usr/local -xzf go1.18.3.linux-amd64.tar.gz

Add /usr/local/go/bin into the PATH environment variable

export PATH=$PATH:/usr/local/go/bin

Verify the go installation by executing following command

go --version

Mac

Open the package file you downloaded and follow the prompts to install Go

The package installs the Go distribution to /usr/local/go. The package should put the /usr/local/go/bin directory in your PATH environment variable. You may need to restart any open Terminal sessions for the change to take effect.

Verify the go installation by executing following command

go --version

Windows

Open the MSI file you downloaded and follow the prompts to install Go

Verify that you've installed Go

  1. In Windows, click the Start menu.
  2. In the menu's search box, type cmd, then press the Enter key.
  3. In the Command Prompt window that appears, type the following command
go version

To initialize a go project we need to execute such a command

go mod init {main_package_name}

The first statement in a Go source file must be package name. Executable commands must always use package main

then we need to create the main file (where we will have the entry point of our program)

package main

import "fmt"

func main() {
    fmt.Println("Hello, world.")
}

you can install and build the program with the go tool

go install {main_package_name}

this command builds an executable file with the same name as our main source file.

If GOBIN environment variable is set, binaries are installed to the bin subdirectory of the first directory in the GOPATH list., Otherwise, binaries will be installed to the bin subdirectory of the default GOPATH ($HOME/go` or `%USERPROFILE%\go)

go env -w GOBIN=/some_directory/bin

To unset a variable previously set by go env -w, use go env -u:

go env -u GOBIN

For the purpose of our CLI application we use a commander library called Cobra.

Execute the following command to install the latest version of cobra

go get -u github.com/spf13/cobra@latest

next, include Cobra in your application

import "github.com/spf13/cobra"
package main

import (
    "fmt"
    "github.com/spf13/cobra"
)

func main() {
    fmt.Println("Hello, world.")
}

A command line application needs a global command that represents the base command called without any subcommand. Thus we need to define this variable inside our main file as below.

var mainCommand= &cobra.Command{
    Use: "main",
    Short: "welcome to my first commandline application",
    Long:" this is a long description about the application",
}

To have a functionality for a command we need to call a function inside the command struct instantiation as follow

var mainCommand= &cobra.Command{
    Use: "main",
    Short: "welcome to my first commandline application",
    Long:" this is a long description about the application",

    Run: func(cmd *cobra.Command, args []string) {},
}

We have an Init function that holds the definition of the flags and configuration settings .Cobra supports persistent flags which if we define them here then they will be global for our application.

mainCommand.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.main.yaml)")

Cobra also supports local flags that only run when the action is called directly.

rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
func init() {
	mainCommand.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.main.yaml)")
	mainCommand.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

We need a function in our main file called Execute() to add all child commands to the root command and set flags appropriately,

func Execute() {
	err := mainCommand.Execute()
	if err != nil {
		os.Exit(1)
	}
}

let put all together

package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

//======================================================================

var mainCommand = &cobra.Command{
	Use: "main",
    Short: "welcome to my first commandline application",
    Long:" this is a long description about the application",
	Run: func(cmd *cobra.Command, args []string) {},
}

//=====================================================================
func Execute() {
	err := mainCommand.Execute()
	if err != nil {
		os.Exit(1)
	}
}

//=====================================================================
func init() {
	mainCommand.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

if we run the application we should see such a result

moeid:~/$ go run main.go -h
 this is a long description about the application

Usage:
  main [flags]

Flags:
  -h, --help     help for main
  -t, --toggle   Help message for toggle

Lets add a command with a flag to our commandline application

to add a command we need make an instantiation from the command struct at first as follow

var PrintHello = &cobra.Command{
	Use:   "hello",
	Short: "Prints a hello sentence",
	Long:  `This command takes a name as an argument and prints a hello {name}`,
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("hello")
	},
}

now we need to add it to the mainCommand

func Execute() {
	mainCommand.AddCommand(PrintHello)
	err := mainCommand.Execute()
	if err != nil {
		os.Exit(1)
	}
}

now if we run the application we will see that the command is added

moeid:~/$ go run main.go -h
this is a long description about the application

Usage:
  main [flags]
  main [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  hello       Prints a hello sentence
  help        Help about any command

Flags:
  -h, --help     help for main
  -t, --toggle   Help message for toggle

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

Now we need to add the argument to our hello command

var PrintHello = &cobra.Command{
	Use:   "hello",
	Short: "Prints a hello sentence",
	Long:  `This command takes a name as an argument and prints a hello {name}`,
	Run: func(cmd *cobra.Command, args []string) {
		name, _ := cmd.Flags().GetString("name")
		fmt.Println("hello:", name)
	},
}

func Execute() {
	mainCommand.AddCommand(PrintHello)
	PrintHello.PersistentFlags().String("name", "unknown", "name of the user to say hello to")
	err := mainCommand.Execute()
	if err != nil {
		os.Exit(1)
	}
}

Now if we run the application again with hello and provide the name argument we should see such a result printed on the terminal

moeid:~/$ go run main.go hello --name moeid
hello: moeid

Put it all together

package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

//======================================================================

var mainCommand = &cobra.Command{
	Use:   "main",
	Short: "welcome to my first commandline application",
	Long:  " this is a long description about the application",
	Run:   func(cmd *cobra.Command, args []string) {},
}

var PrintHello = &cobra.Command{
	Use:   "hello",
	Short: "Prints a hello sentence",
	Long:  `This command takes a name as an argument and prints a hello {name}`,
	Run: func(cmd *cobra.Command, args []string) {
		name, _ := cmd.Flags().GetString("name")
		fmt.Println("hello:", name)
	},
}

//=====================================================================
func Execute() {
	mainCommand.AddCommand(PrintHello)
	PrintHello.PersistentFlags().String("name", "unknown", "name of the user to say hello to")
	err := mainCommand.Execute()
	if err != nil {
		os.Exit(1)
	}
}

//=====================================================================
func init() {
	mainCommand.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

Please let me know if you have any comment on it 😎

Moeid Heidari

Made by Gatsby