exec只返回最后一行输出且需数组参数捕获全部结果,shell_exec返回完整stdout字符串;二者均不捕获stderr,均存在RCE风险,应严格过滤输入或改用proc_open。

exec 和 shell_exec 本质区别在哪
它们都调用系统 shell 执行命令,但返回值处理逻辑完全不同:exec 默认只返回**最后一行输出**(且不自动捕获完整输出),而 shell_exec 返回**整个命令的标准输出字符串**(不含 stderr,除非重定向)。
常见错误现象:用 exec('ls -l') 想看全部文件列表,结果只拿到最后一行 —— 这不是 bug,是设计如此。
-
exec的第二个参数(数组变量)才是它真正“收集全部输出”的方式,不传就丢数据 -
shell_exec更接近直觉,适合简单取结果,比如shell_exec('date') - 两者都不自动处理 stderr;要捕获错误,得手动加
2>&1,例如shell_exec('ls /noexist 2>&1')
什么时候该用 exec 而不是 shell_exec
当你需要同时获取命令退出码、控制输出截断、或边执行边处理输出时,exec 更灵活。
使用场景:监控长时间运行的命令(如 ping -c 4 example.com),或者需要判断成功与否再分支处理。
- 用
exec的第三个参数接收退出码:exec('grep foo bar.txt', $output, $returnCode),此时$returnCode === 0表示匹配成功 - 如果只关心前几行,
exec配合head -n 5管道更省内存,避免把 10MB 日志全读进 PHP 字符串 -
shell_exec在大输出时可能触发内存限制,exec+ 输出数组则能自然分块
安全陷阱:为什么 system/exec/shell_exec 全部高危
只要用户输入拼进命令字符串,就大概率导致远程命令执行(RCE)。这不是函数选错的问题,是用法越界。
典型翻车点:把 $_GET['file'] 直接塞进 exec("cat {$_GET['file']}") —— 攻击者传 file=xxx; rm -rf / 就完事。
- 绝对不要拼接用户输入;必须用
escapeshellarg()或escapeshellcmd(),但注意:前者用于单个参数,后者用于整条命令,别混用 -
escapeshellarg('a b;c') → 'a b\;c',而escapeshellcmd('ls ' . $user) → 'ls a b\;c',后者仍可能被绕过 - 更稳妥的做法是:白名单校验 + 文件路径约束(如限定在
/var/www/logs/下),而非依赖转义
替代方案:proc_open 比 exec 更可控
当你要双向通信、设置超时、分离 stdout/stderr,或防止子进程变成僵尸,proc_open 是唯一靠谱选择。
性能影响不大,但复杂度明显上升;多数简单场景没必要上,但一旦涉及超时控制或流式读取,它就不可替代。
- 必须手动
proc_close(),否则资源泄漏;PHP 7.4+ 可用proc_get_status()查存活状态 - 设置超时要用
stream_set_timeout($pipes[1], 5),不是给proc_open加参数 - stderr 默认不继承,需在描述符数组里显式声明:
['2' => ['pipe', 'w']]
实际项目里,90% 的需求用 shell_exec 加严格输入过滤就够了;剩下 10%,该上 proc_open 就别硬扛。最危险的,永远是以为“我只是临时调试一下”就裸奔拼接命令。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











