Avatar ·

Как прочитать ввод с клавиатуры (стандартный ввод) в консоли на Go?

Я только начал изучать Go, и у меня возникла простая, но важная проблема. Мне нужно написать программу, которая запрашивает у пользователя имя и возраст, а затем выводит приветствие. Но я не могу понять, как правильно прочитать строку или число из консоли. В других языках (например, Python) я просто использовал `input()`, а в C++ — `cin`. В Go же я вижу множество способов: `bufio`, `fmt.Scan`, `os.Stdin`, и это меня путает. Я попробовал самый очевидный вариант: ```go package main import "fmt" func main() { var name string fmt.Print("Введите имя: ") fmt.Scan(&name) fmt.Println("Привет,", name) } ``` Но когда я ввожу "Иван Иванов", программа печатает только "Привет, Иван", а второе слово теряется. Также я не понимаю, как прочитать число и обработать ошибки, если пользователь ввел буквы вместо цифр. Подскажите, пожалуйста, как правильно и надежно читать данные из стандартного ввода в Go? Мне нужны примеры для строк (с пробелами) и для чисел. <<>>

Проблема с fmt.Scan и решение через bufio.Scanner

Ваш пример с fmt.Scan действительно не работает для строк с пробелами, потому что fmt.Scan (как и fmt.Scanf) разбивает ввод по пробельным символам. Для чтения целых строк и чисел с обработкой ошибок лучше использовать пакет bufio в связке с os.Stdin. Это самый гибкий и надёжный способ.

Пошаговое решение: читаем строку, затем число

Вот полный пример программы, которая запрашивает имя (с пробелами) и возраст, а затем выводит приветствие:

package main

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

func main() {
    // Создаём сканер для чтения с клавиатуры
    scanner := bufio.NewScanner(os.Stdin)

    // Запрашиваем имя
    fmt.Print("Введите ваше имя: ")
    scanner.Scan() // читаем следующую строку
    name := scanner.Text() // получаем строку как есть

    // Проверяем ошибку сканера (например, если stdin закрыт)
    if err := scanner.Err(); err != nil {
        fmt.Println("Ошибка чтения имени:", err)
        return
    }

    // Запрашиваем возраст
    fmt.Print("Введите ваш возраст: ")
    scanner.Scan()
    ageStr := scanner.Text()

    // Убираем лишние пробелы
    ageStr = strings.TrimSpace(ageStr)

    // Преобразуем строку в число
    age, err := strconv.Atoi(ageStr)
    if err != nil {
        fmt.Println("Ошибка: возраст должен быть числом, а вы ввели:", ageStr)
        return
    }

    // Вывод результата
    fmt.Printf("Привет, %s! Тебе %d лет.\n", name, age)
}

Как это работает

  • bufio.NewScanner(os.Stdin) — создаёт сканер, который читает из стандартного ввода. Удобно тем, что читает целыми строками (до символа новой строки).
  • scanner.Scan() — считывает следующую строку. Возвращает true, если есть данные, и false при ошибке или конце файла.
  • scanner.Text() — возвращает считанную строку без символа новой строки.
  • strconv.Atoi — преобразует строку в число. Если строка не является числом, возвращает ошибку. Альтернатива: strconv.ParseInt для других разрядностей.
  • strings.TrimSpace — полезно, чтобы удалить случайные пробелы в начале/конце ввода.

Обработка нескольких чисел в одной строке

Если нужно прочитать несколько чисел, разделённых пробелами, используйте fmt.Scan или разбивайте строку через strings.Fields:

// Чтение двух чисел из одной строки
fmt.Print("Введите два числа через пробел: ")
scanner.Scan()
line := scanner.Text()
parts := strings.Fields(line)

if len(parts) != 2 {
    fmt.Println("Ошибка: нужно ввести ровно два числа")
    return
}

a, _ := strconv.Atoi(parts[0])
b, _ := strconv.Atoi(parts[1])
fmt.Println("Сумма:", a+b)

Важные моменты

  • После scanner.Scan() всегда проверяйте scanner.Err() — это поможет отловить неожиданные ошибки (например, если ввод был перенаправлен из файла и он повреждён).
  • Если вы читаете много строк (например, в цикле), создавайте один scanner и используйте его повторно — это эффективно.
  • Для простых случаев (одно слово или число) можно использовать fmt.Scan, но для реальных программ лучше bufio.Scanner.

Этот подход считается идиоматичным в Go и используется в большинстве консольных утилит.

<<>>

Альтернатива: fmt.Scanf и fmt.Scanln

Если вам нужно прочитать строго форматированный ввод (например, "Имя Возраст" в одной строке), можно использовать fmt.Scanf или fmt.Scanln. Они работают аналогично scanf из C.

package main

import "fmt"

func main() {
    var name string
    var age int

    fmt.Print("Введите имя и возраст через пробел: ")
    // Читаем строку, слово и число
    _, err := fmt.Scanf("%s %d", &name, &age)
    if err != nil {
        fmt.Println("Ошибка ввода:", err)
        return
    }

    fmt.Printf("Привет, %s! Тебе %d лет.\n", name, age)
}

Недостатки этого подхода

  • %s читает только до первого пробела, поэтому "Иван Иванов" не сработает — прочитается только "Иван".
  • Если пользователь введёт лишние символы, они останутся в буфере и могут "сломать" следующий ввод.
  • Требуется точное соответствие шаблону — при несовпадении возвращается ошибка.

Когда использовать

fmt.Scanf удобен для быстрых скриптов или когда формат ввода строго контролируется (например, в тестах). Для интерактивных программ с пользователем лучше выбрать bufio.Scanner из первого ответа — он прощает ошибки и обрабатывает строки любой длины.

Ещё один вариант — fmt.Scanln, который читает до символа новой строки, но также разбивает по пробелам. Он подходит для чтения нескольких значений с одной строки без форматирования.