Avatar ·

Как прочитать данные из стандартного ввода (stdin) в консольном приложении на Go?

Всем привет! Я только начал изучать Go и пишу простую консольную утилиту. Мне нужно, чтобы программа запрашивала у пользователя имя и возраст, а затем выводила приветствие. Но я никак не могу разобраться, как правильно прочитать строку, которую вводит пользователь в терминале. Я пробовал использовать `fmt.Scan()`, но он, кажется, читает только до первого пробела. Если я ввожу "Иван Петров", то считывается только "Иван". А если мне нужно прочитать целую строку с пробелами? Или число? Вот мой текущий код, который не работает как надо: ```go package main import "fmt" func main() { var name string var age int fmt.Print("Введите имя: ") fmt.Scan(&name) // Проблема: читает только до пробела fmt.Print("Введите возраст: ") fmt.Scan(&age) fmt.Printf("Привет, %s! Тебе %d лет.\n", name, age) } ``` Если я ввожу "Анна 25", то программа сразу завершается, не дожидаясь второго ввода, и выводит что-то странное. Как правильно читать строки, числа и целые предложения? Какие есть стандартные способы в Go для работы с консольным вводом? Заранее спасибо за помощь! <<>>

Использование bufio.Scanner для чтения строк

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

Вот исправленная версия вашей программы:

package main

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

func main() {
    reader := bufio.NewReader(os.Stdin)
    // Альтернатива: reader := bufio.NewScanner(os.Stdin)

    fmt.Print("Введите имя: ")
    name, _ := reader.ReadString('\n')
    name = strings.TrimSpace(name) // Убираем символ новой строки и лишние пробелы

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

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

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

  • bufio.NewReader(os.Stdin) создаёт буферизированный ридер для стандартного ввода.
  • ReadString('\n') читает всё до символа новой строки (включительно).
  • strings.TrimSpace() удаляет лишние пробелы и символ \n из конца строки.
  • strconv.Atoi() преобразует строку в целое число (int).

Альтернатива: bufio.Scanner

Другой популярный подход — использовать bufio.Scanner. Он удобнее, если нужно читать много строк:

package main

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

func main() {
    scanner := bufio.NewScanner(os.Stdin)

    fmt.Print("Введите имя: ")
    scanner.Scan()
    name := scanner.Text()

    fmt.Print("Введите возраст: ")
    scanner.Scan()
    ageStr := scanner.Text()
    age, _ := strconv.Atoi(ageStr)

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

Scanner.Scan() читает следующую строку (до \n), а Text() возвращает её без символа новой строки.

Чтение чисел напрямую

Если вам нужно читать именно числа (без строк), можно использовать fmt.Scanf с форматированием:

var age int
fmt.Print("Введите возраст: ")
fmt.Scanf("%d\n", &age) // \n в конце — чтобы проглотить перевод строки

Но этот способ менее надёжен при смешанном вводе (строки и числа).

Почему ваша программа завершалась раньше времени?

Когда вы вводите "Анна 25", fmt.Scan(&name) читает только "Анна", а fmt.Scan(&age) пытается прочитать "25" из того же буфера. Если после числа остаётся символ новой строки, следующий Scan может прочитать пустую строку и завершиться с ошибкой. bufio.Scanner решает эту проблему, явно разбивая ввод по строкам.

Рекомендация

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

<<>>

Чтение через fmt.Scanf и обработка ошибок

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

package main

import "fmt"

func main() {
    var name string
    var age int

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

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

Важно: %s читает только одно слово (до пробела). Если имя состоит из двух слов, используйте %q для строк в кавычках или переходите на bufio.

Использование bufio.Reader для построчного чтения

Для максимальной гибкости можно комбинировать bufio.Reader и fmt.Sscanf для разбора строк:

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

func main() {
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Введите данные: ")
    input, _ := reader.ReadString('\n')
    
    var name string
    var age int
    fmt.Sscanf(input, "%s %d", &name, &age)
    
    fmt.Printf("Имя: %s, Возраст: %d\n", name, age)
}

Этот подход даёт полный контроль над процессом: вы читаете сырую строку, а затем разбираете её как угодно (например, через strings.Split или регулярные выражения).