Введение в проблему многострочного ввода

Знакомо ощущение, когда простая консольная утилита или тестовое задание на LeetCode вдруг начинают спотыкаться на банальном чтении пары абзацев текста? В реальной разработке мы постоянно упираемся в задачу «как принимать ввод данных на нескольких строках» — от написания парсеров SQL-запросов и сложных конфигов до автоматизации CI/CD-скриптов. В этот момент стандартные методы начинают вести себя непредсказуемо, а программа предательски зависает в ожидании бесконечного ввода (совсем как легаси-код пятничным вечером).

Сложность заключается в том, что стандартные методы чтения (например, input() в Python или Console.ReadLine() в C#) по умолчанию считывают данные построчно и завершают работу по нажатию клавиши Enter. Если пользователю или внешней системе нужно передать несколько строк, программа должна знать, когда именно следует прекратить чтение: по специальному признаку (стоп-слову), по достижении конца файла (EOF), после ввода определенного количества строк или по тайм-ауту. В этой статье мы подробно рассмотрим, как эффективно решать эту задачу на самых популярных языках программирования, разберем типичные подводные камни и передовые практики.

Python: классические подходы и идиоматичный код

Когда нужно быстро подружить скрипт с живым пользователем, который путается в нажатиях клавиш, на помощь приходят проверенные временем циклы. Python предлагает сразу несколько элегантных способов сбора многострочного ввода, выбор которых зависит от конкретного сценария использования. Самый простой интерактивный способ — это использование цикла с проверкой условия завершения:

print("Введите строки (для завершения введите пустую строку или 'END'):")
lines = []
while True:
    try:
        line = input()
    except EOFError:
        break
    if not line or line.strip() == 'END':
        break
    lines.append(line)

print("Полученные данные:")
print("\n".join(lines))

Этот подход отлично подходит для интерактивных консольных утилит. Однако, если вы пишете скрипт, который принимает данные через стандартный поток ввода (stdin) путем перенаправления файла или передачи данных из другой программы, идиоматичным решением будет чтение всего потока целиком с помощью модуля sys:

import sys

Читает все оставшиеся строки из стандартного ввода до EOF

input_data = sys.stdin.read() lines = input_data.splitlines() for i, line in enumerate(lines): print(f"Строка {i+1}: {line}")

Использование sys.stdin.read() или итерирование непосредственно по объекту sys.stdin является наиболее эффективным способом обработки больших объемов данных, так как минимизирует накладные расходы на операции ввода-вывода (и избавляет от вопросов в духе «а почему оно на моей машине работало за миллисекунду, а на проде упало по памяти»).

Но что делать, если ваш бэкенд крутится на Node.js, где асинхронность диктует свои правила игры? Давайте перенесемся в мир стримов и событий.

JavaScript и Node.js: асинхронность и потоки

В экосистеме JavaScript, особенно при работе в среде Node.js, ввод-вывод традиционно построен на асинхронных событиях и потоках (Streams). Поскольку консольный ввод в Node.js является потоком чтения (Readable Stream), для корректной обработки многострочных данных используется встроенный модуль readline.

Давайте рассмотрим, как правильно настроить интерфейс для чтения данных построчно:

const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

const lines = [];

console.log("Введите данные (нажмите Ctrl+D для завершения в Unix или Ctrl+Z в Windows):");

rl.on('line', (line) => {
    lines.push(line);
});

rl.on('close', () => {
    console.log("Ввод завершен. Всего строк:", lines.length);
    console.log(lines);
});

Такой подход позволяет обрабатывать каждую строку по мере ее поступления, не дожидаясь окончания ввода всего файла, что критически важно при работе