exec默认只返回最后一行输出,要获取完整输出必须传入第二个数组参数接收每行结果,或改用shell_exec/passthru;三个参数分别指定命令、输出数组引用和返回状态码。

exec只返回最后一行,不传数组参数就丢数据
这是最常踩的坑:写 exec('ls -l') 后发现只打印出一个文件名,还以为命令没执行全。其实 exec 默认行为就是只返回 stdout 的最后一行字符串,前面所有输出全被丢弃。它不是 bug,是设计如此。
要拿到全部输出,必须显式传入第二个参数(数组变量),例如:
$output = [];
exec('ls -l /tmp', $output, $returnCode);
// $output 现在是包含每行输出的索引数组,$returnCode 是退出码
- 每次调用前记得清空
$output,否则历史结果会累积 - 如果命令输出超大(比如 10MB 日志),
$output会一次性载入内存,不如用exec('tail -n 20 /var/log/app.log', $output)控制量 -
$returnCode非常关键——0表示成功,非0值(如127)通常意味着命令未找到,126表示无执行权限
shell_exec返回完整stdout字符串,但stderr默认不包含
shell_exec('date') 直观好用,适合拿回一整段文本做 json_decode 或正则匹配。但它返回的是单个字符串,换行符是 \n,不是数组。
常见错误是以为它能捕获报错信息。实际上 stderr 完全被忽略,shell_exec('ls /noexist') 返回 null,而不是错误提示。
- 想同时捕获 stdout + stderr,必须手动重定向:
shell_exec('ls /noexist 2>&1') - 大输出场景下容易触发 PHP 内存限制(
memory_limit),尤其当命令本身输出几 MB 文本时 - 和反撇号
`pwd`完全等价,只是写法更明确
两者都不处理权限、PATH 和禁用函数问题
即使语法完全正确,exec 和 shell_exec 在 Web 环境下也常静默失败。原因往往不在函数本身,而在运行环境:
- PHP 进程用户(如
www-data或nobody)可能没权限执行目标命令或访问路径,用exec('whoami')先确认身份 - 别依赖
PATH,写绝对路径更稳,比如用/bin/ls而非ls - 检查
php.ini中的disable_functions是否禁用了它们;常见组合是disable_functions = exec,passthru,shell_exec,system - SELinux 或 AppArmor 可能拦截调用,CentOS 上可临时用
setenforce 0排查
安全风险远大于函数选型问题
真正致命的不是“该用哪个函数”,而是把用户输入直接拼进命令字符串。例如 exec("cat " . $_GET['file']),攻击者传 ?file=/etc/passwd; rm -rf / 就完成 RCE。
-
escapeshellarg()只适用于单个参数,比如文件路径;escapeshellcmd()用于整条命令,但仍有绕过可能 - 更可靠的做法是白名单校验 + 路径约束,例如只允许读取
/var/www/logs/下的.log文件 - 涉及超时控制、双向通信或分离 stderr/stdout 时,
proc_open是唯一可控选择,但复杂度明显上升,且必须配对调用proc_close()
多数线上服务根本不该开放 shell 执行能力。如果真需要,优先考虑用原生 PHP 实现替代方案,或者将命令封装为独立 API 服务隔离调用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











