Reading from standard input with Go

on October 30, 2017. in Programming, Development. A 5 minute read.

Last year I started learning Go, then I stopped, and now I don’t remember anything what I learned.

Going to try it a bit different this time, by writing down what I do, learn, experiment.

As I don’t have a specific thing I want to build with Go, I’m just going to do simple scripts and examples, to get to know Go’s language specification and built-ins as much as possible.

Hopefully, there won’t be too much mistakes in my code samples. I’ve set up a golearn repository just for this, so feel free to create issues/PRs telling me where I went wrong.

Setting up the Go environment

I installed Go to the location that the official docs recommend — /usr/local, and I created the “Go workspace” in ~/projects/gocode and then I put my code under ~/projects/gocode/src/github.com/robertbasic/golearn.

I do find it kinda weird that everything lives in one “workspace”. But, whatever.

As for the editor, I’m using Visual Studio Code with the Go plugin.

I first tried using the vim-go plugin, but I couldn’t make it work in a custom runtimepath. I didn’t want to brick the Vim setup I have for my day-to-day work, so that’s why I tried a custom location.

Next I tried the Gogland from JetBrains, but when I ran it, my laptop sounded like a jet when the fans kicked off, so… yeah.

Then I tried Visual Studio Code, installed the Go plugin and I was pleasantly surprised when the editor figured out I was missing some Go programs like gofmt, golint, etc., and offered me to install them all. Great user experience for newcomers to Go.

About those docs…

After printing out the required “Hello, world!” to the terminal, I set out to figure out how to read in stuff from the standard input.

I would really love to say I figured it out all on my own how to do it, just by reading the official documentation, but I couldn’t. Maybe I don’t know how to use Go’s docs, maybe I’m spoiled by PHP’s excellent documentation, but after an hour or so flipping through the docs, I ended up Googling the solution.

From the documentation, I couldn’t figure out does the solution lie within the io, or the bufio package. Maybe both? I’ll have to tinker with them some more to understand the difference between the two.

From all the answers I found on the internet about this, I went with what seems the simplest way of reading from standard input: using a bufio.Scanner. Again, I’ll have to poke more around this, to see all the different ways I can accomplish this and compare the solutions.

The first “program”

The goal is to read whatever the user types in to standard input, line by line.

First, I have a slice of strings. Not an array, but a slice. I believe the main difference is that an array has a set size, while a slice’s size can change during execution.

// Inputs holds the inputs read in from stdin
type Inputs []string

I’m not yet 100% sure does this create a new type “Inputs” which is composed of a slice of strings, or is it just a slice of strings named “Inputs”? I think it’s the former, because I can use it as a return type-hint in a function.

Then in the main() I get a new scanner for the standard input:

scanner := bufio.NewScanner(os.Stdin)

It returns a pointer to the Scanner, so I pass it as such to my ReadInput function:

is := ReadInput(*scanner)

The ReadInput function takes a scanner of bufio.Scanner type and returns Inputs:

func ReadInput(scanner bufio.Scanner) Inputs

I declare two variables, one to hold the string read in from stdin, the other to “collect” all the input strings:

	var t string
	var i Inputs

Then I create an endless for loop, from which I break out when the user enters a new line only. In this loop the Scanner scans the stdin, and gets the text from it. If the text entered is not empty, I append it to the Inputs, as it is a slice of strings:

	for true {
		fmt.Print("Add a new todo [empty to stop reading]: ")
		scanner.Scan()
		t = scanner.Text()

		if err := scanner.Err(); err != nil {
			fmt.Println("Error reading from input: ", err)
		}

		// scanner.Text() strips new lines
		// so in case of just a new line
		// it's actually an empty string
		if t == "" {
			break
		}

		i = append(i, t)
	}

and finally I return the Inputs from the ReadInput function:

    return i

Back in main I do a “sanity” check by printing out the number of lines read in, and printing them out one by one:

	fmt.Printf("Got %d inputs from stdin:\n", len(is))

	for _, v := range is {
		fmt.Println(v)
	}

When executed, it looks something like this:

$ go run readstdin.go 
Hello, world!
Add a new todo [empty to stop reading]: Learn go
Add a new todo [empty to stop reading]: Write blog posts
Add a new todo [empty to stop reading]: 
Got 2 inputs from stdin:
Learn go
Write blog posts

The entire file is available here.

With this I learned a bit about Go’s types, creating functions, using the for loop, pointers, slices vs. arrays, and, of course, reading stuff from the standard input.

Happy hackin’!

P.S.: It feels good to have a beginners mind, again, about something.