Software Engineering Essentials is a series of blog posts designed to help you get started with a wide variety of software engineering topics. This post was originally part of my Go-Twitter project, a project-based curriculum designed to take someone from “zero” to “competent” in the world of software engineering.
All writing for this post is licensed under CC-BY-4.0, while all code is licensed under the MIT license.
Flags
Flag Usage
Flags are effectively named arguments, with some extra features. Instead of needing to remember the order of your program’s arguments, you can just specify the name of the flag and what that variable should be set to. Let’s take a look at a simple example:
01-simple-flags/main.go:
package main
import (
"flag"
"fmt"
)
func main() {
greeting := flag.String("greeting", "", "")
name := flag.String("name", "", "")
flag.Parse()
fmt.Printf("%s, %s!\\n", *greeting, *name)
}
Now, if we run this program:
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go
!
Huh, that looks weird. This is because our greeting and name variables are
empty. We never specified the flags. How do we know what the flags are? Flags
have built-in help functionality that will display all of the options available.
Most programs use -h or --help to display program usage text, including
flags and arguments.
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go -h
Usage of /tmp/go-build1913891522/b001/exe/main:
-greeting string
-name string
So now we can see that this program accepts the -greeting and -name flags.
Let’s set some values:
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go -greeting="Sup" -name Tom
Sup, Tom!
Flag arguments can be set with some optional grammar to help avoid problems with ambiguity or spaces:
flag value: For one-word values- Unquoted spaces can change how your command is interpreted, leading to missing values and skipped flags. See the appendix section How Spaces Can Affect Flags for an example.
flag "value with spaces or special characters": For multi-word values or values that contain some special characters.- If your value has a
$symbol, you may need to use single-quotes (') to avoid the shell interpreting$as the start of a shell variable.
- If your value has a
flag="value": This is the belt-and-suspenders of flag declaration.- The
=just removes space from the declaration and makes it very clear that this particular value belongs to that particular flag. This can be helpful to aid in readability, especially if you’re writing out examples or documentation.
- The
We can also specify the flags in any order:
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go -name="General" -greeting="Hello There"
Hello There, General!
Or not specify a value for -name at all!
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go -greeting="Hello There"
Hello There, !
We can even abuse the flags to make the program finish the meme:
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go -greeting="General Kenobi\\!" -name="You are a bold one"
General Kenobi!, You are a bold one!
Defaults
Let’s re-create the program that inserted profiles into a database from the end of the Command Line Arguments chapter, but with flags instead:
02-profile-saver/main.go:
package main
import (
"flag"
"fmt"
)
func main() {
firstName := flag.String("first-name", "", "")
lastName := flag.String("last-name", "", "")
favFruit := flag.String("fruit", "", "")
favVeg := flag.String("veg", "", "")
favPasta := flag.String("pasta", "", "")
favMeal := flag.String("meal", "", "")
riceOrBeans := flag.String("rice-or-beans", "", "")
favDesert := flag.String("desert", "", "")
favDessert := flag.String("dessert", "", "")
flag.Parse()
fmt.Printf(`Profile Name: %s %s
Rice or Beans: %s
Favorites:
Fruit: %s
Vegetable: %s
Pasta: %s
Desert: %s
Dessert: %s
Meal: %s
`, *firstName, *lastName, *riceOrBeans, *favFruit, *favVeg, *favPasta, *favDesert, *favDessert, *favMeal)
}
Right now if we run this program with no flags set, we get this results:
go run example-code/02-command-line-arguments-and-flags/02-flags/02-profile-saver/main.go
Profile Name:
Rice or Beans:
Favorites:
Fruit:
Vegetable:
Pasta:
Desert:
Dessert:
Meal:
Lots of blank spaces here. We don’t want to commit empty values to the database, it can introduce ambiguity. Did this person not have a favorite pasta or did we just forget to add the data? Being explicit in programming has several benefits, including clarity-of-intent, so let’s add some defaults.
Default values can provide the usual options or placeholder data. Flags make
this easy. For now, let’s just use UNKNOWN as the profile placeholder. With
the stanadrd flag package, setting defaults is easy. Let’s take a look at the
GoDocs for flag.String, available here:
func String
func String(name string, value string, usage string) *stringString defines a string flag with specified name, default value, and usage string. The return value is the address of a string variable that stores the value of the flag.
So the second function argument allows us to specify a default value for that particular flag. Let’s do that now:
02-profile-saver/main.go:
package main
import (
"flag"
"fmt"
)
func main() {
firstName := flag.String("first-name", "UNKNOWN", "")
lastName := flag.String("last-name", "UNKNOWN", "")
favFruit := flag.String("fruit", "UNKNOWN", "")
favVeg := flag.String("veg", "UNKNOWN", "")
favPasta := flag.String("pasta", "UNKNOWN", "")
favMeal := flag.String("meal", "UNKNOWN", "")
riceOrBeans := flag.String("rice-or-beans", "UNKNOWN", "")
favDesert := flag.String("desert", "UNKNOWN", "")
favDessert := flag.String("dessert", "UNKNOWN", "")
flag.Parse()
fmt.Printf(`Profile Name: %s %s
Rice or Beans: %s
Favorites:
Fruit: %s
Vegetable: %s
Pasta: %s
Desert: %s
Dessert: %s
Meal: %s
`, *firstName, *lastName, *riceOrBeans, *favFruit, *favVeg, *favPasta, *favDesert, *favDessert, *favMeal)
}
Now when we run this program without any flags set:
go run example-code/02-command-line-arguments-and-flags/02-flags/02-profile-saver/main.go
Profile Name: UNKNOWN UNKNOWN
Rice or Beans: UNKNOWN
Favorites:
Fruit: UNKNOWN
Vegetable: UNKNOWN
Pasta: UNKNOWN
Desert: UNKNOWN
Dessert: UNKNOWN
Meal: UNKNOWN
There we go, that at least looks more complete.
Now let’s actually use those flags to set some values… but wait, what were the
flags again? Let’s use -h to see what options we have:
go run example-code/02-command-line-arguments-and-flags/02-flags/02-profile-saver/main.go -h
Usage of /tmp/go-build261536590/b001/exe/main:
-desert string
(default "UNKNOWN")
-dessert string
(default "UNKNOWN")
-first-name string
(default "UNKNOWN")
-fruit string
(default "UNKNOWN")
-last-name string
(default "UNKNOWN")
-meal string
(default "UNKNOWN")
-pasta string
(default "UNKNOWN")
-rice-or-beans string
(default "UNKNOWN")
-veg string
(default "UNKNOWN")
Ah, got it. Now let’s actually use these:
go run example-code/02-command-line-arguments-and-flags/02-flags/02-profile-saver/main.go -first-name Bob -last-name Sacamano -fruit Apple -veg "Green Beans" -pasta Manicotti -meal Steak -desert Sahara -dessert Sundae -ri
ce-or-beans Rice
Profile Name: Bob Sacamano
Rice or Beans: Rice
Favorites:
Fruit: Apple
Vegetable: Green Beans
Pasta: Manicotti
Desert: Sahara
Dessert: Sundae
Meal: Steak
So now we’re able to print out all of the profile information using flags. Its still a lot of flags to remember, but at least we have the default help text and the order of options doesn’t matter.
Help Text
As we saw above, -h is pretty helpful to understand which flags are available,
what type is expected, and what the defaults are, but we can add some more
context through help text. As always, reading the documentation can help us find the right place to add help text to our flags. For
now, let’s just add one piece of help text and see how it compares to the rest
of the flags:
02-profile-saver/main.go:
package main
import (
"flag"
"fmt"
)
func main() {
firstName := flag.String("first-name", "UNKNOWN", "the first name of the user profile")
lastName := flag.String("last-name", "UNKNOWN", "")
favFruit := flag.String("fruit", "UNKNOWN", "")
favVeg := flag.String("veg", "UNKNOWN", "")
favPasta := flag.String("pasta", "UNKNOWN", "")
favMeal := flag.String("meal", "UNKNOWN", "")
riceOrBeans := flag.String("rice-or-beans", "UNKNOWN", "")
favDesert := flag.String("desert", "UNKNOWN", "")
favDessert := flag.String("dessert", "UNKNOWN", "")
flag.Parse()
fmt.Printf(`Profile Name: %s %s
Rice or Beans: %s
Favorites:
Fruit: %s
Vegetable: %s
Pasta: %s
Desert: %s
Dessert: %s
Meal: %s
`, *firstName, *lastName, *riceOrBeans, *favFruit, *favVeg, *favPasta, *favDesert, *favDessert, *favMeal)
}
Now let’s check out the help text:
go run example-code/02-command-line-arguments-and-flags/02-flags/02-profile-saver/main.go -h
Usage of /tmp/go-build1166983742/b001/exe/main:
-desert string
(default "UNKNOWN")
-dessert string
(default "UNKNOWN")
-first-name string
the first name of the user profile (default "UNKNOWN")
-fruit string
(default "UNKNOWN")
-last-name string
(default "UNKNOWN")
-meal string
(default "UNKNOWN")
-pasta string
(default "UNKNOWN")
-rice-or-beans string
(default "UNKNOWN")
-veg string
(default "UNKNOWN")
Neat! Now let’s add the rest:
02-profile-saver/main.go:
package main
import (
"flag"
"fmt"
)
func main() {
firstName := flag.String("first-name", "UNKNOWN", "the first name of the user profile")
lastName := flag.String("last-name", "UNKNOWN", "the last name of the user profile")
favFruit := flag.String("fruit", "UNKNOWN", "the user's favorite fruit")
favVeg := flag.String("veg", "UNKNOWN", "the user's favorite vegetable")
favPasta := flag.String("pasta", "UNKNOWN", "the user's favorite type of pasta")
favMeal := flag.String("meal", "UNKNOWN", "the user's all-time favorite meal")
riceOrBeans := flag.String("rice-or-beans", "UNKNOWN", "whether the user prefers a side of rice or beans")
favDesert := flag.String("desert", "UNKNOWN", "the user's favorite desert (usually hot and sandy)")
favDessert := flag.String("dessert", "UNKNOWN", "the user's favorite dessert (usually a sweet after-meal treat)")
flag.Parse()
fmt.Printf(`Profile Name: %s %s
Rice or Beans: %s
Favorites:
Fruit: %s
Vegetable: %s
Pasta: %s
Desert: %s
Dessert: %s
Meal: %s
`, *firstName, *lastName, *riceOrBeans, *favFruit, *favVeg, *favPasta, *favDesert, *favDessert, *favMeal)
}
And if we check out help text:
go run example-code/02-command-line-arguments-and-flags/02-flags/02-profile-saver/main.go -h
Usage of /tmp/go-build2677080627/b001/exe/main:
-desert string
the user's favorite desert (usually hot and sandy) (default "UNKNOWN")
-dessert string
the user's favorite dessert (usually a sweet after-meal treat) (default "UNKNOWN")
-first-name string
the first name of the user profile (default "UNKNOWN")
-fruit string
the user's favorite fruit (default "UNKNOWN")
-last-name string
the last name of the user profile (default "UNKNOWN")
-meal string
the user's all-time favorite meal (default "UNKNOWN")
-pasta string
the user's favorite type of pasta (default "UNKNOWN")
-rice-or-beans string
whether the user prefers a side of rice or beans (default "UNKNOWN")
-veg string
the user's favorite vegetable (default "UNKNOWN")
So much better. Now our program has clear flagged arguments, defaults set, and some help text to explain what each flag is intended for. However, this could be cleaned up a bit more. To clean up the help text, let’s dig into constants.
Constants
Constants work very similarly to variables, but with one critical difference: They cannot be changed at runtime. Constants are set in your source code and compiled-in, after that, the value cannot be changed. Constants are helpful to document and organize variables that control large pieces of your application or that are used in a variety of places, but the values won’t change.
Let’s dig in with an example. Let’s say you were making a program that talks to
a website to get information. The functions you write to talk to the website are
all going to use the same address over and over and over again. Instead of
typing https://www.example.com over and over again, you can put this address
in a constant declaration near the top of your file, then just reference it
using the constant name:
package main
import (
"fmt"
)
const (
website = "<https://www.example.com>"
)
func main() {
fmt.Println(website)
}
So constants work kinda like variables, but let’s see what happens when we try
to change the value in our main function:
package main
import (
"fmt"
)
const (
website = "<https://www.example.com>"
)
func main() {
fmt.Println(website)
website = "A new value"
fmt.Println(website)
}
When we run it, we get this:
main.go:13:2: cannot assign to website (untyped string constant "<https://www.example.com>")
The constants must remain constant. Trying to change them results in the program refusing to build.
So, constants can be useful to help avoid re-typing, but they can also help
generally clean up your code. Right now in our profile-saver program, we have a
lot of loose help text that we can clean up by moving those help strings from
the flag.String functions into a constant declaration. So we can turn this:
func main() {
firstName := flag.String("first-name", "UNKNOWN", "the first name of the user profile")
lastName := flag.String("last-name", "UNKNOWN", "the last name of the user profile")
favFruit := flag.String("fruit", "UNKNOWN", "the user's favorite fruit")
favVeg := flag.String("veg", "UNKNOWN", "the user's favorite vegetable")
favPasta := flag.String("pasta", "UNKNOWN", "the user's favorite type of pasta")
favMeal := flag.String("meal", "UNKNOWN", "the user's all-time favorite meal")
riceOrBeans := flag.String("rice-or-beans", "UNKNOWN", "whether the user prefers a side of rice or beans")
favDesert := flag.String("desert", "UNKNOWN", "the user's favorite desert (usually hot and sandy)")
favDessert := flag.String("dessert", "UNKNOWN", "the user's favorite dessert (usually a sweet after-meal treat)")
...
Into this:
const (
firstNameHelp = "the first name of the user profile"
lastNameHelp = "the last name of the user profile"
fruitHelp = "the user's favorite fruit"
vegHelp = "the user's favorite vegetable"
pastaHelp = "the user's favorite type of pasta"
mealHelp = "the user's all-time favorite meal"
riceOrBeansHelp = "whether the user prefers a side of rice or beans"
desertHelp = "the user's favorite desert (usually hot and sandy)"
dessertHelp = "the user's favorite dessert (usually a sweet after-meal treat)"
)
func main() {
firstName := flag.String("first-name", "UNKNOWN", firstNameHelp)
lastName := flag.String("last-name", "UNKNOWN", lastNameHelp)
favFruit := flag.String("fruit", "UNKNOWN", fruitHelp)
favVeg := flag.String("veg", "UNKNOWN", vegHelp)
favPasta := flag.String("pasta", "UNKNOWN", pastaHelp)
favMeal := flag.String("meal", "UNKNOWN", mealHelp)
riceOrBeans := flag.String("rice-or-beans", "UNKNOWN", riceOrBeansHelp)
favDesert := flag.String("desert", "UNKNOWN", desertHelp)
favDessert := flag.String("dessert", "UNKNOWN", dessertHelp)
...
There’s still a lot of text, but its a bit more organized and easier to work
with. This is one of the nicest bonus features of constants: They make great
organizational tools. If you have certain aspects of your program that can be
tuned by yourself or another developer, it may be a good idea to put them in a
const declaration at the top of the file so they’re easy to access and
conveniently grouped.
Its worth keeping in mind that not everything can be a constant. Due to the way
the Go compiler works, complex expressions cannot be pre-evaluated. Said another
way: Constants are baked into your application when you run go build.
According to the Go language spec, the only
valid constant types are:
- Runes (characters)
- Strings
- Booleans (
trueorfalse) - Numeric types (
ints,floats, etc…)
If you want the same style of organization for types that don’t work as
constants, you can use a var declaration instead:
var (
aMap = map[int]string{
1: "one",
2: "two",
3: "three",
}
)
Appendix and References
References
Appendix
How Spaces Can Affect Flags
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go -greeting Hello There -name General
Hello, !
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go -greeting "Hello There" -name General
Hello There, General!
go run example-code/02-command-line-arguments-and-flags/02-flags/01-simple-flags/main.go -greeting="Hello There" -name General
Hello There, General!
