- カテゴリ: PHP
- 掲載バージョン: PHP 8.4
- 名前空間 / FQCN / コマンド:
{system()} - 関連: exec / shell_exec / passthru / proc_open / escapeshellarg / escapeshellcmd
- 変更履歴: 主要仕様は近年変更なし(
safe_modeは PHP 5.4 で廃止)
要点(TL;DR)
- OSコマンドを実行し、出力を即時に標準出力へ流す。戻り値は最終行、終了コードは第2引数で受け取る。
- 最低限の使い方:
$last = system('ls -1 ' . escapeshellarg($dir), $status); - よくある罠:
- コマンドインジェクション(必ず
escapeshellarg/escapeshellcmd) - Web実行で出力がレスポンスに混ざる
- 無タイムアウト・ブロッキング/
disable_functionsで無効化され得る
- コマンドインジェクション(必ず
概要
system() は外部コマンドを同期実行し、子プロセスの標準出力をそのまま PHP の標準出力に書き出します。戻り値で出力の最終行を、引数で終了コードを取得できます。CLI ツールでの進捗表示には便利ですが、Web 実行ではレスポンス汚染・ハングの原因になりがちです。
構文 / シグネチャ
system(
string $command,
int &$result_code = null
): string|false
- 引数(表)
| 引数 | 型 | 必須 | 既定値 | 説明 |
|---|---|---|---|---|
$command | string | ✓ | — | 実行するシェルコマンド。メタ文字含む値は必ずエスケープ |
&$result_code | int(参照) | null | プロセスの終了コード(0=成功が多い) |
- 戻り値:
string|false(最終行の文字列。失敗時はfalse) - 例外/副作用:失敗時に
E_WARNING。標準出力へ即時出力。プロセス生成・環境依存。disable_functions=system,exec,...で無効化される場合あり。
使用例
最小例(依存なし・そのまま動く)
<?php
declare(strict_types=1);
// PHP 自身を使って「hello」を出力するワンライナーを実行(OS非依存)
$cmd = escapeshellarg(PHP_BINARY) . ' -r "echo \"hello\";"';
ob_start(); // system は直接出力するためバッファ開始
$lastLine = system($cmd, $status);
$output = ob_get_clean();
printf("status=%d, lastLine=%s, captured=%s\n", $status, $lastLine, $output);
if ($status !== 0) {
throw new RuntimeException("Command failed with status {$status}");
}
実務例(安全な引数組み立てと出力取得)
<?php
declare(strict_types=1);
function runListFiles(string $dir): array {
// ユーザー入力を必ずエスケープ
$cmd = 'ls -1 ' . escapeshellarg($dir);
ob_start();
$lastLine = system($cmd, $status);
$all = ob_get_clean();
if ($status !== 0) {
throw new RuntimeException("ls failed (status={$status})");
}
// OSや環境によってはエンコーディング変換が必要(例: Windows→UTF-8)
if (PHP_OS_FAMILY === 'Windows') {
$all = mb_convert_encoding($all, 'UTF-8', 'CP932');
}
return [
'lines' => array_values(array_filter(explode("\n", rtrim($all)))),
'lastLine' => $lastLine,
'status' => $status,
];
}
$result = runListFiles(__DIR__);
print_r($result);
よくある落とし穴・注意
- インジェクション:ユーザー入力をそのまま連結しない。引数は
escapeshellarg()、コマンドテンプレートは固定。 - 出力が直接混ざる:Web ルートで使うと HTML に混入。
ob_start()で捕捉するか、初めからexec()/shell_exec()を選ぶ。 - タイムアウト無し:長時間ブロック。必要なら
proc_open()+ 非同期読取や外部ライブラリ(Symfony Process)でタイムアウト制御。 - 環境依存:
PATH、利用コマンドの有無、OS差(ls/dir)に注意。 - 無効化される場合:
php.iniのdisable_functionsで拒否されることがある。 - エンコーディング:Windows は出力が CP932 のことが多い。
mb_convert_encodingで UTF-8 へ。
代替・関連APIとの比較
exec():出力を返り値や配列で取得。標準出力にそのままは書かれない。ログ収集向き。`cmd`(バッククォート):shell_exec()と同等。全出力を文字列で取得。最終行だけ不要ならこちらが簡単。passthru():バイナリ出力をそのまま送出(画像/zip 等)。戻り値はなし、終了コードは第2引数で。proc_open():入出力ストリームを細かく制御(非同期・タイムアウト・環境変数・カレントディレクトリ)。本格運用向け。Symfony Process:高機能なプロセス制御の安全なラッパー。再試行・タイムアウト・逐次出力取得が容易。
選定基準
- 画面に逐次表示したい →
system()/passthru() - すべての出力をまとめて受け取りたい →
shell_exec()/ バッククォート - 堅牢性(タイムアウト/並列/環境)重視 →
proc_open()/ Symfony Process
テスト例(Pest)
<?php
// tests/SystemFunctionTest.php
it('runs a one-liner with system and captures output', function () {
$cmd = escapeshellarg(PHP_BINARY) . ' -r "echo \"123\";"';
ob_start();
$last = system($cmd, $status);
$all = ob_get_clean();
expect($status)->toBe(0);
expect($last)->toBe('123');
expect(trim($all))->toBe('123');
});
トラブルシュート(エラー別)
| 症状/エラー | 原因 | 対処 |
|---|---|---|
system() has been disabled for security reasons | disable_functions で無効化 | CLI 専用 PHP を使う/サーバ設定変更を依頼/proc_open() 等へ切替 |
sh: 1: <cmd>: not found / 'cmd' is not recognized | コマンド未インストール / PATH 未設定 | 絶対パス指定、PATH 追加、パッケージ導入 |
| 出力がHTMLに混ざる | system()が直接出力 | ob_start()で捕捉、exec()系へ変更 |
| ハング・応答なし | 長時間処理・待ち状態 | proc_open()/Symfony Process でタイムアウト設定、ジョブキュー化 |
| 文字化け | OS毎の文字コード差 | mb_convert_encoding($out, 'UTF-8', 'CP932') 等で変換 |
| 終了コードが非0 | コマンド失敗 | 標準エラー確認(2>&1 で統合 or proc_open() で分離)、引数・権限を再確認 |

