suin.io

コマンドラインでPHPを使うときの11のTips

suin2011年4月3日

1. ファイル単位の構文チェックはphpコマンドで行おう

phpコマンドにlオプションを指定することで、特定のファイルの構文チェックができる。エラーの詳細は、php.iniで指定したエラーログに出力されるので、そちらを確認する。

php -l /path/to/file.php

2. 成功・失敗はexit()の引数で切り分けよう

exit()の引数に整数を指定すると、端末側に処理が成功したか失敗したかを伝えることが可能。成功で終わったことを伝える場合は、exit(0);というふうにし、失敗で終わったという場合は、exit(1);か1以上の整数を与える。

<?php
echo "成功しました".PHP_EOL;
exit(0);

<?php
echo "失敗しました".PHP_EOL;
exit(1);

(ちなみに、私のプロンプトは前回のコマンドが失敗したときは、顔文字が赤くなるように細工してあるので、exit()の引数が働いているのがよくわかります。)

3. メッセージ出力用の関数をつくろう

メッセージの出力は、単純にecho 'message';とやってもいいのだが、これだと改行されないので、見づらくなってしまう。改行を有効ににするには、echo "message\n";のように最後に改行コードを付ける。ただ、毎回改行コードを文字列に埋め込むのは非効率的なので、下のようなメッセージ出力用の関数を作って、その関数で運用したほうが100倍楽だ。

function print($message)
{
    echo $message.PHP_EOL;
}

4. エラーメッセージの出力用の関数をつくろう

「メッセージ出力用の関数」と同様にエラーメッセージの出力も関数にしておくのがおすすめ。「え?エラーメッセージって普通にechoするだけでいいんじゃないの?」と思われそうだが、echoは標準出力になるので、実行端末側が普通のメッセージなのかエラーメッセージなのか区別できなくなる。実行端末と上手く連携するためには、エラーメッセージは「標準エラー出力」に吐き出すことが得策だ。標準エラー出力先はphp://stderrになる。エラーメッセージの出力用の関数の最も単純な実装が下の例だ。

function error_message($message)
{
    file_put_contents('php://stderr', $message.PHP_EOL);
}

エラーメッセージを標準エラー出力に吐き出すようにしておくと、例えばcronでエラーの時だけメールがほしいといった場合に便利。crontabを次のように、設定しておくだけでエラーに気づけるようになる。

0 4 * * * php /path/to/scheduled_job.php > /dev/null
# 標準出力は捨てて、標準エラー出力はメールに送られる。

5. exec()関数はラップして使おう

外部コマンドを実行するexec()関数ですが、ラップした関数を用意しておこう。自分がよく使うラッパーは次のような実装だ。

function execute($command, $captureStderr = false)
{
    $output = array();
    $return = 0;

    if ( $captureStderr === true )
    {
        $command .= ' 2>&1';
    }

    exec($command, $output, $return);

    $output = implode("\n", $output);

    return array('output' => $output, 'return' => $return);
}

exec()関数の第二引数はデフォルトではエラー出力を受け取らないので、エラー出力をキャプチャする必要がある場合には、コマンドに 2>&1 を埋め込む必要がある。上のラッパーでは、第二引数でエラー出力を受け取るか指定できるようにしている。

6. プロセスは小分けにしよう

Webインターフェイスだと、プロセスの寿命は長くてもせいぜい3秒程度だが、バッチ処理などを実装すると、どうしてもプロセスの寿命が長くなってしまう。プロセスの実行時間が長くなれば、メモリの使用量も大きくなりがりなので、メモリオーバに陥る危険性も必然的に高くなる。メモリ管理に自身のあるプログラマならメモリの残りを見ながらプロセスをコントローラすべきだ。が、もしメモリ管理に不安を覚えるようであれば、プロセスの寿命を短くすることに注力しよう。プロセス実行時間が短くなれば自ずとメモリオーバの危険性も低くなる。

実行時間を短くするTipsとして、最も単純なのがプロセスを小分けにする方法だ。exec()関数で、サブプロセスのPHPを実行するだけである。

exec("php subprocess.php", $output, $return);

7. 変数のスコープは狭くしよう

メモリオーバの対策として、できるだけグローバル変数を使わないという方法もある。グローバル変数はプロセスが終了しないかぎり、メモリから解放されないが、関数内の変数はスコープが切れると直ちにメモリから解放される。なので、できるだけメソッドや関数に小分けにして実装するのがおすすめだ。

<?php

function hoge()
{
    $bar = range(0, 1000);
}

$foo = range(0, 1000);
hoge();
// $barはここで開放される

// 長い処理…

// $foo は最後の最後まで開放されない

8. 実行権限に注意しよう

特にLinux系のサーバで実行するときは、実行権限に注意する必要がある。root権限で実行されるのか、apache権限で実行されるのか、それとも別のユーザ権限で実行されるのか。ファイル操作や、コマンドの実行で、権限によってエラーになったり実行できなかったりする。今、このPHPが誰によって実行されているか確認するには、passthru('whoami');を実行するといいだろう。

9. apache権限でsudoするときはコマンドごとにNOPASSWDを設定しよう

どうしてもapcahe権限でsudoする必要があるときがある。そんなときは、visudoでapacheをsudorに追加しておこう。その際、apacheに無条件でsudoできるようにするのは危険なので、コマンドを絞ろう。そして、そのコマンドに関してはパスワードなしで実行できるようにしよう。パスワードなしで実行できるようにするのは、PHPでインタラクティブにパスワードを入力するのは実装が面倒だからだ。

apache  localhost=(ALL) NOPASSWD: /path/to/command.sh, /path/to/command2.sh, /path/to/command3.sh

/path/to/command.shの中身は例えば、次のような例だ。たとえ、lsやmkdirのような単純なコマンドでも引数も含めてshファイルに固定しておき、lsやmkdirの実行権限自体は与えないようにする。そうすることで目的のコマンドを安全に実行できるようになる。

#!/bin/sh
ls /root
mkdir /root/newdir

PHP側の実装は、exec()関数を次のように叩くだけだ。

exec('sudo /path/to/command.sh');

余談だが、sudoするときの別の裏技として、apacheにパスワードを設定しておき、sudo -Sを利用する方法がある。ex.「echo "apache passowrd here" | sudo -S ls /root'」

10. プロセスはロックが必要か検討しよう

cronなどで処理の実行をスケジュール化しているとき、スケジュールの間隔が数分単位など短くなっていると、前のプロセスが完了する前に次のプロセスが走ってしまう場合がある。

process 1 -------------------------------------------------------->
process 2                    ------------------------------------------------------>
                             [この部分で二重に処理される危険性がある]

このような場合、ステータスファイルを検討してみるといいだろう。process1が走り始めた段階で、「現在プロセスが走ってます」という状態を表すファイルをどこかに作っておき、そのファイルがある限り、process2が走らないようにしておくといったものだ。下のような実装にすれば、同じプロセスが二重に走る心配がなくなる。

<?php

if ( file_exists('/path/to/process.lock') )
{
    echo "このプロセスはロックされています。".PHP_EOL;
    die(1);
}

echo "プロセスを開始します".PHP_EOL;

touch('/path/to/process.lock');

// 時間のかかる処理

unlink('/path/to/process.lock');

echo "プロセスを終了します".PHP_EOL;
die(0);

11. 今PHPのプロセスが走っているかは ps ax | grep php で調べよう

開発中はデバッグ中に、現在PHPのバッチ処理が動いているか知りたい時がある。Linuxであれば、そうしたときは次のコマンドを実行して調べてみよう。コマンドにphpが含まれているプロセスが表示され、一目瞭然だ。

ps ax | grep php

この応用として、私がよく使う確認コマンドは次のようなものだ。どういうものかというと、1秒ごとに上のコマンドを実行して、実行状況の進捗を確認するものだ。

while true; do ps ax | grep php; sleep 1; done;

まとめ

以上が、コマンドラインPHPを利用する上でのTipsでした。もっとこうしたほうがいいとか、こんな便利なTipsもあるよ、という意見・ご感想などもお待ちしてます^-^ 本気で、バッチ処理などを書くときはPHPを使わないなんて意見もありそうですが、それは自粛し...(ry

RELATED POSTS