下面我将详细讲解这两种方法,重点是如何安全、正确地传递参数,并包含完整的代码示例。

(图片来源网络,侵删)
核心要点:安全第一!
在将 PHP 变量传递给 Shell 命令时,绝对不要使用字符串拼接,这会导致严重的命令注入安全漏洞。
❌ 危险的错误示范:
$userInput = "malicious; rm -rf /"; // 恶意输入 $command = "ls -l /home/" . $userInput; // 拼接 shell_exec($command); // 这会执行 "ls -l /home/malicious; rm -rf /" // 你的服务器可能被删除了!
✅ 正确的安全做法:
使用 escapeshellarg() 或 escapeshellcmd() 函数对参数进行转义。

(图片来源网络,侵删)
escapeshellarg(): 最常用、最推荐,它会将一个字符串转义成 Shell 命令中可以安全使用的参数,它会给字符串加上单引号,并处理掉字符串内部的单引号。escapeshellcmd(): 转义字符串中会对 Shell 造成影响的任何字符,它更侧重于转义命令本身,而不是参数。
使用反引号(`)
这是最简洁的方法,它将 Shell 命令的输出直接作为字符串返回。
语法:
$output = `your command here`;
示例:列出指定目录的文件
假设我们要列出 /var/log 目录下的文件,并将目录路径作为 PHP 变量传递。
<?php $directory = "/var/log"; // 1. 使用 escapeshellarg() 安全地转义参数 $escapedDirectory = escapeshellarg($directory); // 2. 构建命令 $command = "ls -l " . $escapedDirectory; // 3. 执行命令并获取输出 $output = `$command`; // 4. 输出结果 echo "<pre>"; echo "执行的命令: " . htmlspecialchars($command) . "\n\n"; echo "命令输出:\n"; echo $output; echo "</pre>"; ?>
工作原理:
$directory是你的变量。escapeshellarg($directory)会确保这个变量被安全地包裹在单引号中。$directory是/var/log/my log,它会变成'/var/log/my log',这样空格就不会被 Shell 误解为参数分隔符。- 最终执行的命令是
ls -l '/var/log',这是完全安全的。
使用专门函数
PHP 提供了一系列函数来执行 Shell 命令,它们各有特点。
shell_exec()
与反引号功能几乎完全相同,也是将整个命令的输出作为一个字符串返回。
<?php $directory = "/tmp"; $escapedDirectory = escapeshellarg($directory); $command = "ls -l " . $escapedDirectory; $output = shell_exec($command); echo "<pre>"; echo "执行的命令: " . htmlspecialchars($command) . "\n\n"; echo "命令输出:\n"; echo $output; echo "</pre>"; ?>
exec()
exec() 会执行命令,但只返回命令的最后一行输出,如果你需要获取完整的输出,需要传递第二个参数(一个数组)来存储所有输出行。
<?php $directory = "/etc"; $escapedDirectory = escapeshellarg($directory); $command = "ls -l " . $escapedDirectory; // 第二个参数 $outputArray 会接收所有输出行 $lastLine = exec($command, $outputArray, $returnStatus); echo "<pre>"; echo "执行的命令: " . htmlspecialchars($command) . "\n"; echo "命令返回状态码: " . $returnStatus . " (0 表示成功)\n\n"; echo "使用 exec() 获取的最后一行输出:\n"; echo htmlspecialchars($lastLine) . "\n\n"; echo "使用 exec() 获取的所有输出行 (通过数组):\n"; print_r($outputArray); echo "</pre>"; ?>
适用场景:
- 当你只关心命令是否成功执行(检查
$returnStatus)。 - 当你只需要命令输出的最后一行内容。
system()
system() 会执行命令,并立即将输出直接打印到浏览器,同时返回命令的最后一行输出。
<?php $directory = "/usr/bin"; $escapedDirectory = escapeshellarg($directory); $command = "ls -l " . $escapedDirectory; // 输出会直接显示在页面上 $lastLine = system($command, $returnStatus); echo "<hr>"; echo "命令返回状态码: " . $returnStatus . "<br>"; echo "system() 函数返回的最后一行: " . htmlspecialchars($lastLine); ?>
适用场景:
- 当你希望实时看到命令的输出流时(执行一个长时间运行的脚本并显示进度)。
passthru()
passthru() 与 system() 类似,它也会直接输出原始的、未经处理的二进制数据到浏览器,这对于执行像 zip 或 imagemagick 这样的命令并直接让用户下载文件非常有用。
<?php
// 示例:创建一个 zip 文件并直接提供下载
$fileToZip = '/path/to/a/large/file.txt';
$zipFileName = 'archive.zip';
$escapedFile = escapeshellarg($fileToZip);
$escapedZip = escapeshellarg($zipFileName);
$command = "zip -j " . $escapedZip . " " . $escapedFile;
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . basename($zipFileName) . '"');
// 直接将命令的输出(zip文件的二进制流)发送给浏览器
passthru($command);
?>
适用场景:
- 直接输出二进制文件内容(如图片、压缩包)。
传递多个参数
如果你的命令需要多个参数,只需对每一个需要拼接的变量都使用 escapeshellarg()。
示例:使用 grep 在文件中搜索
<?php $searchTerm = "error"; $logFile = "/var/log/nginx/error.log"; // 对每个变量都进行转义 $escapedSearch = escapeshellarg($searchTerm); $escapedFile = escapeshellarg($logFile); $command = "grep " . $escapedSearch . " " . $escapedFile; echo "执行的命令: " . htmlspecialchars($command) . "<br><br>"; $output = shell_exec($command); echo "搜索结果:<br><pre>" . htmlspecialchars($output) . "</pre>"; ?>
总结与最佳实践
| 函数 | 返回值 | 输出方式 | 主要用途 |
|---|---|---|---|
反引号 ` |
完整输出 (字符串) | 返回给变量 | 简单获取命令的全部输出 |
shell_exec() |
完整输出 (字符串) | 返回给变量 | 反引号的函数版,更清晰 |
exec() |
最后一行输出 | 可选:存入数组 | 需要精确控制输出,或获取状态码 |
system() |
最后一行输出 | 直接打印 | 需要实时显示命令执行过程 |
passthru() |
无 (返回 null) |
直接打印原始流 | 输出二进制数据,如文件下载 |
黄金法则:
- 永远不要信任外部输入:无论是来自
$_GET,$_POST, 还是其他任何来源的数据。 - 始终使用
escapeshellarg():在将 PHP 变量作为参数传递给 Shell 命令之前,必须用这个函数进行转义。 - 选择合适的函数:
- 只想拿结果 ->
shell_exec()或 反引号。 - 想实时看输出 ->
system()。 - 想处理二进制文件 ->
passthru()。 - 需要详细状态和行控制 ->
exec()。
- 只想拿结果 ->
