最近プログラミングに自信がないので、かんたんなものを作って気を紛らしてみるテスト。
目標は、ざっくり本物のUnixコマンドをおなじような動きをするPHPを書くこと。もちろんパイプでデータをやりとりできるように。
おまじなひ
if (realpath($_SERVER['SCRIPT_FILENAME']) == realpath(__FILE__)) {
$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : [];
array_shift($argv);
exit(_echo($argv));
}
↑ ファイルの先頭にはこんな感じのコードを書いておく。
つまり、ファイルを直接実行したときだけ発火するように。
あと、$argv
変数はPHP: $argv - Manualを直接使ってもよいのだけれど、php.ini
の設定に依存するので今回は$_SERVER
からとってくることにする。
おやくそく
- コマンド実装の函数はコマンドライン引数の配列を受け取り、Unix終了ステータスを返す
実装
超かんたん実装では、これで良いはず…!
/**
* @return int UNIX status code
*/
function _echo(array $argv)
{
echo array_shift($argv);
while ($argv) {
echo ' ';
echo array_shift($argv);
}
echo PHP_EOL;
return 0;
}
% ./bin/echo a b c
a b c
% ./bin/echo a ' b ' c
a b c
% ./bin/echo a こんにちは c
a こんにちは c
いいね、動いてる。
ただ、これでは微妙なところで差がでる。
% echo 'a\tb\nc\\d'
a b
c\d
% ./bin/echo 'a\tb\nc\\d'
a\tb\nc\\d
な、なるほどなー。
そんなわけで、ちゃんとする。
static $tr_table = [
'\\\\' => '\\',
'\a' => "\a",
'\b' => "\b",
'\c' => "\c",
'\d' => "\d",
'\e' => "\e",
'\f' => "\f",
'\g' => "\g",
'\h' => "\h",
'\i' => "\i",
'\j' => "\j",
'\k' => "\k",
'\l' => "\l",
'\m' => "\m",
'\n' => "\n",
'\o' => "\o",
'\p' => "\p",
'\q' => "\q",
'\r' => "\r",
'\s' => "\s",
'\t' => "\t",
'\u' => "\u",
'\v' => "\v",
'\w' => "\w",
'\x' => "\x",
'\y' => "\y",
'\z' => "\z",
];
echo strtr($l, array_shift($argv));
あとはなんだろう、-n
オプションかな。と思ってman
を読んでみる。そうそう、Macを使ってるのでBSDコマンドになるのかな。たぶんGNU CoreutilsよりもBSD系の方がシンプルだろうから、こっちを参照し続けることにしよう。
DESCRIPTION
The echo utility writes any specified operands, separated by single blank (
) characters and followed by a newline (
\n
) character, to the standard output.The following option is available:
-n
Do not print the trailing newline character. This may also be achieved by appending
\c
to the end of the string, as is done by iBCS2 compatible systems. Note that this option as well as the effect of\c
are implementation-defined in IEEE Std 1003.1-2001 (“POSIX.1”) as amended by Cor. 1-2002. Applications aiming for maximum portability are strongly encouraged to use printf(1) to suppress the newline character.
(man 1 echo
April 12, 2003 BSDより引用、引用者がMarkdownに改変)
なるほどなるほど、BSD echo(1)のオプションは-n
しかないのか。こりゃ好都合だ。あと、\c
があれば、そこが終端になるらしい。
% echo 'a b \c d'
a b %
% ./bin/echo 'a b \c d'
a b \c d
完成
これをecho
って名前で保存してchmod +x ./echo
で実行できるはず。
#!/usr/bin/env php
<?php
namespace zonuexe\UnixCommand;
/**
* ECHO command
*
* @author USAMI KENTA <tadsan@zonu.me>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
* @copyright 2015 USAMI Kenta
*/
if (realpath($_SERVER['SCRIPT_FILENAME']) == realpath(__FILE__)) {
$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : [];
array_shift($argv);
exit(_echo($argv));
}
/**
* @return int UNIX status code
*/
function _echo(array $argv)
{
static $tr_table = [
'\\\\' => '\\',
'\a' => "\a",
'\b' => "\b",
'\c' => "\c",
'\d' => "\d",
'\e' => "\e",
'\f' => "\f",
'\g' => "\g",
'\h' => "\h",
'\i' => "\i",
'\j' => "\j",
'\k' => "\k",
'\l' => "\l",
'\m' => "\m",
'\n' => "\n",
'\o' => "\o",
'\p' => "\p",
'\q' => "\q",
'\r' => "\r",
'\s' => "\s",
'\t' => "\t",
'\u' => "\u",
'\v' => "\v",
'\w' => "\w",
'\x' => "\x",
'\y' => "\y",
'\z' => "\z",
];
$last_newline = true;
$first = array_shift($argv);
if ($first === '-n') {
$last_newline = false;
$first = array_shift($argv);
}
$printline = function ($l) use ($tr_table) {
$terminate = false;
$line = strtr($l, $tr_table);
if (strpos($line, "\c") !== false) {
$terminate = true;
list($line, $_) = explode("\c", $line, 2);
}
echo $line;
return $terminate;
};
if ($printline($first)) {
return 0;
}
while ($argv) {
echo ' ';
if ($printline(array_shift($argv))) {
return 0;
}
}
if ($last_newline) {
echo PHP_EOL;
}
return 0;
}
やったね。さすがにecho
コマンドはシンプルなだけあって100行以内に収まった。
ただ、GNU Coreutils版のecho
はもうちょっとオプション多いので、めんどくさそう…