We are thrilled to announce 📢 Kosli is now SOC 2 Type 2 compliant - Read more
✨ New Feature: Kosli Trails is live ✨ Create comprehensive audit trails for any DevOps activity - Read more
Golang command line arguments

Understanding Golang Command Line Arguments

Subha Chanda
Author Subha Chanda
Published February 8, 2023 in technology
clock icon 14 min read

Command line interface (CLI) tools are essential in the day-to-day life of developers. They allow you to get your desired result by simply sending a few text inputs, and they consume less resources compared to GUI, as there is no additional graphical interface used. It’s for these reasons that most dev tools have a CLI tool built. For example, you can use GitHub through either their GUI or the GitHub CLI. Even hosting services like Fly.io or Heroku have CLI tools for deploying applications quickly on their platform.

Learning to build a CLI tool can be a great skill set to add to your toolbelt. You may often need to make a CLI tool for an internal or a developer-focused application. The Golang (Go) programming language, along with some helpful packages, can be very useful and straightforward for building such a tool.

Go is a fast and performant language compared to many others. It’s quite easy to get started with and it’s being adopted by a vast number of developers. In fact, according to the Stackoverflow Developer Survey 2022, it is one of the most wanted technologies by developers.

There are several benefits for which you may choose to use Go to build a CLI tool. First is its cross-compilation support. Making your tools cross-platform is very easy, allowing you to efficiently distribute your tools to other platforms.

Go’s speed and performance is another reason to use it for building your CLI tools. The language is lighter than programming languages, handles subroutines concurrently, and supports garbage collection.

When building tools in Go, you can easily get started using only the built-in packages. For example, if you are creating a simple REST API client, you can make it work by simply using the net/http package. But Go also has several great libraries to extend its features and help you build a complete and complex application.

In this article, you’ll learn how to accept command line arguments to build a CLI tool using the Cobra package in Go. The tutorial will walk you through building a CLI tool that returns the details of a cryptocurrency from an API. The CLI will also allow you to get a list of cryptocurrency market data by providing a flag. You’ll understand how to use the Cobra generator, request data from an API, and add arguments and flags to your CLI tool.

Accepting Command Line Arguments in Golang to Build CLI Tools

In this section, you’ll learn how command line arguments can be handled in Golang and use the argument values in the CLI tool. But first, what exactly is a command line argument?

When you run a command like go run main.go the value of main.go is considered a command line argument. It is a way of parameterizing the execution of a program. By default, you can use the os.Args method to get the command line arguments:

package main

import (
    "fmt"
    "os"
)

func main() {
    args := os.Args

    fmt.Println(args) 
} // [arg1 arg2 arg3]

If you run the above code by running the command go run main.go arg1 arg2 arg3, you’ll get an output with an array of the arguments you passed. arg1, arg2, and arg3 are the arguments in this case. You can access the individual arguments by giving the index positions in the args variable.

When you’re trying to build a more complex application and take input from the command line, it is best to use a package that helps you do the work more efficiently. One such tool is the Cobra package. In the next section, you’ll go through a step-by-step guide to taking arguments from the CLI using the Cobra package.

Before moving into the tutorial, make sure you have Go installed on your computer if you are building the tool locally.

Initialize a CLI Tool Using Cobra Generator

The Cobra package is a very popular package for building command line tools, boasting almost 30,000 stars on GitHub. Building a CLI tool with this package is very efficient and straightforward. The package follows a specific folder architecture:

appName/
    cmd/
      cmd1.go
      cmd2.go
      root.go
    main.go 

You can either follow this architecture and install Cobra manually to your application or install the Cobra generator and generate your CLI tool. In this tutorial, you’ll be doing the latter.

To install Cobra, initialize your project directory with go mod. Running the command go mod init github.com/yourname/yourproject in your project folder will create a go.mod file. Once that’s created, you can install the Cobra CLI package.

To install the package, run the following command:

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

Once the package installation is complete, a boilerplate for the CLI application can be generated by running a simple command: cobra-cli init. This command will create the files and folders required for Cobra to build a CLI tool.

Specifically, it will create the necessary main.go file and a file called root.go inside the cmd folder. root.go contains the root command. You can install the CLI by running the command go install and then run the CLI tool by providing the project name in the terminal. For example, if your project name is crypto-cli, you can run the CLI tool by running crypto-cli. Running only this command will show a longer description of the CLI tool:

Long description

You can change this description by changing the text inside the rootCmd variable available in the root.go file.

Add Commands to the CLI

Adding commands to a CLI application is very straightforward. Running cobra-cli add <command> will create a new file with the command’s name inside the cmd folder. For this tutorial, create a subcommand called price:

cobra-cli add price

Running the command in the terminal will generate a new file called price.go inside the cmd folder. In this file, the generator will already create a variable called priceCmd. The variable will look like the following:

var priceCmd = &cobra.Command{
    Use: "price",
    Short: "A brief description of your command",
    Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("price called")
    },
}

The Use key represents the keyword for the command to work. Short and Long are the shorter and longer descriptions of the command. The Run key holds the function called when you run the command. The function takes in two parameters: a pointer to cobra.Command and an array of arguments.

Let’s change the function definition a little. Instead of printing price called, let’s also print the arguments passed. Add the line fmt.Println(args) after the first print statement. Now, install the CLI by running go install. To invoke this function, you’ll need to add the subcommand in your command:

crypto-cli price arg1 arg2 arg3

Running the above command in the terminal will give you the following output:

Output

Similar to os.Args, you’ll also receive an array of arguments here. Let’s take advantage of the arguments to get the crypto price of a specific currency.

To get the details of a cryptocurrency, you’ll need an API. For this tutorial, the CoinCap API is sufficient. It’s free to use and doesn’t require any authentication tokens. You can check their docs to understand the API endpoints.

The API endpoint https://api.coincap.io/v2/assets/<currency> returns the currency details. For example, if you want to get the details for Bitcoin, use the endpoint https://api.coincap.io/v2/assets/bitcoin with a GET request.

First, it is essential to create a function for getting the details. The details are returned in JSON format by the API. For unmarshalling the JSON value, let’s create a struct. According to the data, the struct will look like the following:

type Response struct {
    Data CryptoData `json: "data"`
}

type CryptoData struct {
    Id string `json: "id"`
    Rank string `json: "rank"`
    Symbol string `json: "symbol"`
    Name string `json: "name"`
    Supply string `json: "supply"`
    MaxSupply string `json: "maxSupply"`
    MarketCapUsd string `json: "marketCapUsd"`
    VolumeUsd24Hr string `json:"volumeUsd24Hr"`
    PriceUsd string `json: "priceUsd"`
    ChangePercent24Hr string `json:"changePercent24Hr"`
    Vwap24Hr string `json:"vwap24Hr"`
}

If you hit the endpoint, you’ll be able to understand the struct mapping. The function for getting the details for a currency can look similar to this:

func getCurrencyPrice(currency string) {
    fmt.Println("Getting price for ", currency)
    coincapApiUrl := "https://api.coincap.io/v2/assets/" + currency

    client := http.Client{}

    req, err := http.NewRequest("GET", coincapApiUrl, nil)

    if err != nil {
        log.Fatal(err, "Error creating request")
    }

    res, err := client.Do(req)

    if err != nil {
        log.Fatal(err, "Error sending request")
    }

    defer res.Body.Close()

  respBody, err := io.ReadAll(res.Body)

    if err != nil {
        log.Fatal(err, "Error reading response body")
    }

    var data Response
    json.Unmarshal(respBody, &data)

    if data.Data.Id == "" {
        fmt.Println("Currency not found")
    } else {
        fmt.Printf("Currency: %s\n Symbol: %s\n Price: $%s\n Market Cap: $%s\n Volume: %s\n Change: %s%%\n Rank: %s\n Supply: %s\n Max Supply: %s\n Vwap: %s\n", data.Data.Name, data.Data.Symbol, data.Data.PriceUsd, data.Data.MarketCapUsd, data.Data.VolumeUsd24Hr, data.Data.ChangePercent24Hr, data.Data.Rank, data.Data.Supply, data.Data.MaxSupply, data.Data.Vwap24Hr)
    }
}

The function is very straightforward—you’ll easily understand it if you understand how functions and the HTTP package works in Go.

The function takes a parameter called currency for the currency name. According to the currency name, the function hits the API endpoint for getting the asset details. The built-in HTTP package is used here. The response body is read using the io.ReadAll method. If there’s no error, a variable, data, is created from the Response struct. The JSON data is unmarshalled using the built-in json.Unmarshal method available in the json package, and the response body is stored in the data variable.

Finally, if the Id of the data is empty, the function prints that the currency is not found. Otherwise, it prints the details in the terminal.

To use the function in the command, add the following lines inside the Run key:

Run: func(cmd *cobra.Command, args []string) {
        currency := strings.ToLower(args[0])

        getCurrencyPrice(currency)
},

Also, you can use a positional argument validator to ensure only one argument is passed in. Add a new key to the priceCmd variable like this:

Args: cobra.ExactArgs(1),

The validator will check that only one argument is passed. Cobra has many positional argument validators. And to extend its features, you can also write your own custom validators. You can refer to this documentation to learn more.

Install the CLI using go install and run it using crypto-cli price ethereum to check if your command is working correctly. If everything works properly, you’ll get an output similar to the image below:

Output

If you try to pass more than one argument, you’ll get an error stating Error: accepts 1 arg(s), received 2.

crypto-cli price works as a parent-child combination of two commands. The first command is the crypto-cli command itself, and the second is price followed by an argument. If you want to define the Run function for the crypto-cli command itself, you can uncomment the Run key in the rootCmd variable inside the root.go file. Here’s an example:

var rootCmd = &cobra.Command{
    Use: "crypto-cli",
    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. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
    // Uncomment the following line if your bare application
    // has an action associated with it:
    Run: func(cmd *cobra.Command, args []string) { 
        fmt.Println("Hello World")
    },
}

Adding Flags to the CLI

Now, let’s add a flag to the CLI application. A flag is an option passed to the CLI application. Flags can be called by adding two or one hyphens before the term. For example, in the command crypto-cli --help, help is a flag. So far in this tutorial, you’ve learned to get the details of a cryptocurrency for the current date. Now let’s assume you want to get the details of the currency from different markets. For this, a flag can be very helpful.

There are two types of flags in Cobra, persistent and local flags. A persistent flag value can be accessed by the command where it is declared, along with the related commands. For example, if you define a persistent flag in the root.go file, you can access the flag from the price command as well. A local flag can only be accessed by the specific command where it is declared.

Let’s first create a persistent flag for getting market data for a cryptocurrency. The API endpoint https://api.coincap.io/v2/assets/<asset>/markets can be used for getting the market data. To define the persistent flag, open the root.go file and add the following line inside the init function:

rootCmd.PersistentFlags().BoolP("markets", "m", false, "Get the markets listings of a cryptocurrency")

The above code creates a persistent flag of boolean type. The flag’s long name is markets, and the short name is m. The default value is false, and the last parameter is a description of the flag.

The price command can access the flag. Now let’s create the function for getting the market values. The code is similar to the above function, the main difference being the structure:

type MarketResponse struct {
    Data []MarketData `json:"data"`
}

type MarketData struct {
    ExchangeId string `json: "exchangeId"`
    BaseId string `json:"baseId"`
    QuoteId string `json:"quoteId"`
    QuoteSymbol string `json: "quoteSymbol"`
    VolumeUsd24Hr string `json:"volumeUsd24Hr"`
    PriceUsd string `json: "priceUsd"`
    VolumePercent string `json: "volumePercent"`
}

The structure for the market response is shown in the above code. The function for getting the market data is shown below:

func getMarkets(currency string) {
    fmt.Println("Getting markets for ", currency)
    coincapApiUrl := "https://api.coincap.io/v2/assets/" + currency + "/markets?limit=20"

    client := http.Client{}

    req, err := http.NewRequest("GET", coincapApiUrl, nil)

    if err != nil {
        log.Fatal(err, "Error creating request")
    }

    res, err := client.Do(req)

    if err != nil {
        log.Fatal(err, "Error sending request")
    }

    defer res.Body.Close()

    respBody, err := io.ReadAll(res.Body)

    if err != nil {
        log.Fatal(err, "Error reading response body")
    }

    var data MarketResponse
    json.Unmarshal(respBody, &data)

    for _, market := range data.Data {
        fmt.Printf("\n\nExchange: %s\nBase: %s\nQuote: %s\nPrice: $%s\nVolume: %s\nVolume Percent: %s\n", market.ExchangeId, market.BaseId, market.QuoteSymbol, market.PriceUsd, market.VolumeUsd24Hr, market.VolumePercent)
    }
}

The code resembles the earlier getCurrencyPrice function, but with a new endpoint. Here, the response is limited to data from twenty different markets. Next, let’s read the markets flag value in the price command. Add the following code to the Run key inside the priceCmd variable:

showMarketData, _ := cmd.Flags().GetBool("markets")

The boolean value of markets is stored in the showMarketData variable. Now change the logic of the Run key inside the priceCmd variable, as below:

Run: func(cmd *cobra.Command, args []string) {
        currency := strings.ToLower(args[0])
        showMarketData, _ := cmd.Flags().GetBool("markets")

        if showMarketData {
            getMarkets(currency)
        } else {
            getCurrencyPrice(currency)
        }
    },

If the markets flag is available, it returns the market data. Otherwise, it returns the current currency details. The following image shows the price of Ethereum in different markets:

Ethereum data from different markets

To create a local flag, instead of the PersistentFlags function, you’ll need to use the Flags function. For example, the following code sample defines how a local flag can be initialized in a command:

var showHistory bool
func init() {
    rootCmd.AddCommand(priceCmd)
    priceCmd.Flags().BoolVarP(&showHistory, "history", "i", false, "Show history of a currency")
}

A local flag called history is created here, and the flag’s value is stored inside the showHistory variable.

Adding Help Information to Your Commands

Adding help information in a Cobra command is very simple. When you generate a command using the Cobra generator, it creates the necessary function for you. For example, in the rootCmd variable in the root.go file, you can already see a few keys like Use, Short, Long , and so on. Here, Use is the command name. The Long and Short keys hold the long and short descriptions, respectively. The code snippet below shows you how you can add help information:

var rootCmd = &cobra.Command{
    Use: "crypto-cli2",
    Short: "Get cryptocurrency prices and market data",
    Long: `To get the price of a cryptocurrency, use the price command.
    Get the price of a cryptocurrency. For currencies with spaces, example terra lune, use - instead of spaces. Example: terra-luna`,
    // Uncomment the following line if your bare application
    // has an action associated with it:
    Run: func(cmd *cobra.Command, args []string) { 
        fmt.Println("Hello World")
    },
}

You can use the --help or -h flag to get help information for a command.

You have successfully made a command line tool for getting cryptocurrency details. The complete code is available on this GitHub repo.

Conclusion

This article discussed how CLI tools can be built using Golang, and how arguments can be taken in a CLI tool. You learned about the benefits of using Go for building command line tools and saw how the Cobra package can be used to build a complete CLI application.

The Cobra package is very popular and lets users build CLI tools quickly. The article showed how arguments and flags can be passed into a CLI tool and how these values can be used for performing different operations.


ABOUT THIS ARTICLE

Published February 8, 2023, in technology

AUTHOR

Stay in the loop with the Kosli newsletter

Get the latest updates, tutorials, news and more, delivered right to your inbox
Kosli is committed to protecting and respecting your privacy. By submitting this newsletter request, I consent to Kosli sending me marketing communications via email. I may opt out at any time. For information about our privacy practices, please visit Kosli's privacy policy.
Kosli team reading the newsletter

Got a question about Kosli?

We’re here to help, our customers range from larges fintechs, medtechs and regulated business all looking to streamline their DevOps audit trails

Contact us
Developers using Kosli