PHPはわりと便利で、簡単なスクリプトを書くのにも不自由しない。「PHPが動作する」ことを前提にしたシステムでは、PerlやRubyやPythonを導入したり学習させたりせずに、PHPでスクリプトを書いてしまへる。
#!/usr/bin/env php <?php echo $argv[1];
シェルでは実行ファイルにオプションを渡せる。オプションは空白區切りになる。PHP CLIではオプションは $argv
といふarray變數で受け取れる。$argv[0] にはPHPファイル名が入る。これは php some_file.php arg1 arg2
として呼び出した場合と同じ動作である。
またシェルでは実行プロセスに標準入力と標準出力と標準エラー出力がある。PHP CLIではそれぞれ STDIN, STDOUT, STDERR といふ定數で讀み書きできる。$line = fgets(STDIN); fwrite(STDOUT, $line);
のやうにだ。
シェルではオプションと標準入力を同時に受け取ることがある。どちらも受け取ればよい。$argv と STDIN を両方使ふのになにも制限はない。
シェルではオプションと標準入力の、どちらかを受け取ることがある。実は少し困る。
オプションが無い場合は標準入力から受け取ることにする。オプションが無いことは isset($argv[1]) ですぐに判定できる。標準入力を受け取らうとする。stream_get_contents(STDIN) 標準入力が與へられてゐなかった場合、ここでPHPの実行は停止してしまう。EOFが入力されるか、fgets() を使っても改行が入力されるまでPHPは待つ。ユーザーにキーボード操作が必要になってしまふ。
標準入力が無い場合はオプションから受け取ることにする。先と同じである。STDINを読みにいくとPHPはそこで待ってしまふ。ユーザーがキーボードで操作してから、ようやくオプションを見に行く。
よくない。
SELECT
Unixではストリームは基本的な要素だから、うまく扱ふシステムコールがある。SELECTシステムコールは幾つかのストリームを指定すると、順番にタイムアウトするまで見に行き、讀み書き可能なストリームを見つけるとそれを使へるやうにしてくれる。
PHPにもこれと似た函數がある。stream_select() を使へる。
#!/usr/bin/env php <?php $read = [STDIN]; $write = []; $except = []; if (isset($argv[1])) { echo file_get_contents($argv[1]); } elseif (stream_select($read, $write, $except, 0)) { echo stream_get_contents(STDIN); } else { throw new \Exception(); }
SELECTのタイムアウトには0を指定できる。SELECTする時点で即座に讀み書き可能なストリームだけを探せる。stream_select() の引數は參照でなければならないから、予め變數にいれておかねばならない。ここではSTDINだけを指定する。
オッケー。
Gulpからこれを使ってみる
Gulpはストリームベースのソフトウェアだ。魔が差して上記のPHPスクリプトをストリームのなかで使ってみる。
/* jshint node:true */ 'use strict'; var cp = require('child_process'); var gulp = require('gulp'), through = require('through2'); function execPipe(cmd, options) { options = options || []; return through.obj(function (file, encoding, callback) { var me = this, proc = cp.spawn(cmd, options), stdouts = []; proc.on('close', function (code) { if (0 !== code) { me.emit('error', new Error(cmd + ' ' + options.join(' ') + ' ends with ' + code)); return callback(); } file.contents = Buffer.concat(stdouts); me.push(file); callback(); }).on('error', function (err) { me.emit('error', err); }); proc.stdout.on('data', function (chunk) { stdouts.push(chunk); }); proc.stderr.on('data', function (chunk) { process.stderr.write(chunk); }); proc.stdin.write(file.contents.toString(encoding)); proc.stdin.end(); }); } gulp.task('default', function () { return gulp.src('src/'). pipe(execPipe('some_php_cli')). pipe(gulp.dest('dest/')); });
Gulpはnode.jsの通常のストリームではなく、vinyl-fsベースのストリームなので、おとなしくthrough2を使ふのがよい。child_process.spawn() で作ったプロセスに、起動完了を待たず即座に標準入力に書き込んでゐる。結果は標準出力からでてくるから、vinyl-fsのファイルオブジェクトを書き換へる。
練習にしかならない。おとなしくgulp-execやgulp-shellやgulp-runでも使っておくのがよい。