
PHP执行系统命令完全指南:exec、shell_exec、passthru与system函数详解与安全实践
PHP作为一门服务端脚本语言,虽然自带丰富的内置函数,但在实际开发中难免会遇到需要调用外部程序的情况。比如处理上传的图片、转换视频格式、打包下载文件,或者执行一些系统级别的维护任务。这时候就需要用到PHP与系统命令交互的能力。
PHP提供了四个专门用于执行系统命令的函数:exec()、shell_exec()、passthru()和system()。它们看似相似,实则各有侧重。选对了能事半功倍,选错了则可能带来不必要的麻烦。下面我们就来逐个剖析它们的用法和特点。
一、exec函数:适合需要逐行处理输出的场景
基本用法
exec()是PHP中最常用的命令执行函数之一。它的特点是只返回命令输出的最后一行,而不是全部内容。如果你需要获取完整的输出,可以通过第二个参数传入一个数组变量,所有输出行会被存入这个数组中。第三个参数可以用来获取命令的退出状态码,通常0表示成功,非0表示失败。
// 执行ls命令列出当前目录文件
$command = 'ls -la';
$output = []; // 存储所有输出行
$status = null; // 存储退出状态码
$lastLine = exec($command, $output, $status);
if ($status === 0) {
echo "命令执行成功,共输出了" . count($output) . "行";
foreach ($output as $line) {
echo htmlspecialchars($line) . "<br>";
}
} else {
echo "命令执行失败,错误码:" . $status;
}什么时候用exec
当你需要对命令的输出进行逐行处理时,exec()是最合适的选择。比如你要读取某个日志文件的最后几行,或者解析系统命令返回的表格数据,都可以利用它的数组输出功能。而且通过状态码可以精确判断命令是否执行成功,这在自动化任务中非常有用。
实际应用示例
假设你要检测服务器上的磁盘使用情况,然后根据结果决定是否发送告警通知:
$command = 'df -h / | tail -1';
$output = [];
$status = null;
exec($command, $output, $status);
if ($status === 0 && !empty($output)) {
// 输出格式类似:/dev/sda1 50G 30G 18G 64% /
$parts = preg_split('/\s+/', $output[0]);
$usagePercent = str_replace('%', '', $parts[4]);
if ((int)$usagePercent > 90) {
// 发送告警邮件或短信
echo "警告:磁盘使用率已达{$usagePercent}%";
} else {
echo "磁盘使用正常:{$usagePercent}%";
}
}二、shell_exec函数:想要完整字符串输出时首选
基本用法
shell_exec()的功能比较直接:执行一条命令,然后把命令的全部输出作为一个字符串返回。如果命令没有输出,它会返回null。这个函数的名字很容易让人联想到在Shell中执行命令的场景,事实也确实如此。
// 获取当前工作目录
$currentDir = shell_exec('pwd');
echo "当前目录是:" . trim($currentDir);
// 获取系统运行时间
$uptime = shell_exec('uptime');
echo "系统运行状态:" . trim($uptime);和exec的区别
shell_exec()和exec()最大的区别在于返回值的形态。前者直接给你一个完整的字符串,后者则需要通过数组来获取全部输出。如果你的需求只是拿到结果字符串,不需要逐行处理,那么shell_exec()写起来会更简洁。
实际应用示例
在后台管理系统中,经常需要查看服务器的基本信息,用shell_exec()可以快速实现:
function getSystemInfo() {
$info = [];
// CPU信息
$cpuInfo = shell_exec("grep 'model name' /proc/cpuinfo | head -1");
$info['cpu'] = trim(explode(':', $cpuInfo)[1] ?? '未知');
// 内存信息
$memInfo = shell_exec("free -h | grep Mem");
if ($memInfo) {
$parts = preg_split('/\s+/', $memInfo);
$info['memory'] = "总计{$parts[1]},已用{$parts[2]},可用{$parts[3]}";
}
// 硬盘信息
$diskInfo = shell_exec("df -h / | tail -1");
if ($diskInfo) {
$parts = preg_split('/\s+/', $diskInfo);
$info['disk'] = "总计{$parts[1]},已用{$parts[2]},可用{$parts[3]}";
}
return $info;
}
$sysInfo = getSystemInfo();
print_r($sysInfo);三、passthru函数:处理二进制输出和实时流的最佳选择
基本用法
passthru()的工作方式有些特别:它不会捕获命令的输出并返回给PHP,而是直接把原始输出发送到浏览器的输出缓冲区。这对于处理二进制数据非常有用,比如直接输出一张图片或者一个PDF文件。它没有返回值,但可以通过第二个参数获取退出状态码。
// 直接输出一张图片到浏览器
header('Content-Type: image/jpeg');
passthru('cat /path/to/photo.jpg', $status);
if ($status !== 0) {
// 如果命令失败,创建一个简单的错误图片
header('Content-Type: image/png');
$im = imagecreatetruecolor(200, 100);
$bg = imagecolorallocate($im, 255, 255, 255);
$textColor = imagecolorallocate($im, 255, 0, 0);
imagestring($im, 5, 20, 40, 'Image not found', $textColor);
imagepng($im);
imagedestroy($im);
}为什么不用其他函数
如果用exec()或shell_exec()来处理二进制数据,它们会把二进制内容当作字符串读到内存里,然后再输出。这样不仅浪费内存,还可能在处理大文件时导致内存溢出。而passthru()直接把数据流式传输到浏览器,不经过PHP的内存缓冲,效率高得多。
实际应用示例
视频转码是一个典型的耗时操作,使用passthru()可以让用户看到实时的进度信息:
// 将上传的视频转为MP4格式,并实时显示ffmpeg的输出
header('Content-Type: text/plain; charset=utf-8');
$inputFile = '/tmp/uploaded_video.avi';
$outputFile = '/tmp/converted_video.mp4';
$command = "ffmpeg -i {$inputFile} -vcodec libx264 -acodec aac {$outputFile} 2>&1";
// 开启输出缓冲刷新
ob_implicit_flush(true);
ob_end_flush();
passthru($command, $status);
if ($status === 0) {
echo "\n\n视频转码完成!";
} else {
echo "\n\n视频转码失败,请检查源文件格式。";
}四、system函数:既要看输出又要拿结果时的折中选择
基本用法
system()可以说是介于passthru()和exec()之间的一个函数。它像passthru()一样直接把输出发送到浏览器,同时又像exec()一样返回输出的最后一行。如果你需要在显示输出内容的同时,还要获取一部分结果做后续处理,system()就很合适。
// 执行ping命令测试网络连通性
$command = 'ping -c 4 114.114.114.114';
echo "开始网络测试...<br>";
$lastLine = system($command, $status);
echo "<br>测试结束,最后一行输出:" . $lastLine;
if ($status === 0) {
echo "<br>网络连接正常";
} else {
echo "<br>网络连接异常";
}和passthru的细微差别
虽然两者都会把输出直接送到浏览器,但system()会返回最后一行输出,而passthru()什么都不返回。这个区别在某些场景下很关键。比如你想在执行完一系列命令后,获取最后一个命令的结果来做判断,用system()就比passthru()方便。
实际应用示例
在运维后台执行批量任务时,可以用system()来展示实时进度并收集最终结果:
function restartWebServer() {
echo "正在重启Web服务器...<br>";
// 先优雅停止
echo "步骤1:停止Nginx服务<br>";
system('sudo nginx -s quit', $stopStatus);
if ($stopStatus !== 0) {
echo "停止失败,尝试强制停止<br>";
system('sudo killall nginx', $forceStopStatus);
}
// 启动服务
echo "步骤2:启动Nginx服务<br>";
$result = system('sudo nginx', $startStatus);
if ($startStatus === 0) {
echo "Web服务器重启成功!<br>";
echo "最后状态:" . $result;
} else {
echo "Web服务器重启失败,请检查配置文件。<br>";
// 检查配置
system('nginx -t', $configStatus);
}
}
restartWebServer();五、安全问题不容忽视:命令注入防护
危险在哪里
在实际项目中,很多时候需要执行的命令会包含用户输入的内容。比如用户上传了一个文件,你需要对它进行处理;或者用户在搜索框中输入了一个文件名,你要去查找。如果不加处理就直接拼接命令,就会留下严重的安全隐患。
举个反面例子:
// 极度危险的写法!千万不要在生产环境使用!
$filename = $_GET['file'];
$output = shell_exec("cat /var/logs/" . $filename);如果用户输入的file参数是../../etc/passwd,就能读取系统密码文件。更糟糕的是,如果输入的是; rm -rf /,后果不堪设想。
正确的防护方法
PHP提供了两个专门的函数来应对这个问题:
escapeshellarg():给参数加上单引号,并对内部的单引号进行转义,确保参数被当作一个整体处理。
escapeshellcmd():对整个命令字符串进行转义,防止特殊字符被执行。
两者的区别在于:escapeshellarg()更适合保护单个参数,escapeshellcmd()适合保护整个命令。通常建议优先使用escapeshellarg()。
// 安全的做法
$userInput = $_GET['filename'];
// 方法一:使用escapeshellarg保护参数
$safeArg = escapeshellarg($userInput);
$output = shell_exec('cat ' . $safeArg);
// 方法二:如果需要多个参数
$directory = escapeshellarg($_GET['dir']);
$pattern = escapeshellarg($_GET['pattern']);
$output = shell_exec("find {$directory} -name {$pattern}");更多安全建议
除了使用转义函数,还有几点需要注意:
- 尽量限制可执行命令的范围:不要直接拼接用户输入的命令,而是建立一个允许执行的命令列表,用户只能从中选择。
- 使用白名单验证:如果用户输入只能是数字或特定格式,先用正则表达式验证一遍。
- 最小权限原则:PHP进程的运行用户应该只有执行必要命令的最小权限,不要直接用root用户运行Web服务。
// 更安全的做法:白名单+参数校验
$allowedCommands = [
'list' => 'ls -la',
'disk' => 'df -h',
'memory' => 'free -m'
];
$action = $_GET['action'] ?? '';
if (array_key_exists($action, $allowedCommands)) {
// 只执行预定义的命令
$output = shell_exec($allowedCommands[$action]);
echo "<pre>" . htmlspecialchars($output) . "</pre>";
} else {
echo "无效的操作请求";
}总结与实践建议
综合来看,这四个函数各有适用的场景:
- 需要逐行处理输出结果时,用
exec()配合数组参数 - 只需要获取完整的文本输出时,用
shell_exec()最简洁 - 处理二进制数据或需要实时输出时,
passthru()是唯一正确的选择 - 既要显示输出又要获取最后一行结果时,
system()是个不错的折中方案
在实际开发中,还有一个常见的坑需要特别注意:很多PHP环境出于安全考虑,会在php.ini中禁用这些函数。如果发现函数无法使用,可以检查一下disable_functions配置项。当然,如果服务器安全策略不允许执行外部命令,那就要另寻他法,比如使用PHP扩展来代替系统命令的功能。
最后再次强调:只要涉及到用户输入,就必须做好安全防护。宁可多写几行验证代码,也不要给攻击者可乘之机。安全无小事,尤其是在可以执行系统命令的情况下更是如此。
PHP系统交互命令执行安全注意事项exec函数shell_exec函数修改时间:2026-08-01 04:37:06