在 PHP 開發中,有時需要在伺服器端執行系統命令以完成特定任務,如檔案處理、服務管理或呼叫外部程式。PHP 提供了多種執行系統命令的函數,但它們的行為特性、回傳值處理方式與適用場景各有不同。本文將深入剖析 system()、passthru()、shell_exec()、exec() 以及 popen() 的差異,並提供一個健壯的通用執行封裝函數,幫助開發者根據環境可用性自動選擇最佳方式。
| 函數 | 輸出處理 | 回傳值 | 適用場景 |
|---|---|---|---|
system() |
直接輸出至瀏覽器/標準輸出 | 最後一行字串(成功)或 false(失敗) |
需要即時顯示命令輸出的簡單腳本 |
passthru() |
直接輸出原始二進位資料 | 無回傳值(需搭配輸出緩衝捕獲) | 執行產生二進位輸出(如圖片、PDF)的命令 |
shell_exec() |
不直接輸出,需捕獲回傳值 | 完整輸出字串(含換行),失敗回傳 null |
需取得完整命令輸出字串進行後續處理 |
exec() |
不直接輸出,透過參考參數接收每行輸出 | 最後一行字串,陣列參數接收所有輸出行 | 需逐行處理輸出、或取得結束狀態碼 |
popen() |
建立管道資源流,需手動讀取 | 檔案指標資源,失敗回傳 false |
大量資料串流處理、雙向通訊需求 |
以下函數會依序檢測可用函數並自動選擇,確保在不同伺服器環境(如禁用特定函數)下仍能正常運作:
function executeCommand($cmd) {
$output = '';
if (empty($cmd)) {
return $output;
}
// 1. system() + 輸出緩衝
if (function_exists('system')) {
ob_start();
system($cmd);
$output = ob_get_clean();
return $output;
}
// 2. passthru() + 輸出緩衝
if (function_exists('passthru')) {
ob_start();
passthru($cmd);
$output = ob_get_clean();
return $output;
}
// 3. shell_exec() 直接取得字串
if (function_exists('shell_exec')) {
$output = @shell_exec($cmd);
return $output ?: '';
}
// 4. exec() 取得陣列後組合
if (function_exists('exec')) {
$lines = [];
@exec($cmd, $lines);
return implode("\n", $lines);
}
// 5. popen() 串流讀取(最後備援)
$handle = @popen($cmd, 'r');
if (is_resource($handle)) {
while (!feof($handle)) {
$output .= fread($handle, 1024);
}
pclose($handle);
}
return $output;
}
escapeshellarg()、escapeshellcmd())。proc_open() 並設定超時,或使用非同步隊列處理。exec() 的第三個參數或 pcntl_wexitstatus()),記錄失敗日誌以利除錯。對於常見操作(檔案處理、壓縮、圖片轉換等),優先考慮 PHP 內建擴展或函式庫(如 ZipArchive、Imagick、FFmpeg PHP 封裝),避免呼叫外部程式帶來的安全風險與效能開銷。若確實需要執行系統命令,建議封裝為服務類,統一管理日誌、超時與權限控管。