Руководство по чтению ввода с клавиатуры в Go
Многие первые программы общаются с пользователем через терминал: спрашивают имя, читают команду, принимают число, обрабатывают строки до EOF. Для этого Go использует обычную модель потоков: ввод приходит из stdin, вывод идет в stdout, ошибки - в stderr.
Клавиатура в терминале для программы выглядит как поток байтов. Пользователь печатает текст, нажимает Enter, и программа получает строку. Пакеты os, bufio, fmt и strings помогают читать этот поток разными способами.
os.Stdin
os.Stdin - стандартный поток ввода:
fmt.Fprintln(os.Stdout, "Введите имя:")
Для чтения его обычно оборачивают в bufio.Scanner или bufio.Reader.
Важно: stdin не всегда означает клавиатуру. Данные можно передать через pipe:
echo "Alice" | go run main.go
Для программы это все равно os.Stdin.
bufio.Scanner
Для чтения строк в учебных CLI чаще всего подходит bufio.Scanner:
scanner := bufio.NewScanner(os.Stdin) fmt.Print("Введите имя: ") if !scanner.Scan() { if err := scanner.Err(); err != nil { fmt.Fprintln(os.Stderr, "ошибка ввода:", err) } return } name := strings.TrimSpace(scanner.Text()) fmt.Printf("Привет, %s!\n", name)
Scan читает следующую строку. Text возвращает текст без \n. TrimSpace убирает пробелы в начале и конце.
Scanner удобен, когда ввод состоит из строк:
- имя пользователя;
- команды;
- строки файла;
- список значений, по одному на строку.
Чтение до EOF
EOF означает end of file - конец ввода. В терминале его можно отправить через Ctrl+D на macOS/Linux или Ctrl+Z, затем Enter на Windows.
scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { line := scanner.Text() fmt.Println("line:", line) } if err := scanner.Err(); err != nil { fmt.Fprintln(os.Stderr, "read input:", err) }
Такой код подходит для программ, которые работают с pipe:
cat input.txt | go run main.go
Лимит Scanner
У Scanner есть ограничение размера token. Для обычных строк это нормально, но для очень длинных строк может быть мало.
Можно увеличить буфер:
scanner := bufio.NewScanner(os.Stdin) buffer := make([]byte, 0, 64*1024) scanner.Buffer(buffer, 1024*1024)
Если вы читаете большие строки или произвольные блоки данных, иногда лучше использовать bufio.Reader.
bufio.Reader
bufio.Reader дает больше контроля:
reader := bufio.NewReader(os.Stdin) fmt.Print("Введите текст: ") text, err := reader.ReadString('\n') if err != nil && !errors.Is(err, io.EOF) { fmt.Fprintln(os.Stderr, "ошибка ввода:", err) return } text = strings.TrimSpace(text) fmt.Println("Вы ввели:", text)
ReadString('\n') читает до перевода строки и включает разделитель в результат. Поэтому часто нужен strings.TrimSpace.
ReadBytes и ReadString
ReadString возвращает строку:
line, err := reader.ReadString('\n')
ReadBytes возвращает []byte:
data, err := reader.ReadBytes('\n')
Если дальше вы работаете с текстом, удобнее ReadString. Если с байтами, файлами или протоколами, может быть удобнее ReadBytes.
fmt.Fscan
Для чтения простых значений можно использовать fmt.Fscan:
var age int fmt.Print("Введите возраст: ") if _, err := fmt.Fscan(os.Stdin, &age); err != nil { fmt.Fprintln(os.Stderr, "ожидалось число") return } fmt.Println("Возраст:", age)
Fscan удобен для задач, где ввод строго форматирован: например, в алгоритмических упражнениях. Для пользовательских CLI он часто менее дружелюбен, потому что хуже работает с произвольными строками и понятными ошибками.
Scanner или Reader
| Задача | Подход |
|---|---|
| Читать строки по одной | bufio.Scanner |
| Читать до EOF | bufio.Scanner |
| Читать очень длинные строки | bufio.Reader |
| Читать до конкретного разделителя | ReadString или ReadBytes |
| Читать числа из строго форматированного ввода | fmt.Fscan |
Начинайте со Scanner, если нет причины брать более низкоуровневый инструмент.
Полный пример
package main import ( "bufio" "fmt" "os" "strings" ) func main() { scanner := bufio.NewScanner(os.Stdin) fmt.Print("Введите имя: ") if !scanner.Scan() { if err := scanner.Err(); err != nil { fmt.Fprintln(os.Stderr, "ошибка ввода:", err) } os.Exit(1) } name := strings.TrimSpace(scanner.Text()) if name == "" { fmt.Fprintln(os.Stderr, "имя не должно быть пустым") os.Exit(1) } fmt.Printf("Привет, %s!\n", name) }
Здесь есть три важные привычки:
- ввод очищается через
TrimSpace; - ошибка чтения проверяется;
- ошибка пользователя пишется в
stderr.
Частые ошибки
- Игнорировать ошибку после
Scan. - Забывать
TrimSpaceи получать строку с пробелами. - Использовать
Fscanдля ввода имени с пробелами. - Считать, что
stdinвсегда клавиатура. - Не различать
stdoutиstderr. - Использовать
Scannerдля очень больших строк без увеличения буфера.
Чеклист
- Для обычных строк используйте
bufio.Scanner. - Для больших или специальных случаев используйте
bufio.Reader. - После чтения проверяйте ошибку.
- Пользовательский текст очищайте через
strings.TrimSpace. - Ошибки пишите в
os.Stderr. - Помните, что ввод может прийти из pipe, файла или терминала.