Chapter 6 - Slices
Exercise 3: Command-line Arguments
CSV is a common file format used to represent spreadsheet data in plain-text files. Go includes an encoding/csv
package that can read this format.
We’re working on a program that accepts the name of a CSV file and a column number as command-line arguments. The program should go through the file and print only the requested column.
So, for example, if you save the following data as gophers.csv
:
first_name,last_name,username
"Rob","Pike",rob
Ken,Thompson,ken
"Robert","Griesemer","gri"
And you run the program with pcolumn gophers.csv 2
, it should output:
last_name
Pike
Thompson
Griesemer
We’ve written the printColumn
function for you, which reads the specified file, prints the specified column, and returns a non-nil error value if it encounters any problems. We’ve also written a check
function that will log an error an exit the program, unless the error is nil
.
Your task is to update the main
function to read the two command-line arguments, check that they’re valid, and use them to make an appropriate call to printColumn
. We’ve added comments to main
that describe what you’ll need to do.
Solution
// pcolumn prints the contents of a specified column from a
// CSV file. It takes two command-line arguments: the name
// of the file to read, and the column number to print.
// Example:
//
// pcolumn my.csv 2
package main
import (
"encoding/csv"
"fmt"
"io"
"log"
"os"
"strconv"
)
// printColumn reads the specified file and prints the
// specified column from each row. A non-nil return
// value indicates an error was encountered.
func printColumn(fileName string, column int) error {
// Open the file for reading, and return any error.
file, err := os.Open(fileName)
if err != nil {
return err
}
// Ensure the file gets closed, even if there's an
// error. We'll talk about "defer" in chapter 12.
defer file.Close()
// Set up a new csv.Reader that reads from the file.
reader := csv.NewReader(file)
// Loop until an error is encountered.
for {
// Read the next row.
row, err := reader.Read()
// Reaching the end of the file is an "error", but
// it's an expected one, so just return.
if err == io.EOF {
return nil
} else if err != nil {
// Return any other type of error, because
// those are NOT expected.
return err
}
// If a column outside the row was requested,
// return an error.
if int(column) > len(row) {
return fmt.Errorf("invalid column: %d", column)
}
// Otherwise, print the requested column.
fmt.Println(row[column-1])
}
}
// check logs an error and exits if the error is not nil.
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
func main() {
// YOUR CODE HERE: Check the length of os.Args to see
// if the user gave exactly two command-line arguments.
// If not, log an error message and exit. Remember that
// the first element of os.Args is always the name of
// the program that was run.
if len(os.Args) != 3 {
log.Fatal("usage: column <file name> <column number>")
}
// The first argument will be the file name.
fileName := os.Args[1]
// Call strconv.ParseInt with the second command-line
// argument, a base of 10, and a bitSize of 64.
columnNumber, err := strconv.ParseInt(os.Args[2], 10, 64)
// Pass the error value from parseInt to "check", so
// any error will be reported.
check(err)
// Call printColumn with the file name and column
// number. Note that ParseInt returns an int64 and
// printColumn wants an int, so you'll need to cast
// the type.
err = printColumn(fileName, int(columnNumber))
// Call "check" on the error value returned from
// printColumn.
check(err)
}