Comment lire depuis stdin ligne par ligne dans Node

177

Je cherche à traiter un fichier texte avec un nœud en utilisant un appel de ligne de commande comme:

node app.js < input.txt

Chaque ligne du fichier doit être traitée individuellement, mais une fois traitée, la ligne d'entrée peut être oubliée.

En utilisant l'écouteur sur les données du stdin, j'obtiens la vapeur d'entrée fragmentée par une taille d'octet, donc je l'ai configuré.

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

Mais cela semble tellement bâclé. Devoir masser le premier et le dernier élément du tableau de lignes. N'y a-t-il pas une manière plus élégante de faire cela?

Matt R. Wilson
la source

Réponses:

207

Vous pouvez utiliser le module readline pour lire à partir de stdin ligne par ligne:

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
la source
3
Cela semble bien fonctionner pour entrer manuellement les entrées dans la console, cependant, lorsque je passe un fichier dans la commande, le fichier est envoyé à stdout. Un bug? readline est considéré comme instable à ce stade.
Matt R. Wilson
1
Je pense que vous pouvez simplement passer process.stdoutà un flux inscriptible différent - cela pourrait être aussi simple queoutput: new require('stream').Writable()
Jeff Sisson
3
Malheureusement, j'ai besoin du stdout. Je l'ai laissé hors de ma question, mais j'essaie de rendre l'application utilisable en tant que node app.js < input.txt > output.txt.
Matt R. Wilson
Apparemment, c'est «par conception» github.com/joyent/node/issues/4243#issuecomment-10133900 . J'ai donc fini par faire comme vous l'avez dit et fourni l'option de sortie un flux inscriptible factice, puis écrit directement dans le flux stdout. Je n'aime pas ça, mais ça marche.
Matt R. Wilson
13
On dirait que si vous transmettez l'argument terminal: falseà createInterface, cela résout ce problème.
jasoncrawford
61
// Work on POSIX and Windows
var fs = require("fs");
var stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0
console.log(stdinBuffer.toString());
Zélique
la source
3
Pouvez-vous inclure quelques détails? Il y a déjà une réponse acceptée très bien notée
jhhoff02
2
Cela ne fonctionne pas pour moi (nœud v9.2.0, Windows). Error: EISDIR: illegal operation on a directory, fstat at tryStatSync (fs.js: 534: 13) `
AlexChaffee
2
A travaillé pour moi sur le nœud v6.11.2, OSX.
tiffon le
3
@AlexChaffee: Il semble y avoir un bogue sous Windows (toujours présent à partir de la v9.10.1) s'il n'y a pas d'entrée stdin ou si stdin est fermé - voir ce problème GitHub . En dehors de cela, cependant, la solution ne travail sous Windows.
mklement0
3
fonctionne très bien et est de loin le plus court, pourrait le raccourcir en faisantfs.readFileSync(0).toString()
localhostdotdev
56

readlineest spécifiquement conçu pour fonctionner avec le terminal (c'est-à-dire process.stdin.isTTY === true). Il existe de nombreux modules qui fournissent des fonctionnalités de fractionnement pour les flux génériques, comme split . Cela rend les choses très faciles:

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

function processLine (line) {
  console.log(line + '!')
}
vkurchatkin
la source
6
non ce n'est pas. Si vous ne voulez pas lire ligne par ligne, vous n'en avez pas du tout besoin
vkurchatkin
6
Astuce: si vous souhaitez exécuter du code après avoir traité toutes les lignes, ajoutez .on('end', doMoreStuff)après la première .on(). N'oubliez pas que si vous écrivez simplement le code normalement après l'instruction with .on(), ce code s'exécutera avant la lecture de toute entrée, car JavaScript n'est pas synchrone.
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
la source
0

partager pour les autres:

lire le flux ligne par ligne, devrait être bon pour les gros fichiers redirigés vers stdin, ma version:

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
la source
-1

Dans mon cas, le programme (elinks) a renvoyé des lignes qui semblaient vides, mais qui avaient en fait des caractères de terminal spéciaux, des codes de contrôle de couleur et un retour arrière, de sorte que les grepoptions présentées dans d'autres réponses ne fonctionnaient pas pour moi. J'ai donc écrit ce petit script dans Node.js. J'ai appelé le fichier tight, mais ce n'est qu'un nom aléatoire.

#!/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
la source