15. Getting input

So far you have written every value yourself: predictable, but not interactive. This chapter reads what the user types into the terminal.

Go needs a few more lines for this than for printing, and three more packages. This chapter goes through them one at a time.

Reading one line

Here is the whole program, from exercises/15/01-read-name.go:

// Chapter 15 example: read a name and greet.
// Run with: go run exercises/15/01-read-name.go

package main

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

func main() {
    reader := bufio.NewReader(os.Stdin)

    fmt.Println("What is your name?")
    text, _ := reader.ReadString('\n')
    name := strings.TrimSpace(text)

    fmt.Println("Hello, " + name)
}

Run it. It prints the question, waits for your name and Enter, prints the greeting, then exits. Three lines do the new work:

  • reader := bufio.NewReader(os.Stdin) makes a variable that can read from the keyboard. os.Stdin ("standard input") is what the user types into the terminal, and bufio is the package that reads it in convenient pieces. A program needs this line once, before its first read.
  • text, _ := reader.ReadString('\n') waits for the user, and reads everything up to and including the end of the line. '\n' means "stop at the newline". It has single quotes because it is one character and not a string. Like strconv.Atoi in Chapter 14, it gives back two values, and _ throws away the second. Chapter 21 explains it.
  • name := strings.TrimSpace(text) removes the line ending. The next section explains why you cannot skip this.

Open exercises/15/01-read-name.go and run it twice with different names. Watch the greeting change to match.

Why TrimSpace is required

ReadString keeps the line ending as part of the text. On Windows, pressing Enter puts two characters at the end of the line: \r and \n. So when you type Keiko, text holds seven bytes, not five.

The marker %q prints a string with quotes around it and its special characters visible. exercises/15/05-line-ending.go uses it to show what arrives. On Windows, typing 7 and pressing Enter prints:

Type a number: 7
"7\r\n"
"7"
Without TrimSpace: 0
With TrimSpace: 7

strings.TrimSpace removes spaces, tabs, and line endings from both ends of a string. Without it, two things go wrong:

  • Text prints badly. fmt.Println("Hello, " + text + "!") puts the ! on the line below the name, because the line ending is inside text.
  • Numbers come out as 0. To strconv.Atoi, "7\r\n" is not a number, as the last lines of the output above show.

So every read in this book is followed by strings.TrimSpace.

The prompt on the same line

fmt.Println moves to a new line after the question, so the user types below it. To keep the cursor on the same line, use fmt.Print, which adds no new line, and end the prompt with a space. Inside main, with the same imports:

reader := bufio.NewReader(os.Stdin)

fmt.Print("What is your name? ")
text, _ := reader.ReadString('\n')
name := strings.TrimSpace(text)

fmt.Println("Hello, " + name)

Output:

What is your name? Keiko
Hello, Keiko

This is exercises/15/02-prompt-same-line.go.

Reading a number

ReadString always gives a string, even for digits. To do maths, trim it and convert it with strconv.Atoi from Chapter 14. Add "strconv" to the imports:

reader := bufio.NewReader(os.Stdin)

fmt.Print("Enter a number: ")
text, _ := reader.ReadString('\n')
number, _ := strconv.Atoi(strings.TrimSpace(text))

fmt.Printf("Twice that is %d\n", number*2)

The conversion line has two calls in one. The inner call runs first and gives the trimmed string; the outer one turns it into an int. This is exercises/15/03-read-number.go.

For a decimal, use strconv.ParseFloat the same way:

reader := bufio.NewReader(os.Stdin)

fmt.Print("Enter a decimal: ")
text, _ := reader.ReadString('\n')
x, _ := strconv.ParseFloat(strings.TrimSpace(text), 64)

fmt.Printf("Twice that is %.2f\n", x*2)

If the user types something that is not a whole number, such as abc, 1.5, or nothing at all, strconv.Atoi gives 0. The program does not crash and prints no message; it carries on with 0, as though the user had typed it. strconv.ParseFloat does the same. The second value that _ throws away is the only thing that reports the problem. You will check it in Chapter 21. For now, type numbers when the program asks for them, and if an answer comes out wrong, look at what you typed. (One exception: a whole number too large for an int gives the largest int, not 0.)

Reading several values

To read several values, call ReadString once per value, always on the same reader:

reader := bufio.NewReader(os.Stdin)

fmt.Print("First number:  ")
text, _ := reader.ReadString('\n')
a, _ := strconv.Atoi(strings.TrimSpace(text))

fmt.Print("Second number: ")
text, _ = reader.ReadString('\n')
b, _ := strconv.Atoi(strings.TrimSpace(text))

fmt.Printf("Sum: %d\n", a+b)

Each call pauses and waits separately. The program does not continue to the next line until Enter is pressed. This is exercises/15/04-read-two.go.

Look closely at the second read. text already exists, so it uses =, and _ is not a variable, so it does not count as new. Writing := there stops with no new variables on left side of :=. On the line below, b, _ := is fine, because b is new.

You repeat these lines for each value. Chapter 21 shows how to put them in a function of your own and write them once.

Homework

Homework starters import only fmt, and their top comment shows the imports you need. Remember that the Go extension for VS Code may add them for you when you save.

Problem 1 — Greet by name

Open exercises/15/homework/01-greet-by-name.go. Prompt for a first name with fmt.Print, read it, trim it, then print Hello, <name>!. The exclamation mark is part of the output.

Problem 2 — Sum of two

Open exercises/15/homework/02-sum-of-two.go. Prompt for two numbers (separate prompts), convert each with strconv.Atoi, then print the sum like this:

a + b = c

where a, b, c are the actual values.

Problem 3 — Years to retirement

Open exercises/15/homework/03-retirement.go. Ask the user's age and compute the years left until 65. Print:

You have N years until retirement.

(If someone is over 65, the number is negative. That is fine — no need to handle that case yet.)

Challenge — BMI

Open exercises/15/homework/04-bmi.go. Ask for height in metres (a decimal like 1.78) and weight in kilograms (like 72.5). Use strconv.ParseFloat to convert both. Compute Body Mass Index:

bmi := weight / (height * height)

Print the result to one decimal place using fmt.Printf with %.1f. Label it clearly.

Stuck or finished? Open the homework solutions page.