PHPはコマンド実行関数多すぎだろ

  • backtick演算子(バッククォートで実行するやつ)
  • shell_exec()
  • exec()
  • passthru()
  • system()
  • pcntl_exec()
  • popen()
  • proc_open()

このうちbacktick演算子とshell_exec()は等価らしい。
……どれ使えばいいんだyp!


といつも思うので、実際に試してみた。

shell_exec()およびbacktick演算子

string shell_exec ( string $cmd )
cmd
実行するコマンド
戻り値
実行したコマンドの標準出力

標準エラー出力はそのまま標準エラー出力に流れる。
なお、shell_exec()とbacktick演算子(バッククォート)は等価。

exec()

string exec ( string $command, [, array &$output [, int &$return_var ]] )
command
実行するコマンド
output
実行したコマンドの出力
return_var
実行したコマンドの終了ステータス
戻り値
コマンドの標準出力のうち最後の行

標準エラー出力はそのまま標準エラー出力に流れる。
$outputは行ごとの配列となり、末尾のホワイトスペースは取り除かれる。
$outputに内容が含まれる場合は、配列の最後に追加される。
戻り値も末尾のホワイトスペースは取り除かれる。
バイナリデータを吐くプログラムの場合悲惨なことになる。

passthru()

void passthru ( string $command [, int &$return_var ] )
command
実行するコマンド
return_var
実行したコマンドの終了ステータス

実行したコマンドの標準出力および標準エラー出力を、なにもせずにそのまま流す。
とかいいつつ標準出力に関してはob_start()とかoutput_handlerの影響を受ける。

system()

string system ( string $command [, int &$return_var ] )
command
実行するコマンド
return_var
実行したコマンドの終了ステータス
戻り値
コマンドの標準出力のうち最後の行

実行したコマンドの標準出力および標準エラー出力をそのまま流す。
戻り値の末尾のホワイトスペースは取り除かれる。


出力に関しては、passthru()と何か違いがあるのか不明。
コマンドの出力をそのまま出力したい場合はpassthru()を使えと書いてあるけど、こっちも同じように出力されているように見える。
出力を変数として得られるかどうかの違いなんだろうか。

pcntl_exec()

void pcntl_exec ( string $path [, array $args [, array $env ]] )
path
実行ファイルのパス
args
プログラムに渡す引数
env
プログラムに渡す環境変数
戻り値
エラー時にFALSEを返す

実行したコマンドの標準出力および標準エラー出力をそのまま流す。


若干毛色の違う関数。
実行ファイルと引数は別々に指定しないと行けない。$pathに'command arg1 arg2'などと指定しても実行できない。そういうファイル名なら別だけど。

popen()とproc_open()

プロセスをオープンしてストリーム処理とかするやつなので割愛。

標準エラー出力

いずれも標準エラー出力についてはそのまま出力されてしまうが、リダイレクトを指定してやることで制御できる。
ただしpcntl_exec()ではどうやってリダイレクトするのかわからなかった。引数に'2>&1'とか入れたりしてもダメだったし。

実行に失敗したとき

空文字が返ってくるケースとFALSEが返ってくるケースがある。
not foundやPermission denied、変な文字列を与えたときはだいたい空文字が返ってきて、NULLとか空文字をコマンド文字列として渡すとFALSEが返ってくるようだ。
前者の場合シェルの実行結果のようなので、終了ステータスもそれっぽいのが返ってくるが、後者の場合$return_varは何も変更が加えられなかった。

結論……?

マニュアルにもあるとおり、画像加工プログラムとか外部プログラム自身に出力を吐いてもらいたい場合はpassthru()でよさそう。
で、出力結果を取り込んであれこれしたい場合は結構悩む。
テキストベースで末尾のホワイトスペースとか切り落とされてもいいならexec()できっちり$return_varも見ておきたいところだが、生の出力が欲しい場合やバイナリデータの場合はそうもいかない。配列の中身がとても残念なことになる。
この場合、実行エラーが正確に捕捉できなくてもいいならshell_exec()が手軽でよいが、エラーもしっかり捕捉したいならpassthru()をob_start()等と組み合わせて使うぐらいしか思いつかない。
いずれにせよ究極的にはproc_open()かもしれないが、やり過ぎな気もする。