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.
Command Line Arguments
What are command line arguments?
Command line arguments are one way to pass user input into your application.
Let’s take a look at the common Unix utility ls. ls is a small application
that lists files and directories, just like double-clicking on a folder in
Windows Explorer, ls allows you to peer into directories and get additional
information about files, like ownership information, size, last modification
timestamps, and more. ls, like many command line applications, can take in
arguments:
ls some-directory
In this example ls is the application you are running and some-directory is
the argument. What you’re asking ls to do is give you a listing of all files
and folders inside of some-directory.
How can my program use arguments?
Because programming is generally open-ended, you can utilize command line arguments to control any aspect of your program. You could use an argument to control which IP address and port your application listens for requests on, or what to print, or if an operation should target your test or production environments. Let’s take a look at a simple example:
Here’s a simple Go program that will print a person’s name and age on the command line:
01-simple-arguments/main.go:
package main
import (
"fmt"
"os"
)
func main() {
fmt.Printf("My name is %s and my age is %s.", os.Args[1], os.Args[2])
}
So if we run go run example-code/02-command-line-arguments-and-flags/01-command-line-arguments/01-simple-arguments/main.go Bob 99, we’ll see: My name is Bob and my age is 99.
In this example, we use the os package to get an array of arguments provided
on the command line. Arrays in Go start at 0, so we’re actually printing the
second and third argument. The first argument is the location of the program
we’re running. Since we’re using go run, it will be in a temporary,
auto-generated location. Let’s add this block of code to our program and look at
each argument individually:
fmt.Println("Our program's arguments:")
for i, arg := range os.Args {
fmt.Printf("os.Args[%d]: '%s'\\n", i, arg)
}
This code will print out each argument and its number:
Our program's arguments:
os.Args[0]: '/tmp/go-build201851007/b001/exe/main'
os.Args[1]: 'Bob'
os.Args[2]: '99'
So /tmp/go-build201851007/b001/exe/main is the temporary compiled version of
our program, made by the go run command. The other arguments, Bob and 99
are the arguments we provided on the command line.
So, what happens if we provide less than 2 arguments to our program?
go run example-code/02-command-line-arguments-and-flags/01-command-line-arguments/01-simple-arguments/main.go Bob
panic: runtime error: index out of range [2] with length 2
goroutine 1 [running]:
main.main()
/home/samurailink3/git/go-twitter/example-code/02-command-line-arguments-and-flags/01-command-line-arguments/01-simple-arguments/main.go:9 +0x21c
exit status 2
Uh oh… panic: runtime error: index out of range [2] with length 2… That
doesn’t look good. What happened is we tried to access a variable that was
outside of the array. Here’s a simple diagram of the os.Args array and what we’re trying to access:
Arg 0: Our program Arg 1: Our provided name Arg 2: What we're trying to access
| | |
v v v
[ "/tmp/go-build201851007/b001/exe/main", "Bob" ]
The os.Args array only contains 2 items (os.Args[0] and os.Args[1]), but
we’re trying to access something in the third place (os.Args[2]), beyond the
end of the array. This is an issue, so the code panics. How can we defend
against that? We want to avoid panicing and let the user know they’re missing
a required field.
The len check:
if len(os.Args) < 3 {
// Do something here
}
We can get the number of items present in the os.Args array and do something
if we don’t have enough. First, let’s print out a helpful message for the user:
if len(os.Args) < 3 {
fmt.Println("This program requires two arguments: Name and Age, you have", len(os.Args)-1)
}
When we run this with only the first argument, we get:
go run example-code/02-command-line-arguments-and-flags/01-command-line-arguments/01-simple-arguments/main.go Bob
This program requires two arguments: Name and Age, you have 1
panic: runtime error: index out of range [2] with length 2
goroutine 1 [running]:
main.main()
/home/samurailink3/git/go-twitter/example-code/02-command-line-arguments-and-flags/01-command-line-arguments/01-simple-arguments/main.go:13 +0x29b
exit status 2
Generally, we don’t want to show panics to the user, so let’s exit with a non-successful error code (to learn more about error codes, check out the Advanced Bash-Scripting Guide):
if len(os.Args) < 3 {
fmt.Println("This program requires two arguments: Name and Age, you have", len(os.Args)-1)
// See "Advanced Bash-Scripting Guide: Exit Codes With Special Meanings"
// in the References section for why we use "2" here.
os.Exit(2)
}
Now when we run our program with the missing argument, we get:
go run example-code/02-command-line-arguments-and-flags/01-command-line-arguments/01-simple-arguments/main.go Bob
This program requires two arguments: Name and Age, you have 1
exit status 2
That’s way cleaner. Now if we run it with the right number of arguments, we see:
go run example-code/02-command-line-arguments-and-flags/01-command-line-arguments/01-simple-arguments/main.go Bob 99
My name is Bob and my age is 99.
Our program's arguments:
os.Args[0]: '/tmp/go-build1771918657/b001/exe/main'
os.Args[1]: 'Bob'
os.Args[2]: '99'
When should I use arguments?
Arguments are best used when an application has a limited number of distinct options or your program operates on an unlimited number of the same argument. Let’s expand that previous statement:
So git is a great example of having a limited (and segmented) number of
distinct options. It can do a lot, but the options are segmented by
sub-command. So, something git branch would have pretty simple arguments, like
git branch -D branch-name and git branch --track local-branch remote-branch.
There are a couple of arguments to keep in your head, not too bad.
Another program we talked about earlier, ls, is a great example of having an
unlimited number of the same argument. So you can run ls with zero arguments,
which just shows the files and folders present in your current directory, but we
can also run ls some-directory and-another-one but-also-a-third to see what’s
inside those three distinct directories. ls supports an unlimited number of
arguments, but each one does the same thing, so its not hard to remember or
think about.
Let’s say you had a program to insert profile information into a database. You track the following information about people:
- First Name
- Last Name
- Favorite Fruit
- Favorite Vegetable
- Favorite Pasta
- Favorite Meal
- Rice or Beans
- Favorite Desert
- Favorite Dessert
Your application in use may look like this:
new-profile Bob Sacamano Apple GreenBeans Manicotti Steak Sahara Sundae
Your users will need to keep this whole list in their heads in order. That’s a pretty bad user experience and its likely that someone will forget a field or worse, get the order of the arguments wrong. This would add data into the wrong fields in your database. There are too many options to keep track of here, arguments aren’t a good choice for this program. Case-in-point: The above example forgets the “Rice or Beans” field. Did you catch it?
This application would be perfect for flags, so we’ll cover that in the next section.
Appendix and References
References
- Advanced Bash-Scripting Guide: Exit Codes With Special Meanings
- There are few hard rules for exit codes. Exit 0 usually means everything worked as expected, no errors. Exit 1 usually means something went wrong. Exit 2 is commonly used to indicate a problem with the command itself (too many arguments, too few, etc). These “rules” and prefaced with “usually/commonly” because exit codes are ultimately up to the whims of the developer.
