
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 实现的操作。
通过脚本封装,不仅解决了多命令执行问题,更提升了安全性、可测试性与运维友好性——这才是面向生产环境的稳健实践。










