PHPでUNIXコマンドを実装する話

めりーくりすます！　

昨日は@oubakiouさんの[ぼくのかんがえたさいきょうのはいれつ 2016 winter](http://qiita.com/oubakiou/items/ac0dbd3118add58baa98)でした。私も[いろいろ考察して](http://qiita.com/tadsan/items/2049243e47495764b191)、[実装したことがある](https://github.com/BaguettePHP/objectsystem/blob/master/poem.ja.md#typedproperty)ので参考にしてみてくださいね。

----

こんにちは、今年も[いろいろ書きたいことはあったんですけど](https://twitter.com/tadsan/status/809645454191300608)~~ちょっと眠すぎて~~時間がとれないので、一年ちょっと前にプログラミングのれんしゅうのために作ってた[BaguettePHP/UnixCommand](https://github.com/BaguettePHP/UnixCommand)の話をします。

実装過程はQiitaにも書いてました  ヾ(〃＞＜)ﾉﾞ☆

 1. [PHPでコマンドを実装してみる かんたんecho篇 - Qiita](http://qiita.com/tadsan/items/675bd3b37a8689090417)
 2. [PHPでコマンドを実装してみる cat篇 - Qiita](http://qiita.com/tadsan/items/27882550005efbeab35f)
 3. [PHPでコマンドを実装してみる かんたんseq篇 - Qiita](http://qiita.com/tadsan/items/e7dc090a0a873205d9fe)
 4. [PHPでコマンドを実装してみる cp篇 - Qiita](http://qiita.com/tadsan/items/1e272d588f2478c964df)

## UNIXコマンド？

みなさんGNU/LinuxやmacOSなどのUNIX風のOSを利用されますね。

主にUNIXシェルなどから利用されるコマンドの体系です([UNIXユーティリティの一覧](https://ja.wikipedia.org/wiki/UNIX%E3%83%A6%E3%83%BC%E3%83%86%E3%82%A3%E3%83%AA%E3%83%86%E3%82%A3%E3%81%AE%E4%B8%80%E8%A6%A7))。「UNIXコマンド」などとOSの名前が付いてますが基本的にはOSの特別なAPIではなくて、基本的には(ユーザーランドで動く)**ただのプログラム**です。 (内部ではもちろん[システムコール](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB)を読んだりしてますが)

といふことは、基本的には **同じ機能のものを** 勝手に再実装できるといふことです。実際にLinuxでのコマンドはOS本体([Linuxカーネル](https://ja.wikipedia.org/wiki/Linux%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB))とは完全に独立したもので、[GNU Core Utilities](https://ja.wikipedia.org/wiki/GNU_Core_Utilities)(coreutils)と組み合せて利用されることが多い状況です。

もちろんコマンドの実装はcoreutils以外にもいくつかあって、軽量なシステムでは[BusyBox](https://ja.wikipedia.org/wiki/BusyBox)が好まれ、BSD系UNIX(macOS含む)では[BSD](https://ja.wikipedia.org/wiki/BSD)に端を発する系統のコマンドが利用されます。

UNIX系OSの標準仕様として[POSIX](https://ja.wikipedia.org/wiki/POSIX)が存在し、コマンドも定義されますが、今回の記事では詳しく触れないし、実装仕様としても拘りません。 ~~めんどくさいですからね~~

## なぜUNIXコマンドを実装するのか

UNIXコマンドの理解を深めたかったからです。また、文字列処理が基本なのでPHPのコードを書く練習になると思ったからです。

きっかけとしては、数年前に「ふつうのHaskellプログラミング」を読んで、だった気がします。書評([404 Blog Not Found:ふつうくさく、汗臭くないHaskell本](http://blog.livedoor.jp/dankogai/archives/50515970.html))のように賛否のある本ではあるのですが、私は好きな一冊です。 ~~Haskellが好きだとは言ってない~~

## 函数実装

ひとつのUNIXコマンドには、ひとつのPHP函数を対応させることにします。ただしPHPの言語機能と同名のもの([予約語](http://php.net/manual/ja/reserved.keywords.php))は`function echo_`のように`_`を後置することにします。

### 設計

すべてのコマンドは以下のような引数と返り値にします。

```php
/**
 * @param  string[] $argv
 * @param  resource $stdin
 * @param  resource $stdout
 * @param  resource $stderr
 * @return int UNIX status code
 */
function hoge(array $argv, $stdin = STDIN, $stdout = STDOUT, $stderr = STDERR)
```

`$argv`はコマンドライン引数、`$stdin`は標準入力、`$stdout`は標準出力、`$stderr`は標準出力、返り値は[終了ステータス](https://ja.wikipedia.org/wiki/%E7%B5%82%E4%BA%86%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9)です。基本的には**`return 0`が正常終了です**。

`echo 'foo';`と`fwrite(STDOUT, 'foo');`は基本的に同じ意味ですが、この仕様に従ったときは`fwrite($stdout, ' ')`と書くことになります。なぜこんな回りくどいことをするのかは読者への宿題とします。

### コマンド起動

最初はコマンド名と同名のファイルをひとつひとつ用意したのですが、半年ほど前のコミット([`24e1b2e`](https://github.com/BaguettePHP/UnixCommand/commit/24e1b2e35d29f8eb2ea387e9f18a2460d392fc6f))で単一の`bagunix`実行ファイルを用意して、コマンドラインで`bagunix echo foo`と起動すると`echo foo`と同じ意味になるようにしました。

同時に、**コマンド名と同じシンボリックリンク**を作成するとコマンドとして機能するようにしました。

```bagunix.php
$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : [$_SERVER['SCRIPT_FILENAME']];

if (pathinfo($argv[0], PATHINFO_FILENAME) === pathinfo(__FILE__, PATHINFO_FILENAME)) {
    array_shift($argv);
}

$command = pathinfo(array_shift($argv), PATHINFO_FILENAME);
if ($command === 'echo') {
    $command = 'echo_';
}
$funcion = sprintf('\%s\%s', __NAMESPACE__, $command);

exit($funcion($argv));
```

この手法は、先述したBusyBoxと同様の手法です。

うっかり `composer global require` しても実行パスを汚染しないように[`composer.json`](https://github.com/BaguettePHP/UnixCommand/blob/master/composer.json#L15)に定義するのは`bin/bagunix`ファイルのみです。

### プログラミングスタイル

UNIXコマンドの基本はパイプラインを流れる文字列(バイト列)の処理なので、PHPといふ言語は意外と有利な環境です。また、PHP自体が基本機能を標準函数として提供してくれるので、どちらかと言ったらUNIXコマンドの挙動を再現させるためのコードが大半を占めがちになります。

また、コードを簡潔にするために[`array_shift()`](http://php.net/array_shift)あたりを多用しちゃってるのも、モダンPHPとしては良くない習慣なのかもしれませんねヾ(〃＞＜)ﾉﾞ

## Unixコマンドを知る手掛かり

`man`コマンドです… `man`コマンドを引くのです… シェルで`man echo`を実行すると、`echo`の説明を読むことができます。Webに転がってる日本語訳のmanマニュアルは古かったりするので、あんまりおすすめしにくい。 ~~差分に何度苦しめられたことか…~~

## 読者への宿題

この記事を読んで興味を持った型は、自分で`true`コマンドと`false`コマンドを実装しね。何をするコマンドなのか知らないって？ `man true`で調べてみればいいじゃない！ そのほか実装しやすそうなのは`yes`とか`head`あたりかな。`rmdir`とか`tee`もカンタンかも。

もしGitHubでプルリクエストを送ったことがないなら、[BaguettePHP/UnixCommand](https://github.com/BaguettePHP/UnixCommand)に送ってみてくださいね。