
php 中通过 shell_exec 执行多条 mysql 命令时,单条调用易因引号嵌套、特殊字符转义、权限上下文不一致等问题导致后续命令失败;推荐统一通过 shell 脚本封装并参数化执行,确保语法安全与执行一致性。
php 中通过 shell_exec 执行多条 mysql 命令时,单条调用易因引号嵌套、特殊字符转义、权限上下文不一致等问题导致后续命令失败;推荐统一通过 shell 脚本封装并参数化执行,确保语法安全与执行一致性。
在 PHP 中直接拼接字符串调用 shell_exec(‘sudo mysql -u root -e “…”;’) 执行多条 MySQL 命令,看似直观,实则存在多个隐蔽风险:
- 引号嵌套混乱:PHP 双引号字符串中嵌套 SQL 字符串,需手动转义单引号、双引号及 $ 符号,极易出错(如 $pass_db 若含特殊字符或空格将直接破坏命令结构);
- SQL 语句分隔失效:mysql -e 仅支持单条语句(严格来说是单个 SQL 表达式),create database; create user; 这类用分号分隔的多语句会被截断或报错;
- 权限与环境差异:shell_exec 在 Web 服务器(如 apache/nginx 用户)上下文中执行,sudo 可能因缺少密码或 NOPASSWD 配置而静默失败,且每次调用都新建 MySQL 连接,状态不共享(如 USE db 不生效);
- 返回值不可靠:命令执行失败时 shell_exec 默认返回 NULL(而非错误信息),难以定位问题。
✅ 正确做法:将所有 SQL 操作封装为独立 shell 脚本,通过参数传递动态值,并确保原子性与安全性。
✅ 推荐实现方案(安全、可维护、可调试)
# 1. 创建临时安全脚本(建议使用 /tmp + 随机名 或专用可执行目录) $script = sys_get_temp_dir() . '/mysql_setup_' . bin2hex(random_bytes(8)) . '.sh'; file_put_contents($script, <<<EOF #!/bin/bash DB_NAME="$1" USER_NAME="$2" USER_PASS="$3" DB_ZIP_PATH="$4" # 创建数据库 mysql -u root -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 创建用户(显式指定主机 & 密码加引号防注入) mysql -u root -e "CREATE USER IF NOT EXISTS '${USER_NAME}'@'localhost' IDENTIFIED BY '${USER_PASS}';" # 授权(注意:数据库名和用户名必须用反引号包裹,避免关键字冲突) mysql -u root -e "GRANT ALL PRIVILEGES ON `${DB_NAME}`.* TO '${USER_NAME}'@'localhost';" # 刷新权限 mysql -u root -e "FLUSH PRIVILEGES;" # 导入数据(zcat 管道需确保 zip 文件存在且可读) if [ -f "${DB_ZIP_PATH}" ]; then zcat "${DB_ZIP_PATH}" | mysql -u root "${DB_NAME}" fi EOF ); // 设置可执行权限(关键!否则 sudo 会拒绝运行) chmod(0700, $script); // 执行脚本(使用 escapeshellarg 防止参数注入) $output = shell_exec("sudo {$script} " . escapeshellarg($name_db) . " " . escapeshellarg($user_db) . " " . escapeshellarg($pass_db) . " " . escapeshellarg($path_db_zip)); // 清理脚本(生产环境建议添加 try/finally 或 register_shutdown_function) unlink($script); // 检查执行结果 if ($output === null) { error_log("MySQL setup script execution failed or returned empty output."); throw new RuntimeException("Database setup failed. Check system logs and MySQL error log."); }
⚠️ 关键注意事项
- 绝不硬编码 root 密码:示例中假设 sudo mysql 免密(如配置了 sudoers 的 NOPASSWD),生产环境应改用专用管理账户 + 密码文件(–defaults-extra-file);
- 输入严格过滤:$name_db、$user_db 等变量必须校验格式(如仅允许字母数字下划线),或使用 escapeshellarg() —— 但不能替代 SQL 层的参数化(此处为系统命令层防护);
- 字符集显式声明:CREATE DATABASE … CHARACTER SET utf8mb4 避免中文乱码;
- 授权粒度控制:生产环境避免 GRANT ALL,按最小权限原则授予 select, INSERT, UPDATE, delete 等;
- 错误捕获增强:可在脚本中添加 set -e(遇错退出)和 2>&1 重定向 stderr,便于调试;
- 替代方案建议:长期项目推荐改用 pdo/mysqli 执行 DDL/DML,仅用 shell_exec 处理 zcat | mysql 这类无法纯 PHP 实现的操作。
通过脚本封装,不仅解决了多命令执行问题,更提升了安全性、可测试性与运维友好性——这才是面向生产环境的稳健实践。