🧑‍💻

Banner Image: https://unsplash.com/photos/black-red-and-yellow-coated-wires-6ySmw7CwYDk

Software Engineering Essentials: Command Line Arguments

Published On:  

Tags:   Guide Programming Rant

Licenses:   CC-BY-4.0 | MIT
Filter By License


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.