I'm looking to process a text file with node using a command line call like:
node app.js < input.txt
Each line of the file needs to be processed individually, but once processed the input line can be forgotten.
Using the on-data listener of the stdin, I get the input steam chunked by a byte size so I set this up.
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);
});But this seems so sloppy. Having to massage around the first and last items of the lines array. Is there not a more elegant way to do this?
8 Answers
You can use the readline module to read from stdin line by line:
var readline = require('readline');
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false
});
rl.on('line', function(line){ console.log(line);
}) 7 // Work on POSIX and Windows
var fs = require("fs");
var stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0
console.log(stdinBuffer.toString()); 11 readline is specifically designed to work with terminal (that is process.stdin.isTTY === true). There are a lot of modules which provide split functionality for generic streams, like split. It makes things super-easy:
process.stdin.pipe(require('split')()).on('data', processLine)
function processLine (line) { console.log(line + '!')
} 2 #!/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); read stream line by line,should be good for large files piped into stdin, my 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();Explanation:
- to cut it correctly on utf8 letter and not in middle byte set encoding to utf8 it ensures it emits each time full multibyte letter.
- When data is received the input is paused. It is used to block the input until all lines are used up. It prevents overflowing the buffet if the lines processing function is slower than input.
- If there is every time a line without newlines each time. need to accommulate it for all calls and do nothing, return . once there are more than one line also append it and use the accommulated buffer.
- after all the splitted lines were consumed. On the last line push the last line to buffer and resume paused stream.
es6 code
var n=0;
async function on_line(line)
{ ////one each line console.log(n++,"line ",line); ////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', async (chunk) => { const newlines=/[\r\n]+/; var lines=chunk.split(newlines) if(lines.length==1) { buffer.push(lines[0]); return; } readStream.pause(); // let i=0; buffer.push(lines[0]); // take first line var str=buffer.join(''); buffer.length=0;//clear array, because consumed await on_line(str); for(let i=1;i<lines.length-1;i++) await on_line(lines[i]); buffer.push(lines[lines.length-1]); lines.length=0; //optional, clear array to hint GC. return readStream.resume(); }).on('end', async ()=>{ if(buffer.length) var str=buffer.join(''); buffer.length=0; await on_line(str); }); readStream.resume();I did not test the es6 code
2In my case the program (elinks) returned lines that looked empty, but in fact had special terminal characters, color control codes and backspace, so grep options presented in other answers did not work for me. So I wrote this small script in Node.js. I called the file tight, but that's just a random name.
#!/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)
}) if you want to ask the user number of lines first:
//array to save line by line let xInputs = []; const getInput = async (resolve)=>{ const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout, }); readline.on('line',(line)=>{ readline.close(); xInputs.push(line); resolve(line); }) } const getMultiInput = (numberOfInputLines,callback)=>{ let i = 0; let p = Promise.resolve(); for (; i < numberOfInputLines; i++) { p = p.then(_ => new Promise(resolve => getInput(resolve))); } p.then(()=>{ callback(); }); } //get number of lines const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout, terminal: false }); readline.on('line',(line)=>{ getMultiInput(line,()=>{ //get here the inputs from xinputs array }); readline.close(); }) process.stdin.pipe(process.stdout); 2