はじめに
タイトル通り node.js の exec 関数の同期版を作ってみたというお話です。
問題点
node.js の child_process から使える exec 関数は非同期に行われるので、例えば A という処理をコンソール上で行なって成否判定をしてから、 B を行いたい、といった時にはネストするしかありません。A と B の処理がそれぞれ別の関数から exec されたものだとするとお手上げ状態になってしまいます。簡単なコードを示すと以下のようになります。
var exec = require('child_process').exec; function ls() { exec("ls", function(err, stdout, stderr) { if (err) throw err; console.log(stdout); }); } function lsAll() { exec("ls -all", function(err, stdout, stderr) { if (err) throw err; console.log(stdout); }); } ls(); lsAll(); ls(); lsAll(); ls(); lsAll();
この順番で実行して欲しいのにも関わらず非同期で実行されるので ls() が3回連続で実行されたりもします。
この関数たちを順番に実行して欲しいとすると結構手間がかかります。stackoverflow とか覗くとこれに対するいろいろな解が提案されています。
ただ、純粋に同期してコマンドを実行してその結果を受取る、といったことを行おうと思ったらやっぱり面倒なので、自分で作って見ました。
使い方
var exec = require('./build/Release/shell').execSync; console.log(exec('ls -all'));
出力:
$ node execSync.js total 40 drwxr-xr-x 9 hecomi staff 306 7 8 18:00 . drwxr-xr-x 13 hecomi staff 442 7 6 22:38 .. drwxr-xr-x 13 hecomi staff 442 7 8 18:02 .git -rw-r--r-- 1 hecomi staff 51 7 8 15:09 .gitignore -rw-r--r-- 1 hecomi staff 855 7 8 16:30 README.md drwxr-xr-x 6 hecomi staff 204 7 8 15:08 build -rw-r--r-- 1 hecomi staff 90 7 8 15:13 execSync.js -rw-r--r-- 1 hecomi staff 1318 7 7 00:20 export_to_nodejs.cpp -rw-r--r-- 1 hecomi staff 478 7 7 00:27 wscript
こんな感じです。
解説
コマンドを実行、その結果を受け取るには popen を使えば簡単にできそうです。
ただ、popen して得られた FILE 構造体を C 言語っぽく扱うのはイケてないなぁ、と思って探してみると Boost.Iostreams を使うと C++ っぽく扱えることが判明。
辺りを参考にしたところちょっとハマッてしまったのですが、
のお陰で解決しました。ありがとうございます (-人-)
取り敢えず、コマンドライン上で ls した結果を出力してみるサンプルが以下の様な感じ。
#include <iostream> #include <string> #include <cstdlib> #include <boost/iostreams/stream.hpp> #include <boost/iostreams/device/file_descriptor.hpp> namespace io = boost::iostreams; typedef io::stream<io::file_descriptor> boost_stream; int main(int argc, char const* argv[]) { FILE *fp = popen("ls -all", "r"); if (fp == nullptr) return EXIT_FAILURE; boost_stream bs(fileno(fp), io::close_handle); std::string line; while (std::getline(bs, line)) { std::cout << line << std::endl; } return 0; }
stackoverflow のサンプルコードでは boost::iostreams::stream の第2引数で true / false を指定してハンドラのクローズを自動で行うか行わないかを指定していましたが、devm33 さんのブログを見ると、boost::iostreams::close_handle / boost::iostreams::never_close_handle を指定するようになったみたいです。これでコマンドライン上で ls -all をした場合と同じ結果が表示されます。 あとはこれを下に node.js のアドオン化お作法に従って書いてあげれば完成となります。詳細は github 上のコードで。