While most of the applications that programmers use today have a graphical user interface, console or command-line applications are still very common and widely used too. They are simpler, don't take as long to develop and work well to automate jobs or scripts.
In this topic, we'll learn the basics of how to make a Go program interact with the command line.
Accessing command-line arguments
In Go, we can easily access command-line arguments with the Args variable from the os package. The os.Args variable is a slice of strings; its first value, os.Args[0], is the name of our Go program (including its path in Windows OS), while the following values — os.Args[1:] — are the rest of the arguments that we pass to our program.
Now that we know the basics regarding os.Args, let's create a simple program main.go that will take exactly three command-line arguments with the following contents:
package main
import (
"fmt"
"log"
"os"
)
func main() {
// Check that our program takes exactly three arguments:
if len(os.Args) != 4 {
log.Fatal("Error! Expected 3 arguments only!")
}
fmt.Printf("Contents of the os.Args slice = %v\n", os.Args)
fmt.Printf("Name of our program --> os.Args[0] = %[1]s | type: %[1]T\n", os.Args[0])
fmt.Printf("First cmd-line argument --> os.Args[1] = %[1]s | type: %[1]T\n", os.Args[1])
fmt.Printf("Second cmd-line argument --> os.Args[2] = %[1]s | type: %[1]T\n", os.Args[2])
fmt.Printf("Third cmd-line argument --> os.Args[3] = %[1]s | type: %[1]T\n", os.Args[3])
}
In the above code, we validate the length of the os.Args slice first; since the name of the program itself (main) is an argument too, we perform the if statement validation with != 4 instead of != 3. As a good coding practice, we should always validate the number of arguments we pass when creating a console application that accesses command-line arguments.
To start experimenting with the command-line arguments, it's best to build a binary of our program with the go build command first. However, we can also use them if we execute our program with the go run command.
Let's go ahead and execute go build main.go first, and then execute main with the following arguments: hello, 1.618, false:
$ go build main.go
$ ./main hello 1.618 false
Contents of the os.Args slice = [./main hello 1.618 false]
Name of our program --> os.Args[0] = ./main | type: string
First cmd-line argument --> os.Args[1] = hello | type: string
Second cmd-line argument --> os.Args[2] = 1.618 | type: string
Third cmd-line argument --> os.Args[3] = false | type: string
os.Args slice are of the string type!Parsing command-line flags
Command-line flags are a common way to specify additional options or parameters for command-line applications. In Go, we can implement command-line flag parsing with the flag package. The most common flag declarations are for int, string, and bool types; however, the official flag package documentation lists other flag types too.
To see how the flag package works, let's implement the three most basic flag type declarations within main.go:
package main
import (
"flag"
"fmt"
)
func main() {
// Declare string and int flags with default values and help messages:
name := flag.String("name", "User", "Enter your name")
age := flag.Int("age", 1, "Enter your age")
// Another way to declare a flag - bind a command-line flag to an existing variable:
var spacing bool
flag.BoolVar(&spacing, "spacing", true, "Insert a new line between each message")
// After declaring all the flags, enable command-line flag parsing:
flag.Parse()
fmt.Printf("Hello, %s! ", *name)
if spacing {
fmt.Println()
}
fmt.Printf("You are %d years old.", *age)
}
Near the beginning of our program, we see the most basic flag declaration via the flag.String() and flag.Int() functions. They take three parameters: the flag name, the default value, and a help message. These functions return the address of the name and age variables, so we'll need to use pointers to the *name and *age variables to properly access the flags.
Another way to declare a flag is to bind a command-line flag to an existing variable. In this case, we use the flag.BoolVar() function to bind the spacing variable with the spacing flag, and then we pass the flag name, the default value, and a help message.
After declaring all the flags, remember to always call the flag.Parse() function! This allows us to parse the command line into the defined flags.
Now, let's build main.go once again, then execute main and pass arguments to the --name, --age, and --spacing flags.
$ go build main.go
$ ./main --name Keanu --age 57 --spacing=false
Hello, Keanu! You are 57 years old.
bool flag argument, it is mandatory to use the = character.Common errors with arguments and flags
Remember that we always need to specify one - or two -- hyphens before each flag, otherwise our program will not process any arguments we pass to the flags.
In case we omit any flags, our program will use the default values we have previously defined on each flag:
$ ./main
Hello, User!
You are 1 years old.
And if we type the name of a flag incorrectly, we'll get the flag provided but not defined error. It will print the help messages and default values that we've previously defined for our flags:
$ ./main --name Keanu --age 57 --Spacing=false
flag provided but not defined: -Spacing
Usage of ./main:
-age int
Enter your age (default 1)
-name string
Enter your name (default "User")
-spacing
Insert a new line between each message (default true)
-h or --help flags, for example: $ ./main -h. These flags will print the same output as above, except for the error.Adding subcommands
Many command-line tools, like the go tool, have subcommands such as build or run, each with its own set of flags. The flag package allows us to define simple subcommands that have their own flags via the flag.NewFlagSet() function.
Let's update the contents of our main.go program, creating one subcommand – repeat:
package main
import (
"flag"
"fmt"
"log"
"os"
)
func main() {
// Declare the 'repeat' subcommand via the NewFlagSet() function:
repeat := flag.NewFlagSet("repeat", flag.ExitOnError)
// Declare two flags 'name' and 'count' on the 'repeat' subcommand:
repeatName := repeat.String("name", "User", "Enter the name to be repeated")
repeatCount := repeat.Int("count", 1, "Enter the number of repetitions")
if os.Args[1] == "repeat" {
repeat.Parse(os.Args[2:]) // Parse the arguments after the subcommand
for i := 0; i < *repeatCount; i++ {
fmt.Printf("%s\n", *repeatName)
}
} else {
log.Fatal("Expected 'repeat' subcommand") // Exit if the subcommand is not 'repeat'
}
}
In the above code, we create the repeat subcommand with the help of the flag.NewFlagSet() function. It returns a new, empty flag set with the specified name (repeat) and an error handling property in case parsing the flag fails.
After that, we create two flags: name and count on the repeat subcommand. We pass the flag name, default value, and a help message just like in the previous flag declarations.
Since the subcommand is expected as the first argument of the program, we check the value of os.Args[1] and then parse the arguments after the subcommand via the Parse() function. In case the subcommand is not repeat, we exit the program.
After building main.go once again, we can execute main and pass the repeat subcommand with the --name and --count flags:
$ ./main repeat --name Keanu --count 2
Keanu
KeanuSummary
In this topic, we've learned how to create a Go program that can access command-line arguments and parse command-line flags, as well as how to implement subcommands.
In particular, we've covered the following details:
- How to access command-line arguments within the
Argsvariable of theospackage. - How to specify additional options or parameters to command-line applications by implementing command-line flag parsing with the help of the
flagpackage and its functions. - The correct way to pass flags to our program: we should always use one
-or two--hyphens before each flag, otherwise our program will not process any arguments we pass to the flags. - Common errors we can run into when improperly passing flags to a program, and also how to display the usage of a command-line program via the special
-hand--helpflags. - How to add subcommands to our command-line application with the help of the
flag.NewFlagSet()function, and also how to properly call subcommands when executing our program.
This has been a challenging topic! But we're not done yet. Let's now test our newly acquired knowledge regarding command-line arguments and flags in Go by working on a few theory problems!