Filling a Slice Using Command-line Flags in Go (Golang)

I wanted to be able to specify a particular command-line flag more than once in a Go program. I was about to throw my hands up in despair because I didn’t think that the Go flag package could process multiple instances of a command-line flag. I was wrong.

While I was tempted to write my own command-line options parser, I chose to find the idiomatic way to approach the problem.  If I have learned nothing else from my GoMentors, I have learned to try to follow the idioms and to try not to reinvent the wheel.

I found an example by visiting http://golang.org/pkg/flag/ . I had to search for the string “FlagSet” in my browser.  Immediately under the paragraph where the word “FlagSet” first appears, is a clickable item labeled “Example”.  Click the example item and take a look at the code.

I copied the code and toyed with it until I thought I understood it.  Then, I tried to simplify it and rewrite it.  My example program will simply accept one or more command-line flags with the label -i.  Each argument to -i should be an integer.  I want to be able to specify -i multiple times on the command-line.  The program should populate a slice of integers while adhering to the above command-line syntax.

Here’s my code … flagstuff.go :


// Copyright 2013 - by Jim Lawless
// License: MIT / X11
// See: http://www.mailsend-online.com/license2013.php
//
// Bear with me ... I'm a Go noob.

package main

import (
	"flag"
	"fmt"
	"strconv"
)

// Define a type named "intslice" as a slice of ints
type intslice []int

// Now, for our new type, implement the two methods of
// the flag.Value interface...
// The first method is String() string
func (i *intslice) String() string {
	return fmt.Sprintf("%d", *i)
}

// The second method is Set(value string) error
func (i *intslice) Set(value string) error {
	fmt.Printf("%s\n", value)
	tmp, err := strconv.Atoi(value)
	if err != nil {
		*i = append(*i, -1)
	} else {
		*i = append(*i, tmp)
	}
	return nil
}

var myints intslice

func main() {
	flag.Var(&myints, "i", "List of integers")
	flag.Parse()
	if flag.NFlag() == 0 {
		flag.PrintDefaults()
	} else {
		fmt.Println("Here are the values in 'myints'")
		for i := 0; i < len(myints); i++ {
			fmt.Printf("%d\n", myints[i])
		}
	}
}

Let’s dissect the code one section at a time … not necessarily in the order presented in the source code above.

First, I define a type called intslice that refers to a slice of ints:

type intslice []int

Later, I define a variable named myints of type intslice.

var myints intslice

Later in the code, I’m going to be calling flag.Var() passing in &myints as the first argument.  The type of the first value to flag.Var() must conform to the flag.Value interface which is defined as:

type Value interface {
    String() string
    Set(string) error
}

I must now define a String() method and a Set() method for my intslice type:

func (i *intslice) String() string {
	return fmt.Sprintf("%d", *i)
}

func (i *intslice) Set(value string) error {
	fmt.Printf("%s\n", value)
	tmp, err := strconv.Atoi(value)
	if err != nil {
		*i = append(*i, -1)
	} else {
		*i = append(*i, tmp)
	}
	return nil
}

The above methods will be called by the parsing engine in the flag package when I invoke flag.Parse(). In the String() method, I need to return a string-representation of the argument. In the Set() method, I then need to append the string value to the specified intslice variable by first converting value to an int variable named tmp. If an error occurs during conversion, I append an int value of -1 to the intslice variable.

The main body looks like this:

func main() {
	flag.Var(&myints, "i", "List of integers")
	flag.Parse()
	if flag.NFlag() == 0 {
		flag.PrintDefaults()
	} else {
		fmt.Println("Here are the values in 'myints'")
		for i := 0; i < len(myints); i++ {
			fmt.Printf("%d\n", myints[i])
		}
	}
}

Here are a few sample command-line invocations and the output that they produce ( I’ve added a blank line between each command and the counterpart response lines for clarity):

No parameters. I’ve added a check to make sure that more than zero flags are specified.

flagstuff

  -i=[]: List of integers

Let’s specify an invalid flag (-x):

flagstuff -x

flag provided but not defined: -x
Usage of flagstuff:
  -i=[]: List of integers

Let’s specify -i without an argument:

flagstuff -i

Usage of flagstuff:
  -i=[]: List of integers

Now, let’s specify a single -i parameter with an integer value:

flagstuff -i 5

5
Here are the values in 'myints'
5

At each invocation of the intslice.Set() method, I display the string that has been passed in so that I could observe the mechanics of the parsing process. In each example that provide arguments for -i, we’ll first see those values, then we’ll see what the slice contains via the for loop that occurs just a little later in the code.

Let’s specify a string instead of an int as an argument:

flagstuff -i twelve

twelve
Here are the values in 'myints'
-1

Note that this causes the error condition in the call to strconv.Atoi(). I have chosen to add the value -1 to the slice when the argument doesn’t cleanly parse as an integer. You may choose to handle the error differently.

Here is an example with three valid integers:

flagstuff -i 5 -i 6 -i 7

5
6
7
Here are the values in 'myints'
5
6
7

Note that the example at golang.org contains a section that splits the string value passed to Set() based on the presence of the comma character. This allows that code to also accept multiple arguments to a single command-line flag. I have chosen to avoid doing that to simplify my example.

Knowing how to handle multiple occurrences of a given flag without customizing the command-line parser is going to be very helpful for a couple of programs that I plan to write. I’m glad that I spent the time going over the example golang.org code. I hope to tinker with more exotic command-line processing features of the flag package in the near future.

About Jim Lawless

I've been programming computers for well over four decades. I've been a teacher, I've worked as a consultant, and have written articles here and there for publications like Dr. Dobbs Journal, The C/C++ Users Journal, Nuts and Volts, and others. I host the podcast Stray Pointers.
This entry was posted in Programming and tagged . Bookmark the permalink.

2 Responses to Filling a Slice Using Command-line Flags in Go (Golang)

  1. Why not just use flag.String() and accept a comma-delimited string of ints?

    • Jim Lawless says:

      Hi, David.

      To be honest, I would have loved to parse the entire command-line without using the flag library. I have a process that I’ve used in other languages, but alas … it would not be idiomatic Go. I was hesitant to manually parse an item that had already been parsed as a string. I chose integers arbitrarily for this example. It’s more likely that my command-line code will have string-based parms that can occur multiple times. I thought that the integers were more illustrative of what needed to be done so that one could snap a new “type” into the flag engines.

      Long answer to a short question. The short answer is … I wanted to use my preferred parameter format and remain harmonic with Go’s idioms. I hope I accomplished that.

Leave a comment