中級者向け:PHPからシェルコマンドを実行する
PHPから、Linuxのシェルコマンド[1]を実行する方法があります。
何種類もあるので、それぞれ、どう違うのか、どこで使えば良いのか、上級者でも悩みます。
そこで、この記事では一覧にまとめ、各コマンドに対する違いを解説しました。
早見表
コマンド | exitステータス | 実行結果の取得 | 一言で説明すると |
---|---|---|---|
実行演算子 | ✕ | ○ | shell_exec()と同じ |
shell_exec() | ✕ | ○ | 一番シンプル |
passthru() | ○ | ✕ | 画像などの出力に使う |
system() | ○ | △ | 特殊な場面でだけ使う |
exec() | ○ | ○ | 全部入り |
popen() | ? | ? | コマンドを別のプロセスとして実行する |
proc_open() | ? | ? | popen()を細かく制御できる |
exitステータスとは
exitステータスとは、プログラム(コマンド)が正常に終了したかの返り値です。
正常終了は0
で、0
以外は異常終了です。-255〜+255の整数が設定できるようです。(signedの8bit)
コマンドを実行したシェルで、exitステータスを確認するには、$?
を参照します。
ls
echo $? # 正常終了の0が出力される
コマンドラインで実行したPHPのプログラムでも、exitステータスをシェルに返す事ができます。
<?php
echo __FILE__;
exit(0);
exit(0);
の 0
が、正常終了となります。
exit;
と、引数を省略した場合は0
となります。
exit;
を使わずにプログラムが終了した場合も 0
が返り、正常終了となります
結論
- コマンドの実行結果を取得する必要がない(実行結果を直接ブラウザに出力する)→
passthru()
- コマンドの実行結果が全部必要だが、exitステータスは不要な場合→
shell_exec()
- コマンドの実行結果が全部必要で、かつ、exitステータスも必要な場合→
exec()
- コマンドを別のプロセスとして実行したい場合→
popen
Windowsをご利用の方へ
Windowsでは、正しく動かない場合があります。理由は、Windowsのカーネルが、Unix互換ではないからです。WSLやDockerを使うなど、Linux環境を実行できる環境でお試し下さい。[2]
⚠警告⚠
Webなどで、第三者のユーザーから受け取ったデータ($_GET
、$_POST
)をコマンドに渡さないで下さい❗
もしユーザーから受け取ったデータをコマンドに使う必要がある場合は、escapeshellarg() やescapeshellcmd() で必ずエスケープして下さい❗
実行演算子(バッククォート演算子)
もっとも簡単な方法です。おそらく一番最初に実装されたのだと思います。(PHP4の頃に)
<?php
$path = '/';
$result = `ls -la {$path} 2>&1`;
一番シンプルで見た目もクールです。ただし、実行演算子を使った方法では、exitステータスが取得できません。
2>&1 の意味
実行するコマンド(プログラム)によって、全ての実行結果を標準出力(1)に返すとは限りません。標準出力に返して欲しい結果を、エラー出力(2)に返すコマンドもあります。(gitなど)
そのようなプログラム(コマンド)に対して、エラー出力への結果を標準出力にリダイレクトさせる命令が、2>$1
になります。
裏技
PHPには、Linux[1:1]のコマンドをラップした関数群がたくさんあります。例えば、cd
や、pwd
などです。
Linuxのcd
コマンドは、PHPではchdir()
になり、
Linuxのpwd
コマンドは、PHPではgetcwd()
になります。
正直、LinuxのコマンドをPHPの関数で代替えするのはダルい場合が多々あります。そのような場合は、PHPの関数を使わず、実行演算子を使ってしまうという裏技があります。[3]
<?php
echo `ls -la .`;
この裏技が本領を発揮するのは、curl
やrsync
です❗
PHPのcurl関数の実装は、非常にダルいです。PHPからcurlを使いたい場合は、PHPのcurl関数を使わずに、実行演算子で直接curlを呼び出した方が簡単です。(この場合はどうすれば良いんだろう?と悩まずに済みます)
また、rsync
に関しては、そもそもPHPに関数が実装されていません❗[4]
shell_exec()
$result = shell_exec(string $command): string|false|null
この関数は、実行演算子(バッククォート演算子)と同じです。
passthru()
passthru(string $command, int &$result_code = null): ?false
この関数の使い所は、名前の通り、そのままコマンドをパススルーして出力するところです。つまりPHPがデータを加工する必要がない場合です。(そもそもコマンドの出力結果を取得できない)
system()
$result = system(string $command, int &$result_code = null): string|false
C言語のsystem関数に近い実装です。
PHP をサーバーモジュールとして実行している場合、 system() のコールにより、各行を出力した後、 Web サーバーの出力バッファが自動的にクリアされます。
コマンドを実行し、何の加工もせずに全てのデータをコマンドから直接 返す必要がある場合、passthru() 関数を使用してください。
passthru()
との違いは、実行結果を取得できるところですが、文字列の場合は最後の一行しか取得できません。実行結果が必要なほとんどの場面では、exec()
が最適だと思われます。
最後の一行だけチェックしたいというコマンドがあるので、そのような場合には使います。(git switch master
など)
exec()
$result = exec(string $command, array &$output = null, int &$result_code = null): string|false
全部入りです。これを使えばOKです❗
$command = 実行するコマンド
$output = 実行結果が\n区切りで配列として代入されます。
$result_code = exitステータスが代入されます。
$result = 実行結果の最後の一行だけが返ります(あまり意味がないです。必要な場面が分からない)
<?php
exec('ls -la', $result, $status);
if( $status ){
throw new Exception("Error!! ($status)");
}
echo join("<br/>", $result);
popen()
popen(string $command, string $mode): resource|false
command で指定したコマンドのフォークによってできたプロセスへのパイプをオープンします。
proc_open()
proc_open(
array|string $command,
array $descriptor_spec,
array &$pipes,
?string $cwd = null,
?array $env_vars = null,
?array $options = null
): resource|false
proc_open() は popen() と よく似ていますが、プログラムの実行をさらに細かく制御できる点で違います。
双方向(two-way)のサポートを求めているのなら、 proc_open() を使用してください。
バックグラウンド実行
popen()
を使えば非同期処理が可能ですが、コマンド末尾に&
を付けることで、バックグラウンド実行にすることができます。
ポイントは、標準出力とエラー出力を/dev/null
に捨てることです。当然、実行結果は取得できませんし、コマンドの終了を知ることもできません。
`(コマンド) > /dev/null 2>&1 &`;
-
このページでは全て Linux という表現にしていますが、実際はLinuxに限らず、BSD系やmacOS、WindowsのWSL(Windows Subsystem for Linux)でも同じように使えます。ただし、コマンドの結果は、微妙に違う場合があります。 ↩︎ ↩︎
-
おそらくMicrosoftは、将来的にWindowsのカーネルをLinuxカーネルに置き換えようと考えていると思います。そうなれば面白いなと、ちょっと期待していますが、Linusさんの目の黒いうちは難しいでしょう。 ↩︎
-
裏技といっていますが、実は実行演算子を使うのが正攻法な可能性もあります。おそらくの推測ですが、Windows環境での動作も考慮して、コマンドの代替え関数が用意されているのかもしれません。 ↩︎
-
具体的な
rsync
を使いたい場面というのは、GitHubにPUSHして、GitHub Actionをパスしたら、WebHooks
で通知して貰い、本番環境にデプロイするというような使い方です。 ↩︎
Discussion