LoginSignup
5
6

More than 5 years have passed since last update.

PHPでコマンドを実装してみる かんたんecho篇

Last updated at Posted at 2015-08-13

最近プログラミングに自信がないので、かんたんなものを作って気を紛らしてみるテスト。

目標は、ざっくり本物の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終了ステータスを返す

実装

超かんたん実装では、これで良いはず…!

echo
/**
 * @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で実行できるはず。

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はもうちょっとオプション多いので、めんどくさそう…

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6