ページ

2012-08-10

Node.jsで標準入力をパイプで受け取るときにハマった


Node.jsというかjavascriptでJSONをいじくりまわすスクリプトを書いていて、別のコマンドからの入力をパイプで受け取る処理のところでハマったので、記録しておきます。




僕が書いていたスクリプトを例えばhuga.jsだとして、以下のように使おうとした。
cat hogehoge | node huga.js
テキストファイルhogehogeに書かれている内容を一行ずつ読み込んで、JSONに変換しようと企てました。

で、 huga .jsの中身が以下の様な感じです。

process.stdin.resume();
process.stdin.setEncoding('utf8');
// stdinがなんか読み込んだ時に呼ばれる.
process.stdin.on('data', function(chunk){
//実際のchunkは予期せぬ位置で細切れに入ってくる.
chunk.split('\n').forEach(function(){
// 何か処理
});
});
// ストリーム終了時に呼ばれる.
process.stdin.on('end', function(){
});
view raw bad_pipe.js hosted with ❤ by GitHub

上の”何か処理”ってところでJSONに良い感じに変換しようと企てたわけです。

で、これは上手く行ったわけです。
めでたしめでたし。

次に以下のようなコマンドを使おうとしたわけです。
cat hogehoge | mecab | node hoge.js
さっきのhuga.jsじゃなくてhoge.jsになってます。
あとmecabっていうのは形態素解析をするプログラムです。

つまり、hogehogeの内容を形態素解析してhoge.jsを使って良い感じにJSONに変換しようと企てたのです。

「パイプから読み込むとこなんて、さっきのhuga.jsのコードとおんなじじゃーん」と考えておんなじようにスクリプトを書きました。

で、さっきのコマンドを実行するわけです。
cat hogehoge | mecab | node hoge.js
そうするとhoge.jsが何かエラーを出したり出さなかったりするわけです。
何か文字列をいじくり回している辺りでundefinedとか色々言われたり言われなかったりします。

最悪です。
エラーが出たり、出なかったりするバグは、ソフトの実装において最悪の種類のバグです。

「もうなんやねーーん」と関西弁になってしまうぐらいです。


で、よくよくコードを見るわけですね。
process.stdin.on('data', function(chunk){
って所の変数はchunk(=ぶつ切り)になってますね。
変数名は大事だと再認識しました。
これがもしdataとか言う変数名になってたら気がつくのが遅れました。

ってことで、catだと一回でドガッっとデータが入ってくるので上のコードの関数は一回しか呼ばれません。
しかし、mecabみたいに徐々に出力するプログラムだと受け取る方にも徐々にデータが入ってくるので、上のコードの関数は何回も呼ばれます。しかもchunkは改行毎にとかじゃなくて、予想できない位置で分割されて入ってきます。


ということで、スクリプトを以下のように変更しました。

var inputText = "";
process.stdin.on('data', function(chunk){
// 入力されてきた文字列をひたすら連結
inputText += chunk;
});
// ストリーム終了時に呼ばれる.
process.stdin.on('end', function(){
inputText.split('\n').forEach(function(){
// なんか処理する
});
});
view raw ok_pipe.js hosted with ❤ by GitHub

とりあえず、入力がある間はひたすら入力されてきた文字列を連結しています。

で、入力が完了したら実際に行いたい処理を行います。

とりあえずこれで大丈夫でした。

なんかもっとエレガントな方法があったら教えて下さい。

2013/1/8 追記

コメント欄に巨大なファイルも扱えるように修正したものを頂きました。

ソースコードはこちらになります。ありがとうございます!!
process.stdin.resume();
process.stdin.setEncoding('utf8');
var fragment = "";
process.stdin.on('data', function(chunk){
if (chunk == "") { return ;}
var lines = chunk.split("\n");
lines[0] = fragment + lines[0];
fragment = lines.pop();
lines.forEach(function(line){
// なんか処理する
});
});
// ストリーム終了時に呼ばれる.
process.stdin.on('end', function(){
});
view raw ok_pipe.js hosted with ❤ by GitHub
元のコードでは確かに読み込むデータ量が膨大だった場合、メモリが足りず処理が出来なくなってしまいます。

全体を読み込んでから一気に処理をするのではなく、大きなデータを小さい粒度に分けて細かく処理をしていったほうがスケーラブルな感じがしていいと思いました。


2 件のコメント:

  1. 紹介されているok_pipe.jsだと、
    巨大なファイルを扱えないので改良してみました。
    https://gist.github.com/4481500

    返信削除
    返信
    1. ありがとうございます!

      確かに私のコードでは巨大なファイルを扱うことができませんね・・・

      修正して頂いたコードではchunkが読み込まれる毎に処理をしているので、よりnodeのイベント駆動らしい動きになりますね!

      削除