凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

node.js の exec の同期版を作ってみた

はじめに

タイトル通り 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 上のコードで。