キーワードが悪かったのか、なかなか見つけられなかったので。
以下の流れです。
- タスクを作る
- シェルへコマンドを投げる
- 非同期処理が終わるまで待つ
- Node.js
- GruntJS
- エラーとか拾う
とりあえず、ls
してファイル一覧を得る処理を作ってみる事にします。
環境
$ grunt --version grunt v0.4.0rc2
タスクを作る
Gruntfile.js
module.exports = function(grunt) { // … grunt.registerTask('ls', function() { console.log('ls'); }); };
これでgrunt ls
が使えるようになりました。何の意味もないタスクですが。
シェルへコマンドを投げる
grunt.registerTask('ls', function() { var exec = require('child_process').exec; var command = 'ls > list.txt'; exec(command); });
grunt ls
とするとコマンドls > list.txt
が実行され、list.txt
が作られます。
カレントディレクトリ
grunt
のコマンドはGruntfile.js
が置いてあるディレクトリ以下のどこでも実行できます。その際のカレントディレクトリは常にGruntfile.js
が置いてあるディレクトリになります。
なので、先の例ではGruntfile.js
と同じ位置にlist.txt
が出力されます。下位ディレクトリで実行しても、です。
画面に出力してくれない
ファイルに出力するのは良いんだけど、画面に出力しようとするとうまく行かない。
リダイレクト>list.txt
を削って、画面に結果を表示するようにしてみました。
grunt.registerTask('ls', function() { var exec = require('child_process').exec; var command = 'ls'; exec(command); });
これ駄目。
何故かというと、処理が非同期に実行されるためです。exec()
の処理にどれくらい時間がかかるかわからないので、本来の処理をブロックしないようになっているようです。
Node.jsの面目躍如ですな。
非同期処理終了時のコールバックを設定する
こんな感じ。(まだこれだけじゃ駄目です。)
// not works yet... grunt.registerTask('ls', function() { var exec = require('child_process').exec; var command = 'ls'; var options = { timeout:1000 }; var callback = function(error, stdout, stderr) { console.log(stdout); }; exec(command, options, callback); });
options.timeout
で最長待ち時間を指定できます。単位はミリ病。なおoptions
は省略可能です。
非同期処理が終了するとcallback
が実行されます。
コールバック地獄へようこそ。
callback
の引数
error
, stdout
, stderr
の三つです。
成功時error
はnull
になります。失敗時はErrorオブジェクトで、error.code
で終了コードを、error.signal
でシグナルを得る事が出来ます。
stdout
, stderr
はそれぞれ標準出力、標準エラーへの出力文字列が格納されてるみたいです。
コールバック実行まで待つ
Node.js的にはこれでよし。
でもこれだとまだ問題があって、コールバックは登録されるだけで実行されないです。先にGruntJSのタスク実行自体が終了してしまいます。
というわけで、待ちます。async()
という仕組みを使います。
// works well! grunt.registerTask('ls', function() { var exec = require('child_process').exec; var done = this.async(); var command = 'ls'; var options = { timeout:1000 }; var callback = function(error, stdout, stderr) { console.log(stdout); done(); }; exec(command, options, callback); });
this.async()
で終了通知用のコールバック関数を得て、それが呼ばれるまでGruntJSが終了しないようにしました。
処理を終えたら終了通知用のコールバック関数done()
を実行しましょう。
エラーとか拾う
これで完璧、ですか? (まだよくわかってない……。)
grunt.registerTask('ls', function() { var exec = require('child_process').exec; var done = this.async(); var command = 'ls'; var options = { timeout:1000 }; var callback = function(error, stdout, stderr) { if (error) { console.log('ERR', error, stderr); done(false); } else { console.log(stdout); done(); } }; exec(command, options, callback); });
exec()
がタイムアウトした場合は終了シグナルSIGTERM
が飛んできます。(シグナルはUNIX系の仕組みです。)
あとdone()
にfalse
を与えると、GruntJSの実行の表示が通常の”Done, without errors.”(緑色)ではなく”Aborted due to warnings.”(赤色)になります。
まとめ
以下の手順で実現できます。
require('child_process').exec()
でシェルへコマンドを投げるexec(command, callback)
でコールバックを与えるdone =this.async()
を得て、コールバックでdone()
する
処理が長いときは
find /
的な、処理時間が長くてタイムアウトしちゃうようなものもあります。そういう場合はexec
ではなくてspawn
を使うと良さそうです。
それもまたいずれ。
参考
- Child Process Node.js v0.8.15 Manual & Documentation
- child_process.exec(command, [options], callback) … Node.jsのドキュメント邦訳
- grunt-contrib-watch/test/fixtures/tasks/echo.js at master · gruntjs/grunt-contrib-watch · GitHub … GruntJSにおける非同期処理の例
- Frequently Asked Questions · gruntjs/grunt Wiki … GruntJSのFAQ
- Why doesn’t my asynchronous task complete? … 非同期処理のやり方