Читаем стандартный ввод в Go: полный гайд с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как читать из стандартного ввода в Go: от новичка до профи

Вы когда-нибудь задумывались, как заставить вашу Go-программу «слушать» пользователя? Как написать утилиту, которая принимает команды из консоли, или интерактивный CLI-инструмент? Если да, то вы попали по адресу. Сегодня мы разберем чтение из стандартного ввода (stdin) в Go — настолько подробно, насколько это вообще возможно.

Эта тема — настоящий хит на Stack Overflow. Вопрос «How can I read from standard input in the console?» собрал тысячи просмотров. И не зря: стандартный ввод — это база, без которой не обходится ни один серьезный проект на Go. Мы адаптируем этот вопрос под русскоязычную аудиторию и превратим его в полноценный туториал.

В этой статье вы узнаете:

  • Что такое os.Stdin и как с ним работать
  • Как использовать bufio.Scanner для построчного чтения
  • Почему fmt.Scan — это палка о двух концах
  • Как читать целые файлы из stdin
  • Секреты обработки ошибок и производительности

Поехали!

Что такое stdin и зачем он нужен в Go?

Стандартный ввод (stdin) — это поток данных, который ваша программа получает из консоли или из другого процесса. В Go он представлен переменной os.Stdin типа *os.File. Это шлюз, через который данные попадают в вашу программу.

Зачем это нужно? Представьте:

  • Вы пишете CLI-утилиту, которая обрабатывает ввод пользователя
  • Вы создаете парсер логов, который читает данные через пайп (cat log.txt | myapp)
  • Вам нужно реализовать интерактивный ввод с подсказками

Go, будучи языком для системного программирования, предоставляет элегантные инструменты для работы со stdin. Давайте разберем их по порядку.

Метод 1: bufio.Scanner — король построчного чтения

Самый популярный и рекомендуемый способ чтения из stdin в Go — это bufio.Scanner. Он прост, эффективен и решает 90% задач.

Вот как выглядит классический пример:

package main

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

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    fmt.Println("Введите текст (Ctrl+D для завершения):")
    
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Println("Вы ввели:", line)
    }
    
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "Ошибка чтения:", err)
    }
}

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

  • bufio.NewScanner(os.Stdin) — создает сканер, который читает из стандартного ввода
  • scanner.Scan() — возвращает true, пока есть данные (по умолчанию до символа новой строки)
  • scanner.Text() — возвращает текущую строку без символа \n
  • Цикл прерывается при EOF (Ctrl+D в Linux/Mac, Ctrl+Z в Windows)

Этот код — база для любого консольного ввода Go. Он отлично работает с пайпами и интерактивным вводом.

Кастомизация Scanner

По умолчанию Scanner делит ввод по строкам. Но вы можете изменить это:

// Чтение по словам
scanner.Split(bufio.ScanWords)

// Чтение по байтам
scanner.Split(bufio.ScanBytes)

// Чтение по символам (рунам)
scanner.Split(bufio.ScanRunes)

А еще можно задать свой сплиттер — например, для CSV или кастомных разделителей. Но это уже тема для отдельной статьи.

Метод 2: fmt.Scan — быстро и грязно

Если вам нужно ввести одно значение — число, строку или несколько переменных — fmt.Scan придет на помощь. Это как scanf в C, только проще.

package main

import "fmt"

func main() {
    var name string
    var age int
    
    fmt.Print("Введите имя и возраст: ")
    n, err := fmt.Scan(&name, &age)
    
    if err != nil {
        fmt.Println("Ошибка ввода:", err)
        return
    }
    
    fmt.Printf("Прочитано %d значений: %s, %d лет\n", n, name, age)
}

Плюсы: коротко, удобно для простых скриптов.
Минусы: проблемы с пробелами в строках, нет гибкости, сложно обрабатывать ошибки.

Для чтения строки с пробелами используйте fmt.Scanln или bufio.Reader:

reader := bufio.NewReader(os.Stdin)
fmt.Print("Введите строку: ")
text, _ := reader.ReadString('\n')
fmt.Println("Вы ввели:", text)

Но помните: ReadString возвращает строку с символом \n на конце. Удалить его можно через strings.TrimSpace(text).

Метод 3: os.Stdin.Read — низкоуровневый контроль

Иногда (очень редко) вам нужен полный контроль над процессом чтения из стандартного ввода. Например, при работе с бинарными протоколами. Тогда используйте os.Stdin.Read():

package main

import (
    "fmt"
    "os"
)

func main() {
    buf := make([]byte, 1024)
    n, err := os.Stdin.Read(buf)
    if err != nil {
        fmt.Println("Ошибка:", err)
        return
    }
    fmt.Printf("Прочитано %d байт: %s", n, string(buf[:n]))
}

Этот метод блокирует выполнение, пока не получит данные. Его стоит использовать только когда bufio.Scanner не подходит.

Метод 4: Чтение всего stdin сразу

Если вы знаете, что ввод будет небольшим (например, данные из пайпа), можно прочитать все разом:

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    data, err := ioutil.ReadAll(os.Stdin)
    if err != nil {
        fmt.Println("Ошибка:", err)
        return
    }
    fmt.Println("Прочитано", len(data), "байт")
    fmt.Println(string(data))
}

Внимание: не используйте этот метод для интерактивного ввода — программа зависнет, ожидая EOF. Он хорош только для пайпов и перенаправлений.

Сравнение методов: что выбрать?

Вот краткая таблица, чтобы вы могли быстро выбрать правильный инструмент:

Блоги

Рекомендаций по книгам