Как прочитать весь текстовый поток в node.js?

В RingoJS есть функция под названием read который позволяет вам читать весь поток, пока не будет достигнут конец. Это полезно, когда вы создаете приложение для командной строки. Например, вы можете написать tac Запрограммируйте следующее:

#!/usr/bin/env ringo

var string = system.stdin.read(); // read the entire input stream
var lines = string.split("\n");   // split the lines

lines.reverse();                  // reverse the lines

var reversed = lines.join("\n");  // join the reversed lines
system.stdout.write(reversed);    // write the reversed lines

Это позволяет вам запустить оболочку и запустить tac команда. Затем вы вводите столько строк, сколько хотите, и после того, как вы закончите, вы можете нажать Ctrl+D (или Ctrl+Z в Windows), чтобы сообщить об окончании передачи.

Я хочу сделать то же самое в node.js, но не могу найти ни одной функции, которая бы это делала. Я думал об использовании readSync функция от fs Библиотеку имитировать следующим образом, но безрезультатно:

fs.readSync(0, buffer, 0, buffer.length, null);

Файловый дескриптор для stdin (первый аргумент) 0, Так что следует читать данные с клавиатуры. Вместо этого он дает мне следующую ошибку:

Error: ESPIPE, invalid seek
    at Object.fs.readSync (fs.js:381:19)
    at repl:1:4
    at REPLServer.self.eval (repl.js:109:21)
    at rli.on.self.bufferedCmd (repl.js:258:20)
    at REPLServer.self.eval (repl.js:116:5)
    at Interface.<anonymous> (repl.js:248:12)
    at Interface.EventEmitter.emit (events.js:96:17)
    at Interface._onLine (readline.js:200:10)
    at Interface._line (readline.js:518:8)
    at Interface._ttyWrite (readline.js:736:14)

Как бы вы синхронно собрали все данные во входном текстовом потоке и вернули их в виде строки в node.js? Пример кода был бы очень полезен.

7 ответов

Решение

Ключ должен использовать эти два потока событий:

Event: 'data'
Event: 'end'

За stream.on('data', ...) Вы должны собирать данные в буфер (если он двоичный) или в строку.

За on('end', ...) Вы должны вызвать обратный вызов с заполненным буфером, или, если вы можете встроить его и использовать return с помощью библиотеки Promises.

Так как node.js ориентирован на события и потоки, нет API для ожидания окончания работы стандартного буфера и буфера, но это легко сделать вручную

var content = '';
process.stdin.resume();
process.stdin.on('data', function(buf) { content += buf.toString(); });
process.stdin.on('end', function() {
    // your code here
    console.log(content.split('').reverse().join(''));
});

В большинстве случаев лучше не буферизовать данные и не обрабатывать входящие фрагменты по мере их поступления (используя цепочку уже доступных потоковых парсеров, таких как xml или zlib, или ваш собственный анализатор FSM)

Позвольте мне проиллюстрировать ответ StreetStrider.

Вот как это сделать с помощью concat-stream

var concat = require('concat-stream');

yourStream.pipe(concat(function(buf){
    // buf is a Node Buffer instance which contains the entire data in stream
    // if your stream sends textual data, use buf.toString() to get entire stream as string
    var streamContent = buf.toString();
    doSomething(streamContent);
}));

// error handling is still on stream
yourStream.on('error',function(err){
   console.error(err);
});

Обратите внимание, что process.stdin это поток.

Для этой конкретной задачи существует модуль, называемый concat-stream.

Если ты в asynccontext и у вас есть последняя версия Node.js, вот быстрое предложение :

      const chunks = []
for await (let chunk of readable) {
  chunks.push(chunk)
}
console.log(Buffer.concat(chunks))

Это старый вопрос, но стоит упомянуть, что в Node.js есть несколько новых помощников потоков, один из которых — toArray:

      require('http')
    .createServer(async (req, res) => {
        const str = (await req.toArray()).toString().toUpperCase();
        res.end(str);
    })
    .listen(4000);

Обратите внимание: этот API в настоящее время помечен как экспериментальный, поэтому может лучше подходить для тестирования/непроизводственного кода.

В Windows у меня были некоторые проблемы с другими решениями, размещенными здесь, - программа работала бесконечно, когда не было ввода.

Вот реализация TypeScript для современного NodeJS с использованием асинхронных генераторов и for await- немного проще и надежнее, чем использование старых API на основе обратного вызова, и это работало в Windows:

      import process from "process";

/**
 * Read everything from standard input and return a string.
 * 
 * (If there is no data available, the Promise is rejected.)
 */
export async function readInput(): Promise<string> {  
  const { stdin } = process;

  const chunks: Uint8Array[] = [];

  if (stdin.isTTY) {
    throw new Error("No input available");
  }

  for await (const chunk of stdin) {
    chunks.push(chunk);
  }

  return Buffer.concat(chunks).toString('utf8');
}

Пример:

      (async () => {
  const input = await readInput();

  console.log(input);
})();

(подумайте о добавлении try/catch, если вы хотите обработать отказ от обещания и отобразить более удобное для пользователя сообщение об ошибке, когда нет ввода.)

Другие вопросы по тегам