Go diamond

Lately, I’ve been using Go for things that I used to use Perl, Python or Ruby for. This includes quick and dirty scripts for filtering text.

Perl’s “diamond” operator (<>) encapsulates all of this one fell swoop.

#!/usr/bin/env perl

use v5.20;
use warnings;

while (<>) {
    # do something with $_ here
    print;
}

Without any arguments, this will read from stdin line by line. If there are arguments, it will treat them as filenames and read from each of them line by line. If any of those arguments is “-“, it will take that to mean stdin. It’s the perfect thing for the Unix command line. Indeed, we can write one-liners that do all of the above with just a -p flag (or a -n flag, without the print).

In Python, we have a similar capability with the fileinput module.

#!/usr/bin/env python

import fileinput

for line in fileinput.input():
    # do something with line
    print(line, end="")

In Ruby, we iterate through ARGF

#!/usr/bin/env ruby

ARGF.each do |line|
    # do stuff with line
    print line
end

In short, Perl, Python, and Ruby each make it super easy to write command line utilities that just do the right thing. How do we do something similar in Go?

Well, none of it is hard, but there really is quite a lot going on those tiny little snippets above. That becomes apparent when you write it all out “by hand” in a language like Go. Here’s what I came up with.

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {

    filenames := []string{"-"}

    if len(os.Args) > 1 {
        filenames = os.Args[1:]
    }

    for _, filename := range filenames {

        var file *os.File
        var err error

        if filename == "-" {
            file = os.Stdin
        } else {
            if file, err = os.Open(filename); err != nil {
                fmt.Fprintln(os.Stderr, err)
                continue
            }
            defer file.Close()
        }

        scanner := bufio.NewScanner(file)
        for scanner.Scan() {

            line := scanner.Text()

            // do something with line here

            fmt.Println(line)
        }
        if err := scanner.Err(); err != nil {
            fmt.Fprintln(os.Stderr, err)
            continue
        }
    }
}

But wait, there’s more! Perl and Ruby are keeping track of the line numbers already too. So is Python’s fileinput. If we wanted to print those out, we’d just print out “$.” in Perl and Ruby and “fileinput.lineno()” in Python. In Go, we’d have to create a variable to keep track of those as well.

But doing so, we’d know exactly whether we had the line number for each file or for the total. In Perl, Python, and Ruby, we have to take some care to figure out whether it’s per file or not. I think it’s little things like this that cause me to not miss the brevity of Perl, Python, and Ruby when I’m writing Go.

Advertisements
Go diamond

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s