exec只返回最后一行输出,shell_exec返回全部输出;exec需用数组参数捕获全量结果,shell_exec直接返回完整字符串,二者均需加2>&1捕获错误,且必须用escapeshellarg()安全传参。

exec只返回最后一行,shell_exec返回全部输出
这是最直接、最常踩的坑。比如执行 ls -la /tmp,用 exec 你会只看到一个文件名(最后一行),前面权限、大小、时间全丢了;而 shell_exec 返回完整字符串,含所有换行和格式。
如果你需要解析 JSON、XML 或多行日志,别用 exec 直接赋值——它默认只给你最后一行。正确做法是:
-
exec('python3 script.py', $output, $code):把全部行存进$output数组,$code拿退出码 -
$output = shell_exec('python3 script.py'):整段输出当字符串拿,适合json_decode($output)这类操作
调试失败时,2>&1 是必加项
两个函数默认都不捕获 stderr,命令出错(比如找不到 python3、权限拒绝、语法错误)时,exec 和 shell_exec 都可能静默返回空或 null,不报错也不提示。
必须显式合并错误流:
立即学习“PHP免费学习笔记(深入)”;
exec('python3 bad.py 2>&1', $output, $code)$output = shell_exec('python3 bad.py 2>&1')
这样 $output 才会包含类似 /usr/bin/python3: command not found 或 IndentationError 这类真实线索。
传参不逃逸=远程代码执行
用户输入拼进命令字符串,等于把 system() 的钥匙交出去。哪怕只是 $_GET['id'],也别写成:
exec("grep " . $_GET['id'] . " /var/log/app.log")
攻击者传 id=a; rm -rf / 就完了。必须逐个参数用 escapeshellarg() 包裹:
exec("grep " . escapeshellarg($_GET['id']) . " /var/log/app.log")
注意:escapeshellcmd() 是给整个命令逃逸的,不推荐用于参数拼接,它不处理内部空格和引号,容易绕过。
PHP 用户权限和路径问题比函数选型更致命
函数调用失败,90% 不是函数写错了,而是 PHP 进程(如 www-data)根本没权限跑那个命令。
排查顺序应该是:
- 用
which python3或command -v python3确认命令存在且在 PATH 中 - 手动切到 PHP 用户执行:
sudo -u www-data python3 --version - 脚本路径写绝对路径,避免依赖 shell 的 PATH 搜索
- 检查
disable_functions是否禁用了exec或shell_exec
尤其在容器或 cPanel 环境里,/usr/bin/python3 可能被软链接到不可执行位置,或 SELinux 限制了子进程创建——这些都比“该用哪个函数”优先级高得多。



















