🔥

中級者向け:PHPからシェルコマンドを実行する

2023/02/15に公開

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 が返り、正常終了となります

https://www.php.net/manual/ja/function.exit.php

結論

  1. コマンドの実行結果を取得する必要がない(実行結果を直接ブラウザに出力する)→passthru()
  2. コマンドの実行結果が全部必要だが、exitステータスは不要な場合→shell_exec()
  3. コマンドの実行結果が全部必要で、かつ、exitステータスも必要な場合→exec()
  4. コマンドを別のプロセスとして実行したい場合→popen

Windowsをご利用の方へ

Windowsでは、正しく動かない場合があります。理由は、Windowsのカーネルが、Unix互換ではないからです。Dockerを使うなど、Linux環境を実行できる環境でお試し下さい。[2]

⚠警告⚠

Webなどで、第三者のユーザーから受け取ったデータ($_GET$_POST)をコマンドに渡さないで下さい❗
もしユーザーから受け取ったデータをコマンドに使う必要がある場合は、escapeshellarg()escapeshellcmd() で必ずエスケープして下さい❗

実行演算子(バッククォート演算子)

https://www.php.net/manual/ja/language.operators.execution.php

もっとも簡単な方法です。おそらく一番最初に実装されたのだと思います。(PHP4の頃から)

<?php
$path = '/';
$result = `ls -la {$path} 2>&1`;

一番シンプルで見た目もクールです。ただし、実行演算子を使った方法では、exitステータスが取得できません。

2>&1 の意味

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 .`;

この裏技が本領を発揮するのは、curlrsyncです❗

PHPのcurl関数の実装は、非常にダルいです。PHPからcurlを使いたい場合は、PHPのcurl関数を使わずに、実行演算子で直接curlを呼び出した方が簡単です。(この場合はどうすれば良いんだろう?と悩まずに済みます)
また、rsyncに関しては、そもそもPHPに関数が実装されていません❗[4]

shell_exec()

https://www.php.net/manual/ja/function.shell-exec.php

$result = shell_exec(string $command): string|false|null

この関数は、実行演算子(バッククォート演算子)と同じです。

passthru()

https://www.php.net/manual/ja/function.passthru.php

passthru(string $command, int &$result_code = null): ?false

この関数の使い所は、名前の通り、そのままコマンドをパススルーして出力するところです。つまりPHPがデータを加工する必要がない場合です。(そもそもコマンドの出力結果を取得できない)

system()

https://www.php.net/manual/ja/function.system.php

$result = system(string $command, int &$result_code = null): string|false

C言語のsystem関数に近い実装です。

PHP をサーバーモジュールとして実行している場合、 system() のコールにより、各行を出力した後、 Web サーバーの出力バッファが自動的にクリアされます。
コマンドを実行し、何の加工もせずに全てのデータをコマンドから直接 返す必要がある場合、passthru() 関数を使用してください。

passthru()との違いは、実行結果を取得できるところですが、文字列の場合は最後の一行しか取得できません。実行結果が必要なほとんどの場面では、exec()が最適だと思われます。

最後の一行だけチェックしたいというコマンドがあるので、そのような場合には使います。(git switch masterなど)

exec()

https://www.php.net/manual/ja/function.exec.php

$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);

https://paiza.io/projects/2fX3aeVMvB50webSRyZf3A

popen()

https://www.php.net/manual/ja/function.popen.php

popen(string $command, string $mode): resource|false

command で指定したコマンドのフォークによってできたプロセスへのパイプをオープンします。

proc_open()

https://www.php.net/manual/ja/function.proc-open.php

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 &`;
脚注
  1. このページでは全て Linux という表現にしていますが、実際はLinuxに限らず、BSD系やmacOS、WindowsのWSL(Windows Subsystem for Linux)でも同じように使えます。ただし、コマンドの結果は、微妙に違う場合があります。 ↩︎ ↩︎

  2. おそらくMicrosoftは、将来的にWindowsのカーネルをLinuxカーネルに置き換えようと考えていると思います。そうなれば面白いなと、ちょっと期待していますが、Linusさんの目の黒いうちは難しいでしょう。 ↩︎

  3. 裏技といっていますが、実は実行演算子を使うのが正攻法な可能性もあります。おそらくの推測ですが、Windows環境での動作も考慮して、コマンドの代替え関数が用意されているのかもしれません。 ↩︎

  4. 具体的なrsyncを使いたい場面というのは、GitHubにPUSHして、GitHub Actionをパスしたら、WebHooksで通知して貰い、本番環境にデプロイするというような使い方です。 ↩︎

Discussion