Cómo leer de stdin línea por línea en el nodo

177

Estoy buscando procesar un archivo de texto con nodo usando una llamada de línea de comando como:

node app.js < input.txt

Cada línea del archivo debe procesarse individualmente, pero una vez procesada, la línea de entrada puede olvidarse.

Usando el oyente en datos del stdin, obtengo el vapor de entrada fragmentado por un tamaño de byte, así que configuré esto.

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);
});

Pero esto parece tan descuidado. Tener que masajear alrededor del primer y último elemento del conjunto de líneas. ¿No hay una forma más elegante de hacer esto?

Matt R. Wilson
fuente

Respuestas:

207

Puede usar el módulo readline para leer de stdin línea por línea:

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

rl.on('line', function(line){
    console.log(line);
})
levi
fuente
3
Sin embargo, parece funcionar bien para ingresar datos manualmente en la consola, cuando paso un archivo al comando, el archivo se envía a stdout. ¿Un insecto? readline se considera inestable en este momento.
Matt R. Wilson el
1
Creo que puede cambiar process.stdouta una secuencia de escritura diferente, podría ser tan simple comooutput: new require('stream').Writable()
Jeff Sisson el
3
Desafortunadamente, necesito el stdout. Lo dejé fuera de mi pregunta, pero estoy tratando de hacer que la aplicación sea utilizable node app.js < input.txt > output.txt.
Matt R. Wilson el
Aparentemente esto es 'por diseño' github.com/joyent/node/issues/4243#issuecomment-10133900 . Así que terminé haciendo lo que dijiste y proporcioné a la opción de salida una secuencia de escritura ficticia, luego escribí directamente en la secuencia estándar. No me gusta, pero funciona.
Matt R. Wilson el
13
Parece que si pasa el argumento terminal: falsepara crear Interface, soluciona este problema.
jasoncrawford
61
// Work on POSIX and Windows
var fs = require("fs");
var stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0
console.log(stdinBuffer.toString());
Celoso
fuente
3
¿Podría incluir algunos detalles? Ya hay una respuesta aceptada altamente calificada
jhhoff02
2
Esto no funciona para mí (nodo v9.2.0, Windows). Error: EISDIR: illegal operation on a directory, fstat at tryStatSync (fs.js: 534: 13) `
AlexChaffee
2
Me funcionó en el nodo v6.11.2, OSX.
tiffon
3
@AlexChaffee: Parece que hay un error en Windows (todavía presente a partir de v9.10.1) si no hay entrada stdin o si stdin está cerrado; vea este problema de GitHub . Aparte de esto, sin embargo, la solución hace funcionar en Windows.
mklement0
3
funciona muy bien y es el más corto con diferencia, podría acortarlo haciendofs.readFileSync(0).toString()
localhostdotdev
56

readlineestá específicamente diseñado para trabajar con terminal (es decir process.stdin.isTTY === true). Hay muchos módulos que proporcionan funcionalidad dividida para transmisiones genéricas, como división . Hace las cosas súper fáciles:

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

function processLine (line) {
  console.log(line + '!')
}
vkurchatkin
fuente
66
no, no es. Si no quiere leer línea por línea, no lo necesita en absoluto
vkurchatkin
66
Consejo: si desea ejecutar algún código después de procesar todas las líneas, agregue .on('end', doMoreStuff)después del primero .on(). Recuerde que si solo escribe el código normalmente después de la declaración con .on(), ese código se ejecutará antes de que se lea cualquier entrada, porque JavaScript no es síncrono.
Rory O'Kane
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);
simonepri
fuente
0

compartir para otros:

leer flujo línea por línea, debería ser bueno para archivos grandes canalizados en stdin, mi versión:

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();
Shimon Doodkin
fuente
-1

En mi caso, el programa (enlaces) devolvió líneas que parecían vacías, pero de hecho tenían caracteres terminales especiales, códigos de control de color y retroceso, por lo que las grepopciones presentadas en otras respuestas no me funcionaron. Entonces escribí este pequeño script en Node.js. Llamé al archivo tight, pero ese es solo un nombre aleatorio.

#!/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) 
})
exebook
fuente