Как читать из stdin построчно в Node


177

Я ищу, чтобы обработать текстовый файл с узлом, используя вызов командной строки, как:

node app.js < input.txt

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

Используя прослушиватель данных stdin, я получаю входной поток, разделенный на части в байтах, поэтому я настроил это.

process.stdin.resume();
process.stdin.setEncoding('utf8');

var lingeringLine = "";

process.stdin.on('data', function(chunk) {
    lines = chunk.split("\n");

    lines[0] = lingeringLine + lines[0];
    lingeringLine = lines.pop();

    lines.forEach(processLine);
});

process.stdin.on('end', function() {
    processLine(lingeringLine);
});

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

Ответы:


207

Вы можете использовать модуль readline для чтения из stdin построчно:

var readline = require('readline');
var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

rl.on('line', function(line){
    console.log(line);
})

3
Это хорошо работает для ввода ввода вручную в консоли, однако, когда я передаю файл в команду, файл отправляется на стандартный вывод. Жук? На этом этапе readline считается нестабильным.
Мэтт Р. Уилсон,

1
Я думаю, что вы можете просто перейти process.stdoutна другой записываемый поток - это может быть так же просто, какoutput: new require('stream').Writable()
Джефф Сиссон

3
К сожалению, мне нужен стандартный вывод. Я оставил это вне своего вопроса, но я пытаюсь сделать приложение пригодным для использования как node app.js < input.txt > output.txt.
Мэтт Р. Уилсон,

По-видимому, это «по замыслу» github.com/joyent/node/issues/4243#issuecomment-10133900 . В итоге я сделал, как вы сказали, и предоставил опцию вывода фиктивного потока для записи, а затем записал напрямую в поток stdout. Мне это не нравится, но это работает.
Мэтт Р. Уилсон

13
Похоже, если вы передадите аргумент terminal: falsecreateInterface, это решит эту проблему.
Джейсон Кроуфорд

61
// Work on POSIX and Windows
var fs = require("fs");
var stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0
console.log(stdinBuffer.toString());

3
Не могли бы вы включить некоторые детали? Уже есть высоко оцененный принятый ответ
jhhoff02

2
Это не работает для меня (узел v9.2.0, Windows). Error: EISDIR: illegal operation on a directory, fstat at tryStatSync (fs.js: 534: 13) `
AlexChaffee

2
У меня работал на узле v6.11.2, OSX.
Тиффон

3
@AlexChaffee: Похоже, что в Windows есть ошибка (все еще присутствует с v9.10.1), если нет ввода stdin или если stdin закрыт - см. Эту проблему GitHub . Помимо этого, однако, решение делает работу на Windows.
mklement0

3
работает очень хорошо и является самым коротким на сегодняшний день, может сделать его короче, выполнивfs.readFileSync(0).toString()
localhostdotdev

56

readlineспециально предназначен для работы с терминалом (то есть process.stdin.isTTY === true). Существует множество модулей, которые предоставляют функции разделения для общих потоков, например, split . Это делает вещи супер-легкими:

process.stdin.pipe(require('split')()).on('data', processLine)

function processLine (line) {
  console.log(line + '!')
}

6
нет, это не так. Если вы не хотите читать построчно, вам это совсем не нужно
vkurchatkin

6
Совет: если вы хотите запустить некоторый код после обработки всех строк, добавьте .on('end', doMoreStuff)после первой .on(). Помните, что если вы просто пишете код обычно после оператора with .on(), этот код будет выполняться до того, как будет прочитан любой ввод, потому что JavaScript не является синхронным.
Рори О'Кейн

14
#!/usr/bin/env node

const EventEmitter = require('events');

function stdinLineByLine() {
  const stdin = new EventEmitter();
  let buff = "";

  process.stdin
    .on('data', data => {
      buff += data;
      lines = buff.split(/[\r\n|\n]/);
      buff = lines.pop();
      lines.forEach(line => stdin.emit('line', line));
    })
    .on('end', () => {
      if (buff.length > 0) stdin.emit('line', buff);
    });

  return stdin;
}

const stdin = stdinLineByLine();
stdin.on('line', console.log);

0

делиться для других:

читать поток за строкой, должно быть хорошо для больших файлов, передаваемых в stdin, моя версия:

var n=0;
function on_line(line,cb)
{
    ////one each line
    console.log(n++,"line ",line);
    return cb();
    ////end of one each line
}

var fs = require('fs');
var readStream = fs.createReadStream('all_titles.txt');
//var readStream = process.stdin;
readStream.pause();
readStream.setEncoding('utf8');

var buffer=[];
readStream.on('data', (chunk) => {
    const newlines=/[\r\n]+/;
    var lines=chunk.split(newlines)
    if(lines.length==1)
    {
        buffer.push(lines[0]);
        return;
    }   

    buffer.push(lines[0]);
    var str=buffer.join('');
    buffer.length=0;
    readStream.pause();

    on_line(str,()=>{
        var i=1,l=lines.length-1;
        i--;
        function while_next()
        {
            i++;
            if(i<l)
            {
                return on_line(lines[i],while_next);
            }
            else
            {
                buffer.push(lines.pop());
                lines.length=0;
                return readStream.resume();
            }
        }
        while_next();
    });
  }).on('end', ()=>{
      if(buffer.length)
          var str=buffer.join('');
          buffer.length=0;
        on_line(str,()=>{
            ////after end
            console.error('done')
            ////end after end
        });
  });
readStream.resume();

-1

В моем случае программа (elinks) возвращала строки, которые выглядели пустыми, но на самом деле имели специальные терминальные символы, коды управления цветом и backspace, поэтому grepварианты, представленные в других ответах, не работали для меня. Поэтому я написал этот небольшой скрипт в Node.js. Я назвал файл tight, но это просто случайное имя.

#!/usr/bin/env node

function visible(a) {
    var R  =  ''
    for (var i = 0; i < a.length; i++) {
        if (a[i] == '\b') {  R -= 1; continue; }  
        if (a[i] == '\u001b') {
            while (a[i] != 'm' && i < a.length) i++
            if (a[i] == undefined) break
        }
        else R += a[i]
    }
    return  R
}

function empty(a) {
    a = visible(a)
    for (var i = 0; i < a.length; i++) {
        if (a[i] != ' ') return false
    }
    return  true
}

var readline = require('readline')
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false })

rl.on('line', function(line) {
    if (!empty(line)) console.log(line) 
})
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.