首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 執行系統命令完全指南:exec、system、passthru 異同、背景執行、超時處理與安全防範

     編輯:PHP 相關知識

PHP 執行系統命令完全指南:exec、system、passthru 異同、背景執行、超時處理與安全防範

PHP 通過 exec()、system()、passthru()、shell_exec()、proc_open() 等函數可調用操作系統 Shell 命令,實現啟動外部程式、執行批處理、視頻轉碼、圖片處理、系統維護等功能。但這也是安全風險最高的操作之一。本文系統梳理各函數差異、最佳實踐、常見坑點與防範措施。

核心函數對比表

函數返回值輸出行為適用場景獲取完整輸出獲取退出碼
exec()最後一行輸出 (string)不直接輸出需程式化處理輸出、獲取退出碼第 2 參數 &$output (array)第 3 參數 &$return_var
system()最後一行輸出 (string)直接輸出到標準輸出簡單命令、調試、需即時顯示進度不可直接獲取(需輸出緩衝捕獲)第 2 參數 &$return_var
passthru()void直接輸出二進制數據圖片/PDF/視頻流、二進制輸出不可第 2 參數 &$return_var
shell_exec() / ` `完整輸出 (string)不直接輸出需完整文本輸出、簡單腳本返回值即完整輸出不可直接獲取
proc_open()resource (process handle)完全可控(管道讀寫)雙向通訊、長時間運行、非阻塞、多進程協作通過管道流讀取proc_get_status()['exitcode']

實戰代碼範例

1. exec():獲取完整輸出數組與退出碼

<?php
$cmd = 'ps -ef | grep php-fpm';
$output = [];
$returnVar = 0;
$lastLine = exec($cmd, $output, $returnVar);

echo "退出碼: $returnVar\n";        // 0=成功,非 0=失敗
echo "最後一行: $lastLine\n";
echo "完整輸出:\n";
print_r($output); // Array ( [0]=>'root 123 ...', [1]=>'www-data 456 ...' )

2. system():即時顯示輸出(如安裝進度)

<?php
// 模擬長時間任務,即時輸出進度
system('for i in {1..5}; do echo "Step $i"; sleep 1; done', $ret);
echo "返回碼: $ret";

注意: 若在 Web 環境需即時輸出,必須關閉輸出緩衝:@ob_end_flush(); ob_implicit_flush(true);。

3. 背景執行(非阻塞):解決 PHP 等待導致超時

核心原則: 將命令輸出重定向到文件/設備,並放入後台運行(& 或 start /B)。

Linux/Unix 環境
<?php
$cmd = '/usr/bin/php /var/www/scripts/long_task.php > /var/log/long_task.log 2>&1 &';
exec($cmd); // 立即返回,不等待
// 或使用 nohup 免疫掛起信號
exec('nohup /usr/bin/php /var/www/scripts/long_task.php >> /var/log/long_task.log 2>&1 &');
Windows 環境
<?php
// 方法 1:start /B (無新窗口)
exec('start /B "" "C:\php\php.exe" "C:\scripts\long_task.php" > "C:\logs\long_task.log" 2>&1');

// 方法 2:COM 組件 (真正獨立進程)
$WshShell = new COM("WScript.Shell");
$WshShell->Run('cmd /c "C:\php\php.exe C:\scripts\long_task.php"', 0, false);
// 參數 0=隱藏窗口, false=不等待返回

4. proc_open():雙向通訊、精細控制(高級用法)

<?php
$descriptorspec = array(
    0 => array("pipe", "r"),  // stdin
    1 => array("pipe", "w"),  // stdout
    2 => array("pipe", "w")   // stderr
);
$cmd = 'php -r "fwrite(STDOUT, \"hello\\n\"); fwrite(STDERR, \"error\\n\"); exit(42);"';
$proc = proc_open($cmd, $descriptorspec, $pipes);

if (is_resource($proc)) {
    // 寫入 stdin
    fwrite($pipes[0], "input data\n");
    fclose($pipes[0]);

    // 讀取 stdout/stderr
    $stdout = stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[1]);
    fclose($pipes[2]);

    $status = proc_get_status($proc);
    $exitCode = $status['exitcode'];
    proc_close($proc);

    echo "Exit: $exitCode\nOut: $stdout\nErr: $stderr";
}

安全防範:必須遵守的鐵律

  1. 禁用危險函數: php.ini 設置 disable_functions = exec,system,passthru,shell_exec,proc_open,popen,pcntl_exec。若業務必須使用,僅在特定 CLI 腳本中通過 ini_set() 臨時開啟(需 disable_functions 為空)。
  2. 絕對禁止用戶輸入直接拼接命令:
    // ❌ 極度危險
    exec("ping " . $_GET['ip']);
    
    // ✅ 正確:escapeshellarg() 轉義單個參數
    $ip = escapeshellarg($_GET['ip']);
    exec("ping -c 4 $ip", $out, $ret);
    
    // ✅ 更好:白名單驗證
    if (filter_var($_GET['ip'], FILTER_VALIDATE_IP)) {
        exec("ping -c 4 " . escapeshellarg($_GET['ip']), $out, $ret);
    }
    
  3. 使用 escapeshellcmd() 轉義整個命令字串(較少用,參數級用 escapeshellarg())。
  4. 最小權限原則: Web 進程用戶(www-data/nginx)不應有 Shell 登入權限(/usr/sbin/nologin),無 sudo 權限,僅能讀寫項目目錄。
  5. 審計日誌: 所有命令執行記錄 cmd, user, ip, time, exit_code 至不可篡改日誌系統。

常見問題排查

現象原因解決
PHP 腳本卡住、超時命令輸出未重定向、前台運行、等待輸入重定向 stdout/stderr 到文件/ /dev/null,加 & 或 start /B 後台運行
Windows 下 exec('start ...') 仍阻塞路徑含空格未加引號、未用 start /Bexec('start /B "" "C:\Program Files\app.exe"') 雙引號處理
權限被拒絕Web 用戶無執行權限、目標文件不可執行chmod +x script.sh;確認目錄可讀寫;SELinux/AppArmor 策略
環境變量丟失(PATH、LD_LIBRARY_PATH)Web 環境非登入 Shell,環境變量極少命令中使用絕對路徑 /usr/bin/php;或 putenv('PATH=...') 設置
中文亂碼編碼不一致(Windows GBK vs PHP UTF-8)iconv('GBK', 'UTF-8', $output);或 chcp 65001 設置控制台編碼

現代替代方案:避免直接調用 Shell

  • Symfony Process Component:new Process(['php', 'script.php']),物件化、跨平台、支援超時、回調、輸出流。
  • PHP 原生擴展替代: 圖片處理用 GD/Imagick、視頻用 FFmpeg-PHP、壓縮用 ZipArchive、郵件用 SwiftMailer/Symfony Mailer。
  • 異步任務隊列: Laravel Queue / Symfony Messenger / Redis + Worker,將耗時命令推入隊列,HTTP 請求秒級返回。
  • 容器化調用: 將外部工具封裝為 Docker 映像,PHP 通過 Docker API 或 HTTP 調用,隔離環境、限制資源。

總結

PHP 執行系統命令是一把雙刃劍。首選 exec() 獲取結構化輸出,proc_open() 處理複雜交互,背景任務務必重定向輸出並後台運行。 生產環境嚴格遵循「最小權限、參數轉義、白名單驗證、審計日誌」四大原則。隨著生態成熟,越來越多場景可由純 PHP 庫或異步隊列替代,應優先評估替代方案,將 exec 類函數作為最後手段。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved