先确认shell_exec是否被disable_functions禁用:通过phpinfo()页面查找disable_functions值,若含shell_exec则需编辑对应php.ini删除该函数并重启web服务。

shell_exec 被禁用时怎么确认和修复
90% 的 shell_exec() 失败不是代码写错,而是它被 disable_functions 明确禁用了。Web 环境和 CLI 环境的 PHP 配置是两套——你在终端跑 php -i | grep disable_functions 看不到 Web 里实际生效的值。
必须访问一个临时 phpinfo.php 页面,搜索 disable_functions 字段,确认 shell_exec 是否在列表中。如果在,且你有服务器权限:
- 编辑
/etc/php/*/apache2/php.ini(Apache)或/etc/php/*/fpm/php.ini(Nginx + PHP-FPM) - 找到
disable_functions =行,删掉shell_exec(不要留空格,多个函数用逗号分隔) - 重启 Web 服务:
sudo systemctl restart apache2或sudo systemctl restart php*-fpm
注意:禁用函数列表里还常见 exec、system、passthru,它们往往一并被关。别只修一个。
用户输入拼接命令的致命错误
直接把 $_GET['path'] 插进命令字符串,等于给黑客递刀子。Shell 遇到分号 ;、管道 |、反引号 ` 会执行后续指令,比如传入 test.txt; rm -rf / 就真能删根目录。
正确做法不是“转义后拼接”,而是彻底隔离参数:
- 用
escapeshellarg()包裹每个外部输入,例如'ls -l '.escapeshellarg($user_path) - 避免用
escapeshellcmd()处理整条命令——它会过度转义空格、引号,导致命令解析失败 - 更安全的做法是:把用户输入只作为脚本参数,不参与命令构造,例如
exec('/usr/local/bin/backup.sh '.escapeshellarg($site_id), $out, $rc) - 对关键参数加白名单校验,比如只允许
preg_match('/^[a-z0-9_-]{3,20}$/', $site_id)
为什么 exec() 比 shell_exec() 更适合生产环境
shell_exec() 只返回字符串,没有返回码,出错时你只能靠输出内容猜;exec() 能同时拿到输出数组和 $return_var,这对判断成败至关重要。
例如备份脚本失败时,shell_exec('tar -cf /tmp/bak.tar /data') 返回空字符串,你不知道是路径不存在、磁盘满,还是权限不够。而 exec() 可以:
- 检查
$return_var === 0判断是否成功 - 读
$output数组看具体报错,比如"tar: /data: Cannot open: Permission denied" - 配合
error_log()记录完整上下文,方便回溯
另外,exec() 默认不输出到页面,避免敏感信息意外暴露;system() 和 passthru() 会直接刷屏,调试期可用,上线前务必换掉。
脚本权限和执行用户经常被忽略
PHP 进程通常以 www-data(Debian/Ubuntu)或 apache(CentOS)身份运行,它跟你的 SSH 登录用户完全无关。你 chmod 755 了脚本,但若脚本里调用了 sudo、访问了 /root/ 或写了 /var/log/,照样失败。
验证方式必须模拟真实上下文:
- 用
sudo -u www-data /path/to/script.sh手动执行一次 - 检查脚本里所有路径是否对
www-data可读可写(比如/tmp通常可以,/home/yourname几乎一定不行) - 脚本开头加
set -e,确保任意命令失败立即退出,而不是静默跳过 - 避免在脚本里用
cd /some/path && ./do.sh,改用绝对路径,防止工作目录不可控
真正棘手的不是“能不能跑”,而是“跑一半卡住”——比如没设超时、没处理 stdin/stderr 阻塞、或后台进程被系统回收。这种问题不会报错,只会让按钮点了没反应,查日志也一片空白。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











