Программа читает ввод
До сих пор все данные жили внутри программы: цена, количество, остаток были записаны прямо в коде. Чтобы посчитать другой заказ, приходилось открывать main.go, менять число и запускать снова. Настоящие программы так не работают: кладовщик вводит количество с клавиатуры, склад присылает файл выгрузки, одна программа передаёт данные другой.
Сегодня программа научится получать данные снаружи. И сразу окажется, что снаружи приходит что угодно — в том числе не то, что ждали. Поэтому вторая половина дня — про ошибки: как их заметить, как о них сообщить и как завершить программу так, чтобы другие программы поняли, что что-то пошло не так.
Первая программа с вводом
Ситуация. На приёмке кладовщик пересчитал коробки и хочет ввести число. Программа должна дождаться ввода и что-то с ним сделать. Для начала — просто показать, что прочитала.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Сколько штук пришло? ")
scanner.Scan()
line := scanner.Text()
fmt.Println("Вы ввели:", line)
}Что увидите. После go run . программа печатает вопрос и останавливается. Наберите 40 и нажмите Enter:
stagiaire@lab:~/gocourse/day07/scratch$ go run .
Сколько штук пришло? 40
Вы ввели: 40Цифры 40 после вопроса напечатала не программа: их набрали вы, а терминал показал набранное.
Как это читать.
| Строка | Что делает |
|---|---|
import ( … ) |
три пакета: bufio — чтение ввода, fmt — печать, os — связь с операционной системой |
scanner := bufio.NewScanner(os.Stdin) |
создать «читателя строк» scanner и подключить его к стандартному вводу os.Stdin — если ничего не менять, это клавиатура |
fmt.Print("Сколько штук пришло? ") |
приглашение. Print, а не Println: курсор остаётся в той же строке, сразу после вопроса |
scanner.Scan() |
прочитать одну строку: ждать, пока человек не нажмёт Enter |
line := scanner.Text() |
взять прочитанную строку — без самого Enter. Тип у неё string |
Новое в записи — точка после переменной: scanner.Scan(). До сих пор точка стояла после имени пакета: fmt.Println, strings.ToUpper. Здесь слева переменная scanner, а справа — действие, которое она умеет выполнять. Что это за переменные и откуда у них свои действия, разберём в блоке 5. Пока эти три строки — готовый рецепт: создать scanner, вызвать Scan(), забрать Text().
Почему нельзя сразу «прочитать число»? Всё, что приходит на ввод, — символы. Набрали 40 — пришли символ 4, символ 0 и перевод строки. Для программы это такая же строка, как сорок или 4О с буквой О. Превращать символы в число придётся отдельным шагом — на третьей странице. И этот шаг может не получиться.
Программа ждёт
Пока вы ничего не набрали, программа ничего не делает: она остановилась на scanner.Scan() и ждёт. Курсор мигает, приглашения терминала нет — это не зависание. Если программа не печатает вопрос, со стороны кажется, что не происходит вообще ничего. Поэтому первое, что стоит проверить, когда программа «молчит», — не ждёт ли она ввода.
Выйти из ожидания можно двумя способами.
Ctrl+D — «ввод закончен». Нажатый на пустой строке, он говорит программе, что ввода больше не будет: Scan() возвращается ни с чем, Text() отдаёт пустую строку, и программа идёт дальше:
stagiaire@lab:~/gocourse/day07/scratch$ go run .
Сколько штук пришло? Вы ввели: Обе надписи оказались в одной строке: Enter вы не нажимали, поэтому на новую строку никто не перешёл.
Если в строке уже набраны символы, первое Ctrl+D отдаёт их программе, но ввод не заканчивает: Scan() ждёт либо перевод строки, либо конец ввода, и на экране ничего не меняется. Чтобы строка дошла до программы, Ctrl+D нажимают второй раз — об этом ещё сказано в таблице «Что может пойти не так» в конце страницы.
Ctrl+C — прервать программу. Она завершается сразу и до конца main не доходит:
stagiaire@lab:~/gocourse/day07/scratch$ go run .
Сколько штук пришло? ^Csignal: interrupt^C — так терминал показывает нажатие Ctrl+C. signal: interrupt дописывает go run: программу остановили сигналом прерывания. Тот же Ctrl+C вы знаете с дня 01 — он останавливает любую команду в терминале.
Попробуйте сейчас: первая программа с вводом.
Цель: увидеть три исхода ожидания ввода — строка с Enter, Ctrl+D и Ctrl+C.
1. Замените содержимое
~/gocourse/day07/scratch/main.go— наберите первую программу с вводом и сохраните:✎ Наберите в файлscratch/main.gopackage main import ( "bufio" "fmt" "os" ) func main() { scanner := bufio.NewScanner(os.Stdin) fmt.Print("Сколько штук пришло? ") scanner.Scan() line := scanner.Text() fmt.Println("Вы ввели:", line) }2. Запустите три раза: наберите число и Enter; ничего не набирайте и нажмите Ctrl+D; запустите ещё раз и нажмите Ctrl+C.
▶ Выполнитеcd ~/gocourse/day07/scratch go run .3. Потом запустите и наберите вместо числа слово
сорок— программа напечатает и его: что лежит в строке, ей всё равно.Готово, когда: после числа и Enter программа напечатала
Вы ввели:с вашим числом; после Ctrl+D —Сколько штук пришло? Вы ввели:одной строкой; после Ctrl+C —^Csignal: interrupt; насорок—Вы ввели: сорок.
Две строки подряд
Ситуация. Кладовщик вводит название товара, потом склад, куда его приняли. Строк две.
Каждый вызов scanner.Scan() читает следующую строку. Две строки — два вызова. scanner при этом один на всю программу.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Товар: ")
scanner.Scan()
item := scanner.Text()
fmt.Print("Склад: ")
scanner.Scan()
store := scanner.Text()
fmt.Println("Принято:", item, "на склад", store)
}Что увидите.
stagiaire@lab:~/gocourse/day07/scratch$ go run .
Товар: Болт М6
Склад: Север
Принято: Болт М6 на склад СеверКак это читать. Строка — это всё, что набрано до Enter, вместе с пробелами: Болт М6 пришло целиком, одной строкой, а не двумя словами. Порядок работы такой: вопрос, ожидание, первая строка в item; вопрос, ожидание, вторая строка в store; печать.
Если после первой строки нажать Ctrl+D, второй Scan() ничего не прочитает, и store останется пустой:
stagiaire@lab:~/gocourse/day07/scratch$ go run .
Товар: Болт М6
Склад: Принято: Болт М6 на склад Программа при этом не упала и не пожаловалась — просто напечатала пустое место. Для склада это плохо: товар «принят» неизвестно куда. Как заметить, что ввода не было, — на следующей странице.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
undefined: bufio |
пакет не подключён | добавить "bufio" в import ( … ) |
undefined: os |
то же для os: os.Stdin есть, а "os" в import нет |
добавить "os" |
"bufio" imported and not used |
пакет подключён, а bufio. в коде нигде нет |
убрать строку из import или дописать код, который его использует |
scanner.Scan (value of type func() bool) is not used |
scanner.Scan без скобок: действие названо, но не выполнено |
scanner.Scan() |
not enough arguments in call to bufio.NewScanner и ещё две строки: have (), want (io.Reader) |
bufio.NewScanner() без аргумента: не сказано, откуда читать |
bufio.NewScanner(os.Stdin) |
программа собралась и напечатала что-то вроде Вы ввели: 0x… — адрес в памяти |
line := scanner.Text без скобок: в line попала не строка, а само действие, и печатается его адрес |
scanner.Text(). go vet . находит такое заранее: fmt.Println arg <имя> is a func value, not called |
| программа не ждёт и печатает пустую строку | нет scanner.Scan() перед Text(): строку никто не прочитал |
Scan() перед каждым Text() |
| программа ничего не печатает, приглашение терминала не возвращается | программа ждёт ввода | набрать строку и Enter; Ctrl+D — закончить ввод; Ctrl+C — прервать |
набрали 40, нажали Ctrl+D — ничего не произошло |
в строке уже есть символы: первое Ctrl+D отдаёт их программе, но ввод не заканчивает, а Scan() ждёт либо перевод строки, либо конец ввода |
нажать Ctrl+D ещё раз. Тогда программа напечатает Вы ввели: 40 вплотную к набранному 40, без перевода строки: Enter не нажимали, переводить нечего |
| терминал закрылся после Ctrl+D | программа уже закончилась, вернулось приглашение, и Ctrl+D получил сам терминал: для него это «работа окончена» | Ctrl+Shift+ — новый терминал, cd ~/gocourse/day07/scratch` обратно. Ctrl+D нажимать, только пока программа ждёт ввода |
Две строки have () и want (io.Reader) читаются так: «передано ничего, а нужно одно значение — то, из чего можно читать». os.Stdin как раз такое значение.
Попробуйте сейчас: три поломки программы с вводом.
Цель: увидеть сообщения о трёх частых поломках и записать их в словарик.
Наберите в
scratch/main.goпрограмму «Товар / Склад» из раздела «Две строки подряд»:✎ Наберите в файлscratch/main.gopackage main import ( "bufio" "fmt" "os" ) func main() { scanner := bufio.NewScanner(os.Stdin) fmt.Print("Товар: ") scanner.Scan() item := scanner.Text() fmt.Print("Склад: ") scanner.Scan() store := scanner.Text() fmt.Println("Принято:", item, "на склад", store) }Сломайте её тремя способами — по одному за раз, запуская
go run .после каждого:
- удалите строку
"bufio"изimport;- уберите скобки у первого
scanner.Scan();- уберите скобки у
scanner.Text()в строке сitem; запустите и наберите две строки, потом выполнитеgo vet ..После каждой поломки:
▶ Выполнитеcd ~/gocourse/day07/scratch go run .После третьей — ещё:
▶ Выполнитеgo vet .После каждой поломки верните как было. Каждое новое сообщение — строкой в
~/errors.md: текст ошибки, тире, что это значило.Готово, когда: вы видели
undefined: bufio,scanner.Scan (value of type func() bool) is not used, адрес вместо товара в выводе и находкуgo vet… is a func value, not called; программа снова как в «Двух строках», сообщения записаны в~/errors.md.
Дальше: ввод из конвейера и файла — course next