PHPからコマンドを実行する方法について

採用はこちら

PHPでは、OS上の外部コマンドやプログラムを実行できます。
代表的な方法として、exec()shell_exec()system()passthru()proc_open()などがあります。

たとえば、PHPからLinuxのlsコマンドを実行したり、Pythonスクリプトや別のPHPファイルを起動したりすることが可能です。
FFmpegやImageMagickなどの外部プログラムをPHPから実行する場合にも利用できます。

ただし、PHPからOSコマンドを実行する処理には、OSコマンドインジェクションなどのセキュリティリスクがあります。
特に、ユーザーが入力した値をそのままコマンドに組み込むことは避けなければなりません。

用途に応じて適切な関数を選び、安全性を考慮して実装することが重要です。

目次

exec()でコマンドを実行する

PHPから外部コマンドを実行する代表的な方法がexec()です。
コマンドを実行するだけでなく、出力内容や終了コードを取得できます。

exec()の基本的な使い方

基本的な書き方は次のとおりです。

<?php

exec('ls', $output, $resultCode);

print_r($output);

echo '終了コード: ' . $resultCode;

$outputには、コマンドから標準出力された内容が1行ずつ配列として格納されます。
$resultCodeには、実行したコマンドの終了コードが格納されます。

LinuxやmacOSなどでは、正常終了したプログラムが終了コード0を返すのが一般的です。
ただし、終了コードの意味は実行するプログラムによって異なるため、使用するコマンドの仕様を確認する必要があります。

exec()の出力をforeachで処理する

exec()の出力は配列として取得できるため、foreachを使って1行ずつ処理できます。

<?php

exec('ls', $output, $resultCode);

foreach ($output as $line) {
    echo htmlspecialchars($line, ENT_QUOTES, 'UTF-8') . '<br>';
}

Webページ上にコマンドの出力を表示するときは、HTMLとして解釈される文字列が含まれる可能性があります。
そのため、必要に応じてhtmlspecialchars()などでエスケープすると安全です。

exec()の終了コードを確認する

コマンドが正常に終了したかどうかを確認したい場合は、第3引数の終了コードを利用します。

<?php

exec('ls', $output, $resultCode);

if ($resultCode === 0) {
    echo '正常に実行されました';
} else {
    echo 'コマンドの実行に失敗しました';
}

コマンドの出力が空だからといって、必ずしも実行に失敗しているとは限りません。
成功・失敗を判断するときは、可能であれば出力内容ではなく終了コードを確認するのが基本です。

shell_exec()で出力全体を取得する

コマンドの出力を配列ではなく、1つの文字列として取得したい場合にはshell_exec()を利用できます。

shell_exec()の基本的な使い方

<?php

$output = shell_exec('ls');

echo $output;

shell_exec()では、実行したコマンドの標準出力全体を文字列として取得できます。

たとえば、ls -laの結果をそのまま取得する場合は次のように記述できます。

<?php

$output = shell_exec('ls -la');

echo '<pre>';
echo htmlspecialchars($output ?? '', ENT_QUOTES, 'UTF-8');
echo '</pre>';

ブラウザ上では通常の改行がそのまま反映されないため、<pre>タグで囲むとコマンドの出力を確認しやすくなります。

shell_exec()では終了コードを取得できない

shell_exec()は出力全体を簡単に取得できる一方で、exec()のように終了コードを直接取得できません。

また、戻り値だけでは、コマンドが何も出力しなかったケースとエラーが発生したケースを明確に判別しにくい場合があります。

そのため、コマンドが成功したかどうかまで確認する必要がある場合には、exec()proc_open()などを利用するほうが適しています。

system()でコマンドの出力を直接表示する

コマンドを実行し、その結果をPHPの出力として直接表示したい場合にはsystem()を利用できます。

system()の基本的な使い方

<?php

system('ls');

system()を実行すると、コマンドの出力がそのままPHPの出力として表示されます。

第2引数を指定すれば、終了コードも取得できます。

<?php

system('ls', $resultCode);

echo '終了コード: ' . $resultCode;

出力をPHP側で加工する必要がなく、そのまま表示したいケースではsystem()が便利です。

passthru()でコマンドの出力をそのまま渡す

passthru()も、外部コマンドを実行してその出力を直接送るための関数です。

passthru()の基本的な使い方

<?php

passthru('ls');

使い方はsystem()と似ています。

ただし、passthru()はコマンドが出力するデータをほぼそのまま出力するため、バイナリデータなどを扱うケースにも適しています。

単純な文字列の出力であれば、exec()system()のほうが扱いやすいこともあります。

バッククォートでコマンドを実行する

PHPでは、バッククォート演算子を使用して外部コマンドを実行することもできます。

バッククォートの基本的な使い方

<?php

$output = `ls`;

echo $output;

バッククォートで囲まれた文字列はシェルコマンドとして実行され、その出力を取得できます。

用途としてはshell_exec()に近い方法です。

ただし、バッククォートはコードを見たときに外部コマンドを実行していることが分かりにくい場合があります。
可読性を重視するなら、次のようにshell_exec()を明示的に使用するほうが分かりやすいでしょう。

<?php

$output = shell_exec('ls');

proc_open()で外部プロセスを細かく制御する

より高度な外部プロセスの制御が必要な場合にはproc_open()を利用できます。

proc_open()では、標準入力、標準出力、標準エラーを個別に扱えます。
外部プログラムとのデータの受け渡しや、エラー出力を分離して取得したい場合などに便利です。

標準出力と標準エラーを取得する

次の例では、PHP CLIのバージョン情報を取得しています。

<?php

$descriptors = [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
    2 => ['pipe', 'w'],
];

$process = proc_open(
    ['php', '-v'],
    $descriptors,
    $pipes
);

if (is_resource($process)) {
    fclose($pipes[0]);

    $stdout = stream_get_contents($pipes[1]);
    fclose($pipes[1]);

    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[2]);

    $resultCode = proc_close($process);

    echo "標準出力:\n";
    echo $stdout;

    echo "標準エラー:\n";
    echo $stderr;

    echo "終了コード: {$resultCode}";
}

0が標準入力、1が標準出力、2が標準エラーに対応しています。

単純なコマンド実行だけならexec()のほうが簡単ですが、入出力を細かく制御したい場合にはproc_open()が適しています。

proc_open()では配列形式でコマンドを指定できる

PHP 7.4以降では、proc_open()のコマンドを配列として指定できます。

<?php

$command = [
    '/usr/bin/php',
    '/var/www/scripts/sample.php',
    'hello',
];

$process = proc_open(
    $command,
    $descriptors,
    $pipes
);

配列形式では、実行するプログラムと引数を分離して指定できます。

また、配列形式の場合はシェルを経由せずにプロセスを直接起動できます。
シェルのメタ文字が解釈されないため、シェルを必要としない外部プログラムを実行する場合には有力な選択肢です。

PHPから別のPHPファイルを実行する

PHPから別のPHPスクリプトをCLIとして実行することもできます。

PHPファイルをexec()で実行する

<?php

exec('php script.php', $output, $resultCode);

print_r($output);

サーバー環境によってはphpコマンドへのPATHが通っていない場合があります。
その場合はPHP実行ファイルの絶対パスを指定します。

<?php

exec(
    '/usr/bin/php /var/www/scripts/batch.php',
    $output,
    $resultCode
);

Webサーバー経由のPHPとSSHログイン時では、PATHなどの環境変数が異なる場合があります。
コマンドがWeb環境だけ動作しない場合は、実行ファイルの絶対パスを確認すると原因を切り分けやすくなります。

PHPからPythonを実行する

PHPからPythonなど、別のプログラミング言語で作成したスクリプトを実行することも可能です。

Pythonスクリプトを実行する

<?php

exec(
    'python3 /var/www/scripts/sample.py',
    $output,
    $resultCode
);

print_r($output);

Python実行ファイルへのPATHが通っていない場合は、絶対パスを指定します。

<?php

exec(
    '/usr/bin/python3 /var/www/scripts/sample.py',
    $output,
    $resultCode
);

PHPでWebアプリケーション部分を構築し、データ処理などをPythonに任せるといった構成も可能です。

PHPからコマンドに引数を渡す方法

外部コマンドへ引数を渡す場合は、文字列としてコマンドへ追加できます。

固定された引数を渡す

たとえば、ls-laオプションを指定する場合は次のようにします。

<?php

exec('ls -la', $output, $resultCode);

固定値だけで構成されている場合は比較的単純です。

問題になるのは、ユーザーが入力した値をコマンドへ渡す場合です。

ユーザー入力をそのままコマンドへ渡してはいけない

次のようなコードは危険です。

<?php

$file = $_GET['file'] ?? '';

exec('cat ' . $file);

$fileの値にシェルの特殊文字や追加コマンドを含められると、意図していないOSコマンドが実行される可能性があります。

これはOSコマンドインジェクションと呼ばれる代表的な脆弱性です。

コマンドそのものをユーザーに指定させない

特に危険なのが、次のような実装です。

<?php

$command = $_GET['command'] ?? '';

system($command);

このような実装では、外部から任意のコマンドを指定される可能性があります。

基本的には、実行できるコマンドをPHP側であらかじめ固定してください。

<?php

$action = $_GET['action'] ?? '';

$commands = [
    'status' => '/usr/bin/example --status',
    'version' => '/usr/bin/example --version',
];

if (!isset($commands[$action])) {
    exit('許可されていない操作です');
}

exec($commands[$action], $output, $resultCode);

ユーザーにはコマンド文字列そのものではなく、PHP側で用意した操作だけを選択させる設計が安全です。

escapeshellarg()で引数をエスケープする

外部から受け取った値をどうしてもコマンドの引数として渡す必要がある場合には、escapeshellarg()を使用できます。

escapeshellarg()の使い方

<?php

$file = $_GET['file'] ?? '';

$command = 'cat ' . escapeshellarg($file);

exec($command, $output, $resultCode);

escapeshellarg()は、値を1つのシェル引数として扱える形式に変換します。

たとえば、ファイル名にスペースやシェルの特殊文字が含まれている場合でも、それらが別のコマンドとして解釈される危険を減らせます。

ただし、escapeshellarg()を使用すれば、あらゆるコマンド実行が自動的に安全になるわけではありません。

実行するコマンド自体を固定し、入力値を検証したうえで、必要な可変引数だけにescapeshellarg()を使用することが重要です。

escapeshellcmd()とescapeshellarg()の違い

PHPにはescapeshellcmd()という関数もあります。

escapeshellcmd()の役割

<?php

$command = escapeshellcmd($command);

escapeshellcmd()は、コマンド文字列全体に含まれるシェルのメタ文字をエスケープするための関数です。

一方、escapeshellarg()は1つの引数を安全な単一引数として渡すために使用します。

一般的に、外部入力をコマンドの引数として渡す場合には、次のように引数ごとにescapeshellarg()を使用する方法が分かりやすいでしょう。

<?php

$keyword = $_GET['keyword'] ?? '';

$command =
    'grep '
    . escapeshellarg($keyword)
    . ' /var/www/data/sample.txt';

exec($command, $output, $resultCode);

ユーザー入力は許可リストで制限する

エスケープだけに頼るのではなく、入力値そのものを制限する方法も重要です。

許可された値だけを受け付ける

たとえば、実行モードを指定する場合は次のようにできます。

<?php

$mode = $_GET['mode'] ?? '';

$allowedModes = [
    'start',
    'stop',
    'status',
];

if (!in_array($mode, $allowedModes, true)) {
    exit('不正な値です');
}

このような許可リスト方式では、PHP側が想定している値以外を受け付けません。

OSコマンドを利用する処理では、自由入力をできるだけ減らし、選択肢をPHP側で限定することが重要です。

標準エラーを取得する方法

外部コマンドのエラーメッセージは、標準出力ではなく標準エラーへ出力される場合があります。

2>&1で標準エラーをまとめる

LinuxやmacOSなどのシェルでは、次のように記述できます。

<?php

exec('some-command 2>&1', $output, $resultCode);

print_r($output);

2>&1は、標準エラーを標準出力へリダイレクトするシェルの記法です。

これにより、exec()$outputからエラーメッセージも確認できます。

ただし、標準出力と標準エラーを別々に扱いたい場合には、proc_open()を使用するほうが適しています。

WindowsでPHPからコマンドを実行する

PHPのコマンド実行機能はWindowsでも利用できます。

dirコマンドを実行する

Windowsでディレクトリの一覧を確認する場合は、次のように記述できます。

<?php

exec('dir', $output, $resultCode);

print_r($output);

必要に応じてcmdを明示的に指定することもできます。

<?php

exec('cmd /c dir', $output, $resultCode);

ただし、WindowsとLinux・macOSでは、シェル、パス、引用符、特殊文字の扱いが異なります。

escapeshellarg()escapeshellcmd()もOSによって処理内容が異なるため、実際に使用するサーバー環境で動作確認することが重要です。

PHPからコマンドを実行できない場合の原因

コード自体に問題がなくても、サーバー環境によってコマンドを実行できないことがあります。

PHPの実行ユーザーに権限がない

Webサーバーから実行されるPHPは、SSHでログインしているユーザーとは異なるユーザーとして動作する場合があります。

たとえば、対象ファイルを読み取る権限や外部プログラムを実行する権限がなければ、コマンドは正常に実行できません。

対象となるファイルやディレクトリ、実行ファイルの権限を確認しましょう。

PATHが設定されていない

SSHでは次のコマンドが動作しても、PHPからは実行できない場合があります。

exec('python3 script.py');

その場合は、実行ファイルの絶対パスを指定します。

exec('/usr/bin/python3 /var/www/scripts/script.py');

Linux環境では、次のコマンドなどで実行ファイルの場所を確認できます。

which python3

コマンド実行関数が無効化されている

サーバーの設定によっては、exec()system()などの関数が無効化されていることがあります。

共有レンタルサーバーなどでは、セキュリティ上の理由から利用できない場合もあります。

コマンドが実行できない場合は、PHPの設定や利用しているホスティングサービスの仕様も確認しましょう。

exec()・shell_exec()・system()・passthru()・proc_open()の違い

PHPからコマンドを実行する方法は、それぞれ用途が異なります。

主なコマンド実行関数の比較

関数主な特徴向いている用途
exec()出力を配列で取得でき、終了コードも取得できる一般的な外部コマンド実行
shell_exec()出力全体を文字列として取得する結果をまとめて取得したい場合
system()コマンドの出力を直接表示する出力をそのまま表示したい場合
passthru()コマンドの生の出力を直接送るバイナリ出力など
proc_open()標準入力・標準出力・標準エラーを細かく制御できる高度な外部プロセス制御

迷った場合には、一般的なコマンド実行ならexec()を使うと扱いやすいでしょう。

出力全体を単純に文字列として取得したいならshell_exec()、出力をそのまま表示するならsystem()が候補になります。

標準入力や標準エラーまで細かく管理したい場合にはproc_open()が適しています。

PHPからコマンドを実行するときのセキュリティ対策

PHPからOSコマンドを実行する機能は非常に強力です。
一方で、実装方法を誤るとサーバー全体に影響する重大な脆弱性につながる可能性があります。

実行するコマンドを固定する

ユーザーに任意のコマンド文字列を入力させる設計は避けましょう。

実行可能な処理をPHP側であらかじめ決めておく方法が基本です。

入力値を許可リストで検証する

選択肢が限られている場合には、自由入力ではなく許可された値だけを受け付けます。

$allowedModes = [
    'start',
    'stop',
    'status',
];

許可リスト方式を利用すると、想定外の文字列がコマンドへ渡されるリスクを減らせます。

可変引数にはescapeshellarg()を利用する

どうしても外部入力をコマンドの引数として渡す必要がある場合には、値をそのまま連結せず、escapeshellarg()の利用を検討します。

ただし、エスケープだけに依存するのではなく、入力値の形式や長さ、許可する内容についても検証することが重要です。

PHPだけで処理できないか検討する

外部コマンドを利用する前に、PHP標準関数やライブラリだけで同じ処理を実現できないか検討することも重要です。

たとえば、ファイルの一覧取得だけであれば、lsを実行しなくてもscandir()DirectoryIteratorなどを使用できます。

PHP内部で完結できれば、シェルを経由する必要がなくなり、セキュリティリスクや環境依存性を減らせます。

PHPからコマンドを実行するなら用途と安全性で方法を選ぶ

PHPから外部コマンドを実行するには、exec()shell_exec()system()passthru()proc_open()などを利用できます。

一般的な用途では、出力を配列として取得でき、終了コードも確認できるexec()が使いやすいでしょう。

出力全体を文字列で取得するだけならshell_exec()、コマンドの出力を直接表示するならsystem()、より細かなプロセス制御が必要ならproc_open()が適しています。

特にWebアプリケーションでは、ユーザーが入力した文字列をそのままコマンドへ連結してはいけません。

実行するプログラムをPHP側で固定し、入力値を許可リストで制限し、必要な可変引数だけを適切にエスケープすることが重要です。

また、シェルを利用する必要がない処理であれば、PHP標準機能で代替できないか検討すると、より安全で環境依存の少ない実装にできます。

以上、PHPからコマンドを実行する方法についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次