レベル: 中級 Thomas Myer, Principal, Triple Dog Dare Media
2009年 5月 19日 この記事では、スクリプトとコマンドライン・ツールを適切に統合する方法を学び、shell_exec()、exec()、passthru()、system() の使い方、コマンドラインに情報を安全に渡す方法、コマンドラインから情報を安全に取得する方法を説明します。
PHP を扱ってきた人であれば、機能の豊富な Web ページを作成する上で PHP が優れたツールであることはご存知のことでしょう。PHP は汎用のスクリプト言語として、以下の特徴を持っています。
- 容易に学ぶことができます。
- 強力なフレームワーク (CakePHP や CodeIgniter など) が数多くあり、それらを利用することで Rails プログラマーと同じくらいの生産性が可能になります。
- MySQL、PostgreSQL、Microsoft® SQL Server、さらには Oracle とも通信することができます。
- JavaScript フレームワーク (script.aculo.us や jQuery) と容易に統合することができます。
しかし、こうした特徴よりも高度なことを求める場合や、強制的に求められる場合があります。つまり、PHP が実行されているサーバーのファイルシステムを直接操作しなければならない場合がある、ということです。そうした場合には、ファイルシステム上のファイルを扱う必要があったり、どんなプロセスが実行されているかを理解する必要があったり、あるいは何か他のタスクを実行したりする必要があります。
最初は、PHP の file() などのコマンドを使ってファイルを開くことで満足していても、あるときサーバー上でシェル・コマンドを実行して何らかの出力を取得するのが唯一の方法という事態に出くわすことがあります。例えば、特定のディレクトリーにファイルがいくつあるかを知る必要があるかもしれません。あるいは、一連のログ・ファイルに何行書き込まれたかを知る必要があるかもしれません。さらには、前述のディレクトリー内のファイルを操作して、別のディレクトリーにコピーしたり、rsync を使って別の場所に転送したりする必要があるかもしれません。
Roger McCoy は「コマンド・ライン PHP? もちろん可能です!」の中で、Web ブラウザーを一切使わずにコマンドラインから PHP を直接使う方法を説明しています。本記事では、この話題を別の観点から取り上げることにします。つまりベースとなっているシェルのコマンドと PHP のコマンドラインとを密接に統合する方法、そして戻り値がある場合にはその戻り値をインターフェースやプロセスの中に取り入れる方法について説明します。
ここで説明する内容は、Linux®、BSD (Berkeley Software Distribution)、あるいはその他の UNIX® フレーバー上で実行している場合でないと動作しません。この記事では LAMP (Linux-Apache-MySQL-PHP) スタックで実行しているという前提で説明を進めます。別の UNIX フレーバーを実行している場合には、多少の調整が必要かもしれません。これは利用可能なコマンドがインストールごとに異なるためです。皆さんの多くは (BSD フレーバーを実行する) Mac OS X 上でも開発を行っていると思いますので、確実に移植できるように、サンプル・コマンドは可能な限り汎用的なものにしています。
コマンドラインの概要
PHP CLI (Command Line Interface) SAPI (Server Application Programming Interface) は PHP V4.2.0 で実験的にリリースされました。V4.3.0 の時点で、PHP CLI SAPI は完全にサポートされており、デフォルトで有効になっています。PHP CLI SAPI を利用することによって、PHP の強力さを利用したシェル・ベースのスクリプトや、さらにはデスクトップ・ベースのスクリプトを作成することができます。もちろん、コマンドラインから直接実行するツールを PHP で作成することもできます。これらを利用すると、PHP 開発者は、Perl、AWK、Ruby などのスクリプトを作成する人達、あるいはシェル・スクリプトを作成する人達と同じくらいの生産性が実現できます。
この記事では PHP に組み込まれているツールについて説明します。このツールを利用すると、PHP が実行されているシステムのベースとなっているシェル環境やファイルシステムを活用することができます。PHP には、外部コマンドを実行するための関数がいくつも用意されています (例えば shell_exec()、exec()、passthru()、system() など)。これらのコマンドは似ていますが、実行されている外部プログラムに提供するインターフェースは異なります。これらの各コマンドは、指定されたコマンドやスクリプトを実行する子プロセスを生成し、各プロセスは、コマンドの出力が標準出力 (stdout) に出力される際に、そのコマンドの出力をキャプチャーします。
shell_exec()
shell_exec() コマンドは、実際にはバックティック (`) 演算子の別名にすぎません。シェル・スクリプトや Perl スクリプトを扱ったことのある人であれば、バックティック演算子で他のコマンドを囲むとそのコマンドの出力を取り込めることはご存知かと思います。例えばリスト 1 は、バックティックを使うことで、カレント・ディレクトリーの中にあるすべてのテキスト (.txt) ファイルの単語数を取得する方法を示しています。
リスト 1. バックティックを使って単語数を取得する
#! /bin/sh
number_of_words=`wc -w *.txt`
echo $number_of_words
#result would be something like:
#165 readme.txt 388 results.txt 588 summary.txt
#and so on....
|
PHP スクリプトの中では、この単純なコマンドを shell_exec() 内部で実行することができ (リスト 2)、必要な結果を取得することができます (ただし同じディレクトリーの中にテキスト・ファイルがいくつかあるという前提です)。
リスト 2. 同じコマンドを shell_exec() 内部で実行する
<?php
$results = shell_exec('wc -w *.txt');
echo $results;
?>
|
図 1 を見るとわかるように、シェル・スクリプトから実行した場合と同じ結果を得ることができます。これは、shell_exec() を使うことで外部プログラムをシェルで実行することができ、結果がストリングとして返されるからです。
図 1. shell_exec() を使ってシェル・コマンドを実行した結果
バックティック演算子を使うだけでも同じ結果が得られることに注意してください (リスト 3)。
リスト 3. バックティック演算子のみを使用する
<?php
$results = `wc -w *.txt`;
echo $results;
?>
|
リスト 4 は、もっと簡単な方法を示しています。
リスト 4. もっと簡単な方法
<?php
echo `wc -w *.txt`;
?>
|
重要な注意点として、UNIX のコマンドラインで行えることやシェル・スクリプトで行えることは、その大部分をこの方法で行うことができます。例えば、パイプを使うとコマンドを連結することができます。さらに、すべての操作を中に含んだシェル・スクリプトを作成しておき、必要に応じて引数を付けたり、付けなかったりして、そのシェル・スクリプトを単純に呼び出すこともできます。
例えば、ディレクトリーの中にある最初の 5 つのテキスト・ファイルの単語数のみをカウントしたい場合には、パイプ (|) を使って wc コマンドと head コマンドとを連結することができます。さらに、出力結果を pre タグで囲み、Web ブラウザーで見やすくすることもできます (リスト 5)。
リスト 5. さらに複雑なシェル・コマンド
<?php
$results = shell_exec('wc -w *.txt | head -5');
echo "<pre>".$results . "</pre>";
?>
|
図 2 はリスト 5 のスクリプトを実行した結果を示しています。
図 2. shell_exec() を使って複雑なシェル・コマンドを実行した結果
この記事の後の方で、これらのスクリプトに PHP を使って引数を渡す方法を学びます。ここではとりあえず、これらのスクリプトはシェル・コマンドを実行するための方法だと思ってください。ただしこの方法は、標準出力しか見ないという前提です。スクリプトやコマンドにエラーがある場合には、stdout に標準エラー (stderr) をパイプしない限り、標準エラーを見ることはできません。
passthru()
passthru() コマンドを使うと、外部プログラムを実行することができ、その結果を画面に表示することができます。実行結果を表示するために echo や return を使う必要はなく、実行結果はブラウザーに表示されます。オプションの引数として、外部プログラムからの戻りコード (成功した場合の 0 など) を保持する変数を追加すると、デバッグに効果的です。
リスト 6 では、この前のセクションで実行した簡単な単語カウント・スクリプトを passthru() コマンドを使って実行しています。これを見るとわかるように、ここでは戻りコードを含む $returnval 変数も追加してあります。
リスト 6. passthru() コマンドを使って単語カウント・スクリプトを実行する
<?php
passthru('wc -w *.txt | head -5',$returnval);
echo "<hr/>".$returnval;
?>
|
表示のために echo を実行する必要がないことに注目してください。結果は単純に画面上に表示されます (図 3)。
図 3. passthru() コマンドを実行した結果と return コード
リスト 7 では、スクリプトの先頭部分で 5 の前にあるダッシュ (-) を削除し、ちょっとしたエラーをコードに含めています。
リスト 7. 単語カウント・スクリプトにエラーを含める
<?php
//we introduce an error below (removing - from the head command)
passthru('wc -w *.txt | head 5',$returnval);
echo "<hr/>".$returnval;
?>
|
このスクリプトが想定どおりには実行されないことに注目してください。図 4 に示すように、空白の画面と横線、そして戻り値の 1 が表示されています。この戻りコードは通常、何らかのエラーが起きたことを示しています。この戻りコードをテストできると、何を修正すればよいかを判断しやすくなります。
図 4. passthru() を使った場合のエラー・コードを確認する
exec()
exec() コマンドは shell_exec() と似ていますが、出力の最後の行を返す点、そしてオプションとしてコマンドの完全な出力とエラー・コードを配列に格納できる点が異なります。リスト 8 は、exec() を実行して結果をデータ配列に取り込まない場合の例です。
リスト 8. exec() を実行し、ただし結果をデータ配列に取り込まない
<?php
$results = exec('wc -w *.txt | head -5');
echo $results;
#would print out just the last line or results, i.e.:
#3847 myfile.txt
?>
|
結果を配列に取り込むためには、exec() の 2 番目の引数として配列の名前を追加します。リスト 9 は配列名として $data を使った例です。
リスト 9. exec() の結果をデータ配列に取り込む
<?php
$results = exec('wc -w *.txt | head -5',$data);
print_r($data);
#would print out the data array:
#Array ( [0]=> 555 text1.txt [1] => 283 text2.txt)
?>
|
結果を配列に取り込めると、各行に対して何らかのことを行うことができます。例えば、検出される最初の空白で分割し、その分割された各値をデータベース・テーブルに保存したり、あるいは各行に特定のフォーマットやタグを適用したりすることもできます。
system()
リスト 10 に示す system() コマンドには、これまで紹介したコマンドの機能が入り交じっています。例えば system() は passthru() と同様、外部プログラムから直接受信するものをすべて出力します。また exec() と同様、最後の行を返し、戻りコードを利用できるようにします。
リスト 10. system() コマンド
<?php
system('wc -w *.txt | head -5');
#would print out:
#123 file1.txt 332 file2.txt 444 file3.txt
#and so on
?>
|
いくつかの例
こうしたさまざまな PHP コマンドの使い方を学ぶと、おそらく皆さんはいろいろと質問したくなるはずです。例えば、どのコマンドをどういう場合に使えばよいのか疑問に思うかもしれませんが、それは完全に皆さん次第であり、また皆さんが何を要求するかによります。
私はほとんどの場合、exec() コマンドを使い、データ配列を利用して何らかの処理を行います。それ以外で、特に出力を気にしない場合には、より単純なコマンドとして shell_exec() を使います。単にシェル・スクリプトを実行する必要がある場合には、passthru() を使います。同じ関数を別の目的で使ったり、また場合によると同じ目的に別の関数を使ったりしていることに自分で気付くことがよくあります。すべて私の気分次第であり、私が何を実現しようとしているかによります。
また、これらのコマンドが一体何の役に立つのかわからないという人もいるかもしれません。シェル・コマンドを使った良い例としてのアイデアやプロジェクトを思いつかない場合のために、いくつかのアイデアを以下に示すことにします。
何らかのバックアップ機能、あるいはファイル転送機能を提供するアプリケーションを作成している場合には、shell_exec()、または今回紹介した他のコマンドの 1 つを使って、rsync コマンドを利用したシェル・スクリプトを実行するとスマートです。必要な rsync コマンドを含むシェル・スクリプトを作成したら、passthru() を使ってそのシェル・スクリプトを、ユーザー・コマンドまたは cron ジョブとして実行すればよいのです。
例えば、そのアプリケーションに適切な特権を持つユーザー (管理者など) が、サーバー間で 50 個の PDF を転送する必要があるとします。このユーザーは、そのアプリケーションの適切な場所までナビゲートし、Transfer をクリックし、転送が必要な PDF を選択したら、Submit をクリックします。こうしたアクションを行う場合、フォームには PHP スクリプトがあり、このスクリプトが passthru() を使って rsync スクリプトを実行し、問題が起きた場合にそのことがわかるように戻りのオプション変数を使う、といったことができます (リスト 11)。
リスト 11. passthru() を使って rsync スクリプトを実行する PHP スクリプトの例
<?php
passthru('xfer_rsync.sh',$returnvalue);
if ($returnvalue != 0){
//we have a problem!
//add error code here
}else{
//we are okay
//redirect to some other page
}
?>
|
プロセスやファイルの一覧、あるいはそうしたプロセスやファイルに関する何らかのデータの一覧をアプリケーションで表示する必要がある場合、この記事で紹介したコマンドのいずれかを使うと、容易にそうした動作を実現することができます。例えば、簡単な grep コマンドを使うことで、一定の検索基準に一致するファイルを見つけることができます。これを exec() コマンドと組み合わせ、その結果を配列にダンプすると、HTML の表やフォームを作成することができます。すると、その表やフォームを使って他のコマンドを実行することができます。
ここまでは、ユーザーが生成するイベントについて説明してきました。つまりユーザーがボタンを押したりリンクをクリックしたりすると、PHP がスクリプトを実行します。それとは別に、cron や別のスケジューラーを使ってスタンドアロンの PHP を実行すると、いくつか興味深いことを実現することができます。例えばバックアップ・スクリプトがある場合、cron を使ってそのスクリプトを単独に実行することもできますが、そのスクリプトを PHP スクリプトの中にラップして実行することもできます。なぜそんなことをする必要があるのでしょう。そんなことをすると冗長で無駄のように思えてしまいますが、そんなことはありません。例えば exec() や passthru() を使ってバックアップ・スクリプトを実行し、次に戻りコードに基づいて何らかの動作を実行させることを考えてみてください。エラーが発生した場合には、エラー・ログやデータベースにエントリーを書き込むことができ、あるいは警告の E メール・メッセージを送信することができます。そのスクリプトが成功したら、そのスクリプトの出力をそのままデータベースにダンプすることができます (例えば rsync には、後で問題診断に役立つ冗長モードがあります)。
セキュリティー
ここで、セキュリティーについて簡単に一言触れておきましょう。ユーザー入力を受け付け、その情報をシェルに渡す場合には、そのユーザー入力をサニタイズした方が確実です。危険と思われるコマンドをすべて削除し、sudo (スーパーユーザー権限での実行) や、rm (削除) などの実行を許可しないようにします。実際、ユーザーが任意のリクエストを送信できないようにし、利用可能なコマンドの一覧から選択すること以外はできないようにするのが妥当かもしれません。
例えば転送プログラムを実行する場合、引数としてファイルの一覧を受け付けるのであれば、すべてのファイルを一覧表示し、各ファイルの隣にチェックボックスを付けるようにします。すると、ユーザーはファイルを選択または選択解除して Submit をクリックし、rsync スクリプトを実行することはできますが、ファイルの一覧に入力したり、正規表現を使ったりすることはできなくなります。
まとめ
この記事では、shell_exec()、exec()、passthru()、system() などの PHP コマンドを使ってシェル・スクリプトや他のコマンドを実行するための基本を説明しました。この知識を皆さん自身のアプリケーションでどのように使うかは皆さん次第です。
参考文献 学ぶために
製品や技術を入手するために
- 皆さんの次期オープンソース開発プロジェクトを IBM ソフトウェアの試用版を使って革新してください。ダウンロード、あるいは DVD で入手することができます。
- IBM 製品の試用版をダウンロードするか、あるいは IBM SOA Sandbox をオンラインで試し、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品をお試しください。
議論するために
著者について  | 
|  | Thomas Myer は、テキサス州オースチンにあるコンサルティング会社、Triple Dog Dare Media の共同創立者です。彼は、ナレッジ・マネージメント、情報設計、そしてユーザビリティーに関する著作活動を行っています。 |
記事の評価
|