In the 25th post of the series, we will be taking a look into parsing of command line arguments in golang. We will be exploring how to do the basics of parsing and using the positional parameters or arguments from the command line in the program. By using standard library packages like os
and flag
, we can make powerful yet easy-to-build CLI apps and programs.
We can use the os
package to get the arguments from the command line in a go script. We have to use the Args
variable in the os package. The Args
variable is a slice of strings which thereby is the parsed arguments from the command line.
package main
import (
"fmt"
"os"
)
func main() {
args := os.Args
fmt.Printf("Type of Args = %T\n", args)
fmt.Println(args[0], args[1])
}
$ go run main.go hello
Type of Args = []string
/tmp/go-build1414795487/b001/exe/main hello
In the above example, we can see that the Args
is a slice of string and we can get the indices as per the arguments passed from the command line.
If you don’t parse any arguments and access the 1st argument as os.Args[1]
it will result in an index out of range
error. So, you need to first check if the argument is parsed and set a default value otherwise.
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
var port int
var err error
if len(os.Args) > 1 {
port, err = strconv.Atoi(args[1])
if err != nil {
panic(err)
}
} else {
port = 8000
}
fmt.Println(port)
}
$ go run main.go
8000
$ go run main.go 7000
7090
$ go run main.go h
panic: strconv.Atoi: parsing "h": invalid syntax
In the above example, we have declared the port variable as an integer and tried to see if we had an argument parsed from the command line using the len function and if there was a variable, we will simply cast it into an integer using the strconv.Atoi function. If there are any errors in the process, we log an error message and panic out of the program. So, this is how we can set default values or check for any arguments from the command line in golang.
We can use the len function with the Args
slice to get the total number of arguments from the command line. To ignore the first argument which would be the path to the program, we simply can slice the first element as os.Args[1:]
. This will slice the list of the arguments from the first index till the last element in the slice.
package main
import (
"fmt"
"os"
)
func main() {
total_args := len(os.Args[1:])
fmt.Println("Total Args =", total_args)
}
$ go run main.go hello world 56
Total Args = 3
This will simply give us the number of arguments passed from the command line, excluding the first(0th) argument which is the default argument as the execution path of the current program.
We can use the simple for loop with range over the os.Args or os.Args[1:] for iterating over each of the arguments passed from the command line.
package main
import (
"fmt"
"os"
)
func main() {
for n, args := range os.Args {
fmt.Println("Arg", n, "->", args)
}
/*
// For excluding the 0th argument
for n, args := range os.Args[1:] {
fmt.Println("Arg", n, "->", args)
}
*/
}
$ go run main.go hello world 56
Arg 0 -> /tmp/go-build2248205073/b001/exe/main
Arg 1 -> hello
Arg 2 -> world
Arg 3 -> 56
We can now iterate over the arguments passed from the command line using a simple for loop. We can further process these arguments per the program’s requirements and need.
Golang has a package in its standard library called flags which allows us to parse flags and arguments from the command line with a lot of built-in features. For instance, a default value is easily parsed with a simple function parameter, help text in case of an error in parsing the arguments or flag, customization and freedom for choosing a data type for the type of argument, and so on. For a bare-bones and quick CLI program, the flag package is a great choice.
We can use typed flag values using the functions provided in the flags
package like IntVar for an integer value, StringVar for string, BoolVar for boolean values and so on. Each function takes in 4 parameters and they set the value of the parsed variable from the parsed argument/flag from the command line.
So, let’s take the previous example of port number parsing from the command line. We can use the flag.IntVar(&port, "p", 8000, "Provide a port number")
, this will set the value of the variable port from the command line as the value of -p 6789
or the default value as 8000
. The help text will be used if the user has provided a non-integer or an invalid value as an error message.
package main
import (
"flag"
"fmt"
)
func main() {
var port int
var dir string
var publish bool
flag.IntVar(&port, "p", 8000, "Provide a port number")
flag.StringVar(&dir, "dir", "output_dir", "Directory")
flag.BoolVar(&publish, "publish", false, "Publish the article")
flag.Parse()
fmt.Println(port)
fmt.Println(dir)
fmt.Println(publish)
if publish {
fmt.Println("Publishing article...")
} else {
fmt.Println("Article saved as Draft!")
}
}
$ go run flag.go
8000
output_dir
false
Article saved as Draft!
$ go run flag.go -p 1234
1234
output_dir
false
Article saved as Draft!
$ go run flag.go -p 1234 -dir site_out
1234
site_out
false
Article saved as Draft!
$ go run flag.go -publish
8000
output_dir
true
Publishing article...
So, in the above, example, we have used a few types of values like IntegerVar
for port
, StringVar
for dir
, and BoolVar
for publish
. As explained earlier, the functions take 4 parameters in the same format, the reference to the variable to hold the parsed value, the name of the argument/flag, the default value the variable will hold, and the help text or usage string. The BoolVar is slightly different but it works logically well, if we parse -publish
the value will be set as true and false otherwise. You can manually add the value like -publish true
and so on but it is not mandatory and understood as true.
In the above example, we have parsed different arguments in the output and displayed the values of these flags. If we don’t specify a value, we can see the default value being parsed, in the case of the bool
variable, the default value is taken as false
. Hence we can see how easily we can use and parse flags from the command line in golang, it’s simple, quick, and also extensible.
For other data types, the flag package has functions like Float64Var for float64 values, DurationVar for time duration values and TextVar for other types as inferred by the unmarshalling of the text.
We can set the value of a flag/argument from the script rather than from the command line using the Set method in the flag package. The Set
method takes in two values as parameters the name of the argument and the value of that argument to set as. It returns an error if any arise during the setting of the argument.
package main
import (
"flag"
"fmt"
)
func main() {
var port int
var dir string
var publish bool
flag.IntVar(&port, "p", 8000, "Provide a port number")
flag.StringVar(&dir, "dir", "output_dir", "Directory")
flag.Parse()
fmt.Println(port)
fmt.Println(dir)
flag.Set("dir", "dumps")
fmt.Println(dir)
}
$ go run flag.go -p 8080
8080
output_dir
dumps
So, it is clearly visible that the value of an argument can be changed within the script, it also changes the value of the associated variable. Remember, we gave the two-parameter as strings so the first parameter is the name of the argument and not necessarily the variable name.
Also, there are functions like Int , Float64 , String , Bool in the flag package that can allow getting the values of the arguments without using the Parse
method. We use the reference of the value stored in as the arguments instead of defining the variables as a data value; we have a pointer to that value of data.
package main
import (
"flag"
"fmt"
)
func main() {
port := flag.Int("p", 8000, "Provide a port number")
dir := flag.String("dir", "output_dir", "Directory")
publish := flag.Bool("publish", false, "Publish the article")
help := flag.Bool("help", false, "Help")
if *help {
flag.PrintDefaults()
} else {
fmt.Println(*port)
fmt.Println(*dir)
flag.Set("dir", "dumps")
fmt.Println(*dir)
fmt.Println(flag.NFlag())
fmt.Println(flag.NArg())
fmt.Println(*publish)
if *publish {
fmt.Println("Publishing article...")
} else {
fmt.Println("Article saved as Draft!")
}
vals := flag.Args()
fmt.Println(vals)
}
}
$ go run flag.go -p 80 -dir node_mods 1234
80
node_mods
dumps
2
1
false
Article saved as Draft!
[1234]
As we can it performs the same task, but we have to use pointers as references to the arguments instead of storing them in an actual memory address. We have performed the same set of operations on the arguments and flags as we do with the other examples.
We first, use the Int
method or other methods appropriate that String
can be used in general use cases, the function returns a reference (memory address) of the actual stored value of the arguments/flag. We can access the value from its memory address using the * operator. We have covered the pointer arithmetic in the last part of the series. When we use *port
we get the value from the memory address and thereby we can use it for the required task in the program, we can also store a copy of the variable by creating a new variable with the value of that argument.
So, if we want to parse flags, with a single value, we have seen the use of the flag.Args function to get the values of the arguments passed from the command line which don’t have any flag labels attached to them(just raw arguments from the CMD). Just as we used the os.Args
variable but this function is much clean and filtered out the path to the program argument. So we can directly have the arguments which are clearly passed by the user from the command line.
package main
import (
"flag"
"fmt"
)
func main() {
var port int
flag.IntVar(&port, "p", 8000, "Provide a port number")
flag.Parse()
fmt.Println(port)
vals := flag.Args()
fmt.Println(vals)
}
$ go run flag.go -p 8123
8123
[]
$ go run flag.go -p 8123 1234 hello true
8123
[1234 hello true]
$ go run flag.go -p 8123 1234 hello true -p 9823 world
8123
[1234 hello true -p 9823 world]
In the above example, we can see that we have used a few non-flagged arguments from the command line. The return value of the Args
function is a slice of string, we can then convert it into appropriate types using type casting and functions. Once the flagged arguments are parsed, if we use the Args
function, it won’t be possible to again use flagged arguments in the command line. It will be considered a simple string thereafter.
That’s it from this part. Reference for all the code examples and commands can be found in the 100 days of Golang GitHub repository.
We can use the flag.PrintDefaults method for just printing the default values and the help text for the expected arguments from the command line in the script. We can simply use it as a help flag or use it in error messages for guiding the user to the proper arguments and flags.
package main
import (
"flag"
"fmt"
)
func main() {
var port int
var help bool
flag.IntVar(&port, "p", 8000, "Provide a port number")
flag.BoolVar(&help, "help", false, "Help")
flag.Parse()
if help {
flag.PrintDefaults()
} else {
fmt.Println(port)
vals := flag.Args()
fmt.Println(vals)
}
}
$ go run help.go -h
Usage of /tmp/go-build121267600/b001/exe/help:
-help
Help
-p int
Provide a port number (default 8000)
$ go run help.go
8000
[]
So, we can see the PrintDefaults
function will simply print the helper text for the flags expected in the script and the default value of those flags as well. This can be used to provide a good user-friendly interface for a simple terminal application.
We can use the NFlag method in the flag
package. The function returns an integer that indicates a count of the arguments that have been set from the command line.
package main
import (
"flag"
"fmt"
)
func main() {
var port int
var dir string
var publish bool
flag.IntVar(&port, "p", 8000, "Provide a port number")
flag.StringVar(&dir, "dir", "output_dir", "Directory")
flag.Parse()
fmt.Println(port)
fmt.Println(dir)
fmt.Println(flag.NFlag())
}
$ go run flag.go
8000
output_dir
0
$ go run flag.go -p 8080 8999 false hello
8080
output_dir
1
$ go run flag.go -p 8080 -dir dumps hello 1234
8080
dumps
2
The port
flag has been set from the command line, so we just have one argument set, hence the function NFlag
returns 1
as the number of set flags.
Also, the NArg method will return an integer that will count the number of arguments that have been provided leaving out the flag arguments.
package main
import (
"flag"
"fmt"
)
func main() {
var port int
var dir string
var publish bool
flag.IntVar(&port, "p", 8000, "Provide a port number")
flag.StringVar(&dir, "dir", "output_dir", "Directory")
flag.Parse()
fmt.Println(port)
fmt.Println(dir)
fmt.Println(flag.NArg())
}
$ go run flag.go 1234
8000
output_dir
1
$ go run flag.go -p 8080 -dir dumps hello 1234
8080
dumps
2
$ go run flag.go -p 8080 hello 1234 false
8080
dumps
3
In the first example, we don’t have any flag arguments set, we just have one unflagged argument as 1234
, hence the NArg
function returns 1
. The second example has 2 values that are not flagged, we have set the values of port
and dir
as 8080
and dumps
respectively, so the remaining unflagged values are hello
and 1234
hence the return value as 2
. The third example has 3 unflagged values as hello 1234 false
, hence we return 3
.
That’s it from this part. Reference for all the code examples and commands can be found in the 100 days of Golang GitHub repository.
We have seen how to parse command line arguments in golang with the os
and the flag
packages. Though these two are not the only options for building CLI applications, they provide a clean and easy-to-start approach, also they come with the standard library which makes it even better as we don’t have to mingle with third-party libraries. We saw the basics of parsing flags and arguments from a command line program.
Thank you for reading. If you have any queries, questions, or feedback, you can let me know in the discussion below or on my social handles. Happy Coding