День 08 · Циклы: for, границы, накопители · страница 5 из 7

Строки ввода до конца

В день 07 программа читала столько строк, сколько раз написано scanner.Scan(). В накладной их сколько угодно — значит, Scan встанет в заголовок цикла.


for scanner.Scan()

Ситуация. Обработать каждую строку ввода, сколько бы их ни было.

Код для чтения · разбираем, набирать не нужно
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		fmt.Println("прочитал:", scanner.Text())
	}
	fmt.Println("ввод кончился")
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ printf '12\n7\n3\n' | go run .
прочитал: 12
прочитал: 7
прочитал: 3
ввод кончился

Как это читать. В день 07 вы вызывали scanner.Scan() отдельной строкой и не смотрели, что он возвращает. А возвращает он bool: true — строка прочитана, false — читать нечего. Поэтому вызов и годится в условие, как stock > 0 на странице 3: пока Scan() отдаёт true, выполняется тело, и scanner.Text() в нём даёт прочитанную строку без перевода строки.

Где кончается ввод, зависит от того, откуда он пришёл.

Откуда ввод Как запускали Когда Scan() вернёт false
файл go run . < lines.txt после последней строки файла
конвейер printf … | go run . когда printf закончил печатать
клавиатура go run . когда нажали Ctrl+D в начале строки

Попробуйте сейчас: ввод из конвейера и из файла.

Цель: программа читает строки и из printf, и из файла.

1. Наберите её в scratch/main.go — файлы для опытов там уже лежат:

✎ Наберите в файл scratch/main.go
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		fmt.Println("прочитал:", scanner.Text())
	}
	fmt.Println("ввод кончился")
}

2. Запустите:

▶ Выполните
cd ~/gocourse/day08/scratch
printf '12\n7\n' | go run .
go run . < lines.txt

В lines.txt вторая строка пустая: Text() вернул пустую строку, и цикл её не пропустил.

Готово, когда: оба запуска кончаются строкой ввод кончился, а во втором вторая строка — прочитал: без числа; пункты t_file и t_pipe.


С клавиатуры: Ctrl+D

Без < и | строки читаются с клавиатуры: каждая уходит по Enter, конец ввода — Ctrl+D в начале новой строки. Запустили и ничего не набрали — программа молчит: это не зависание, она ждёт строку. Ctrl+D значит «ввод кончился», цикл заканчивается и итог печатается; Ctrl+C — «прекратить», строка после цикла не выполнится. Оба сеанса — в course extra 08.


Пустые строки и ошибка в строке

Ситуация. В выгрузке бывают пустые строки, пробелы по краям и опечатки вроде 3шт: пустые пропустить, на опечатке остановиться и назвать номер строки.

Код для чтения · разбираем, набирать не нужно
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	line := 0
	for scanner.Scan() {
		line++
		text := strings.TrimSpace(scanner.Text())
		if text == "" {
			fmt.Println("строка", line, "пустая — пропускаю")
			continue
		}
		qty, err := strconv.Atoi(text)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Ошибка в строке", line, err)
			os.Exit(1)
		}
		fmt.Println("строка", line, "количество", qty)
	}
	fmt.Println("Ввод кончился, строк:", line)
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run . < lines.txt
строка 1 количество 12
строка 2 пустая — пропускаю
строка 3 количество 7
строка 4 количество 3
Ввод кончился, строк: 4
stagiaire@lab:~/gocourse/day08/scratch$ go run . < bad.txt
строка 1 количество 12
строка 2 количество 7
строка 3 пустая — пропускаю
Ошибка в строке 4 strconv.Atoi: parsing "3шт": invalid syntax
exit status 1

Как это читать.

Собрать сообщение по шаблону поможет пара к Fprintln — fmt.Fprintf(os.Stderr, "…", …): то же, что Printf, только первым аргументом сказано, куда печатать.


Попробуйте сейчас: где считать номер строки.

Цель: увидеть, как место line++ в теле меняет номера строк в выводе.

1. Замените программу в scratch/main.go на эту:

✎ Наберите в файл scratch/main.go
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	line := 0
	for scanner.Scan() {
		line++
		text := strings.TrimSpace(scanner.Text())
		if text == "" {
			fmt.Println("строка", line, "пустая — пропускаю")
			continue
		}
		qty, err := strconv.Atoi(text)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Ошибка в строке", line, err)
			os.Exit(1)
		}
		fmt.Println("строка", line, "количество", qty)
	}
	fmt.Println("Ввод кончился, строк:", line)
}

Запустите три ввода:

▶ Выполните
cd ~/gocourse/day08/scratch
go run . < lines.txt
go run . < bad.txt
printf '12\n7\n' | go run .

2. Перенесите line++ на строку после if text == "" { … } и снова запустите на bad.txt:

▶ Выполните
go run . < bad.txt

Должно получиться:

Вывод · это печатает программа, набирать не нужно
строка 1 количество 12
строка 2 количество 7
строка 2 пустая — пропускаю
Ошибка в строке 3 strconv.Atoi: parsing "3шт": invalid syntax
exit status 1

3. В файле 3шт — четвёртая строка. Почему пустая названа второй, а ошибка — в строке 3? Верните line++ первой строкой тела.

Готово, когда: вывод совпал с блоком выше, вы можете объяснить, почему номера уехали, и line++ снова первой строкой тела.


Что может пойти не так

Самая коварная ошибка здесь — лишний scanner.Scan() перед циклом: он читает первую строку, Text() её никто не берёт, и строка теряется молча (код и вывод — в course extra 08).

Что видите Что это значит Что делать
первая строка ввода не обработана лишний scanner.Scan() до цикла убрать: читает только заголовок
non-boolean condition in for statement написано for scanner.Scan {: без скобок это не вызов for scanner.Scan() {
parsing "3шт": invalid syntax, потом exit status 1 в строке не число, программа вышла с кодом 1 так и задумано
номер строки в сообщении меньше настоящего счётчик стоит после continue line++ — первой строкой тела

Дальше: накопители — сумма, количество и среднее по всем строкам — course next