Kinx ライブラリ - Process

# Kinx ライブラリ - Process

## はじめに

**「見た目は JavaScript、頭脳(中身)は Ruby、（安定感は AC/DC）」** でお届けしているスクリプト言語 [Kinx](https://github.com/Kray-G/kinx)。言語はライブラリが命。ということでライブラリの使い方編。 

今回は Process です。子プロセス起動とかやっぱり必要ですよね、ということで急遽こしらえました。

* 参考
    * 最初の動機 ... [スクリプト言語 KINX（ご紹介）](https://qiita.com/Kray-G/items/ca08b6fb40d15dd0ec76)
        * 個別記事へのリンクは全てここに集約してあります。
    * リポジトリ ... [https://github.com/Kray-G/kinx](https://github.com/Kray-G/kinx)
        * Pull Request 等お待ちしております。 

## System.exec()

昔から標準で用意していたコマンド実行インタフェース。単純に C レベルでの `system()` を呼ぶだけなので手軽だが、終了するまで帰ってこないとか、標準出力を取得できないとか、色々不便ではある。ただし、シェル経由でコマンド実行するので、リダイレクトとかは使える。

で、今回はもっと色々とできる `Process` クラスを作ったので、そちらが今回の説明での本命。

## Process

### using Process

Process ライブラリは標準組み込みではないため、using ディレクティブを使用して明示的に読み込む。

```javascript
using Process;
```

### Exec

Process オブジェクトは `new Process(command, opts)` で作成する。引数はコマンド名と引数の配列、またはコマンド文字列。配列の場合は引数を別々に渡す感じで、コマンドライン文字列の場合は内部で解析して配列形式に自動で分解。

* 配列： `["ls", "-1"]` のような感じ。
* 文字列： `"ls -1"` のような感じ。

作成されたプロセス・オブジェクトは以下のメソッドを持つ。

|  メソッド   |                                          概要                                           |
| ----------- | --------------------------------------------------------------------------------------- |
| `run()`     | プロセスを開始させる。                                                                  |
| `launch()`  | プロセスを開始させて切り離す。                                                          |
| `std()`     | 引数で渡されたオプションが返る。<br/>`{ in: opts.in, out: opts.out, err: opts.err }`    |

この時点ではまだ実行していない。`run()` または `launch()` を行った時点で起動する。`run()` すると `ProcessController` クラスのオブジェクトが返る。

#### Run

`run()` することで `ProcessController` クラスのオブジェクトが返る。

```javascript
var p = new Process(["cmd","arg1"]).run();
```

#### Launch

`launch()` は何も返さない（というか null が返る）。突き放して以後、子供の面倒は見ない、という方法。

```javascript
new Process(["cmd","arg1"]).launch();
```

#### ProcessController

`run()` で返された `ProcessController` クラスは以下のメソッドを持つ。

|  メソッド   |                                          概要                                           |
| ----------- | --------------------------------------------------------------------------------------- |
| `isAlive()` | プロセスが生きている場合は true、既に終了している場合、または `detach` された後は false |
| `wait()`    | プロセスの終了を待ち、終了後にプロセスの終了コードを返す。`detach` 後は 0 を返す。      |
| `detach()`  | プロセスを detach する                                                                  |

`detach()` はプロセス起動後に切り離す。Linux では `launch()` で切り離した場合と微妙に動作が違うがやりたいことは同じ。Windows で内部動作も同じ。

Linux ではプロセス起動時に切り離すためにいわゆる **double-fork** というやり方で切り離すが、これはプロセス起動時にしか使えない。プロセス起動後に切り離すのは実質的に不可能で、親プロセスではきちんと `wait` なり `waitpid` なりしてやらないと子はゾンビとなって生き残ってしまう。

そこで、`detach()` した瞬間に `waitpid` するためだけのスレッドを起動して、子が死ぬまで面倒見るようにしてある。

ちなみに double-fork とは Linux の、

* 親プロセスが死ぬと子プロセスは init 配下になった上に init はひたすら wait しまくって面倒見てくれる

...という機能を利用して、一度 fork したプロセスからさらに fork した上で、最初に fork したプロセスを速攻終了させて孫プロセスの管理を init に任せる、というやり方です。

一番上の親プロセスは、最初に fork した子供の `waitpid` を忘れずに。勝手に面倒見てもらうのは孫のほうなので。

### Wait

終了を待って終了コードを取得する例は以下の通り。

```javascript
var p = new Process(["cmd", "arg1"]).run();
var status = p.wait();
```

`detach` していた場合は当然取得できない（0 が返る）。

### Detach

先ほどから出てきている `detach`。プロセスは `detach`（切り離し）することもできる。切り離してしまえば子との縁は切れる。`wait` する必要もないし、終了を気にする必要もない。というか、気にしたくてもできなくなる。

```javascript
var p = new Process(["cmd", "arg1"]).run();
p.detach();
```

### Pipe

お待ちかねパイプ。`Process` を作った一番の目的はパイプ。子プロセスとの標準入出力をパイプに自由自在につないで情報のやり取りをしたい、というのが一番欲しい機能ですよね。

パイプの指定は、`new Process(cmd, opts)` の `opts` で指定。パラメータは以下の 3 種類。

| パラメータ |                                                  内容                                                  |
| ---------- | ------------------------------------------------------------------------------------------------------ |
| `in`       | 標準入力を指定。<br />指定可能なものは、パイプ・オブジェクト、文字列、`$stdin`                         |
| `out`      | 標準出力を指定。<br />指定可能なものは、パイプ・オブジェクト、文字列、`$stdout` または `$stderr`       |
| `err`      | 標準エラー出力を指定。<br />指定可能なものは、パイプ・オブジェクト、文字列、`$stdout` または `$stderr` |

* パイプ・オブジェクト ... パイプを使うためのオブジェクト。詳細は後述。
* 文字列 ... ファイル名として、入力元、出力先ファイル。
* `$stdin`、`$stdout`、`$stderr` ... 入力元、出力先を本プロセスの標準入出力にバインドする。

#### パイプ・オブジェクト

パイプ・オブジェクトは `new Pipe()` で作成する。`[Read, Write]` の 2 つのオブジェクトをペアで配列で返す。パイプオブジェクトには、以下のメソッドがある。

通常は `Write` パイプを子プロセスの `out` または `err` に指定して、`Read` パイプから読み込む。

##### Read Pipe

パイプのクローズは `run()` してからすること。`run()` するときに設定されるため。

| メソッド  |                                                                              |
| --------- | ---------------------------------------------------------------------------- |
| `peek()`  | パイプにデータがなければ 0、あれば 0 より大きい数値を返す。-1 はエラー。     |
| `read()`  | パイプのデータを全て文字列として取得する。データがない場合は空文字列を返す。 |
| `close()` | パイプを閉じる。                                                             |

##### Write Pipe

パイプのクローズは `run()` してからすること。`run()` するときに設定されるため。

|   メソッド    |                                                                                |
| ------------- | ------------------------------------------------------------------------------ |
| `write(data)` | パイプにデータを書き込む。全て書き込めるとは限らず、書き込んだバイト数を返す。 |
| `close()`     | パイプを閉じる。                                                               |

##### サンプル

一般的な形として以下のように使う。

```javascript
using Process;

var [r1, w1] = new Pipe();
var p1 = new Process([ "ls", "-1" ], { out: w1 }).run();
w1.close(); // もう使わないのでクローズしてよい
while (p1.isAlive() || r1.peek() > 0) {
    var buf = r1.read();
    if (buf.length() < 0) {
        System.println("Error...");
        return -1;
    } else if (buf.length() > 0) {
        System.print(buf);
    } else {
        // System.println("no input...");
    }
}
System.println("");
```

Write Pipe を親プロセス側で使う場合は、こんな感じ。

```javascript
using Process;

// stdin はパイプから読み込み、標準出力に出力
[r1, w1] = new Pipe();
var p1 = new Process("cat", { in: r1, out: $stdout }).run();
r1.close(); // もう使わないのでクローズしてよい

// p1 の stdin に送り込む
var nwrite = w1.write("Message\n");
w1.close(); // パイプクロ―ズ、送信終了

p1.wait();
```

ちなみに、こうすると標準出力と標準エラー出力を制御できる。

```javascript
new Process("cmd", { out: $stdout, err: $stdout }); // 標準エラー出力を標準出力に合流
new Process("cmd", { out: $stderr, err: $stderr }); // 標準出力を標準エラー出力に合流
new Process("cmd", { out: $stderr, err: $stdout }); // 入れ替え
```

### Pipeline

パイプをつないでいくのは結構面倒（というか、どっちがどっちだっけ...? みたいな）な作業なので、一括して行ってくれる `Process.pipeline` というのも定義してみた。最後にコールバック関数を置いて、以下のように使う。

```javascript
var r = Process.pipeline(cmd1, cmd2, cmd3, ..., &(i, o, pipeline) => {
    // i ... 最初のコマンドの stdin への書き込みパイプ
    // o ... 最後のコマンドの stdout からの読み込みパイプ
    // pipeline ... パイプライン・オブジェクト
    //    pipeline.input ....... 上記 i と同じ
    //    pipeline.output ...... 上記 o と同じ
    //    pipeline.peek() ...... pipeline.output.peek() と同じ
    //    pipeline.read() ...... pipeline.output.read() と同じ
    //    pipeline.write() ..... pipeline.input.write() と同じ
    //    pipeline.isAlive() ... パイプラインのいずれかのプロセスが生きていたら true
    //    pipeline.wait() ...... パイプラインの全てのプロセスが完了するのを待ち、
    //                           終了コードを配列で返す

    // コールバックの復帰値がそのまま Process.pipeline() の復帰値になる。
    return pipeline.wait();
});
```

コールバックしなくても使える。

```javascript
var pipeline = Process.pipeline(cmd1, cmd2, cmd3, ...);
// pipeline ... パイプライン・オブジェクト
//  以下省略。
```

## おわりに

子プロセス関係は Windows と Linux で違うので、そういうのを統一的に扱えるのはスクリプトの良いところ。ただし、コマンド自体は違ったりするので、そこはなかなか吸収できませんね。私は Windows ユーザーですが、[UnxUtils](https://en.wikipedia.org/wiki/UnxUtils) を使って Unix コマンドをある程度コマンドプロンプトでも使えるようにしています。（Cygwin は環境を変えてしまうのであまり好きではない...）

ということで、ではまた次回。