phpコマンドにlオプションを指定することで、特定のファイルの構文チェックができる。エラーの詳細は、php.iniで指定したエラーログに出力されるので、そちらを確認する。
php -l /path/to/file.php
exit()の引数に整数を指定すると、端末側に処理が成功したか失敗したかを伝えることが可能。成功で終わったことを伝える場合は、exit(0);というふうにし、失敗で終わったという場合は、exit(1);か1以上の整数を与える。
<?php echo "成功しました".PHP_EOL; exit(0);
<?php echo "失敗しました".PHP_EOL; exit(1);
(ちなみに、私のプロンプトは前回のコマンドが失敗したときは、顔文字が赤くなるように細工してあるので、exit()の引数が働いているのがよくわかります。)
メッセージの出力は、単純にecho 'message';とやってもいいのだが、これだと改行されないので、見づらくなってしまう。改行を有効ににするには、echo "message\n";のように最後に改行コードを付ける。ただ、毎回改行コードを文字列に埋め込むのは非効率的なので、下のようなメッセージ出力用の関数を作って、その関数で運用したほうが100倍楽だ。
function print($message) { echo $message.PHP_EOL; }
「メッセージ出力用の関数」と同様にエラーメッセージの出力も関数にしておくのがおすすめ。「え?エラーメッセージって普通に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 # 標準出力は捨てて、標準エラー出力はメールに送られる。
外部コマンドを実行する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 を埋め込む必要がある。上のラッパーでは、第二引数でエラー出力を受け取るか指定できるようにしている。
Webインターフェイスだと、プロセスの寿命は長くてもせいぜい3秒程度だが、バッチ処理などを実装すると、どうしてもプロセスの寿命が長くなってしまう。プロセスの実行時間が長くなれば、メモリの使用量も大きくなりがりなので、メモリオーバに陥る危険性も必然的に高くなる。メモリ管理に自身のあるプログラマならメモリの残りを見ながらプロセスをコントローラすべきだ。が、もしメモリ管理に不安を覚えるようであれば、プロセスの寿命を短くすることに注力しよう。プロセス実行時間が短くなれば自ずとメモリオーバの危険性も低くなる。
実行時間を短くするTipsとして、最も単純なのがプロセスを小分けにする方法だ。exec()関数で、サブプロセスのPHPを実行するだけである。
exec("php subprocess.php", $output, $return);
メモリオーバの対策として、できるだけグローバル変数を使わないという方法もある。グローバル変数はプロセスが終了しないかぎり、メモリから解放されないが、関数内の変数はスコープが切れると直ちにメモリから解放される。なので、できるだけメソッドや関数に小分けにして実装するのがおすすめだ。
<?php function hoge() { $bar = range(0, 1000); } $foo = range(0, 1000); hoge(); // $barはここで開放される // 長い処理… // $foo は最後の最後まで開放されない
特にLinux系のサーバで実行するときは、実行権限に注意する必要がある。root権限で実行されるのか、apache権限で実行されるのか、それとも別のユーザ権限で実行されるのか。ファイル操作や、コマンドの実行で、権限によってエラーになったり実行できなかったりする。今、このPHPが誰によって実行されているか確認するには、passthru('whoami');を実行するといいだろう。
どうしても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'」
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);
開発中はデバッグ中に、現在PHPのバッチ処理が動いているか知りたい時がある。Linuxであれば、そうしたときは次のコマンドを実行して調べてみよう。コマンドにphpが含まれているプロセスが表示され、一目瞭然だ。
ps ax | grep php
この応用として、私がよく使う確認コマンドは次のようなものだ。どういうものかというと、1秒ごとに上のコマンドを実行して、実行状況の進捗を確認するものだ。
while true; do ps ax | grep php; sleep 1; done;
以上が、コマンドラインPHPを利用する上でのTipsでした。もっとこうしたほうがいいとか、こんな便利なTipsもあるよ、という意見・ご感想などもお待ちしてます^-^ 本気で、バッチ処理などを書くときはPHPを使わないなんて意見もありそうですが、それは自粛し...(ry
氷川 XOOPS Module 開発室