PHP exec()执行zip备份命令与终端结果不一致的问题排查
问题背景
有一个名为backup-generic的PHP进程,用于构建并执行zip命令备份目录。生成的命令如下:
zip --symlinks --recurse-paths --quiet /usr/local/backups/home_dennis-daily-2024-09-19.zip '/home/dennis' -x '.DS_Store' -x '._*' -x '/home/delovelady/.vscode-server/bin/*/node/*' -x '/home/delovelady/.vscode-server/data/*' 2>&1
该命令通过PHP的exec()执行时,压缩包仍包含本应通过-x排除的vscode文件;但将日志中的命令原样在ksh终端执行时,能正确排除文件,压缩包体积小300MB。相关执行代码:
$lov->writeLogLine( [ 'Executing command: %s', $backupCmd ] ) ; $rc = 0 ; $OK = exec($backupCmd, $output, $rc) ;
排除列表通过globFileList()函数处理,用escapeshellarg()对路径转义加单引号,拼接命令的逻辑为:
foreach ($exclusions as $exclude) $backupCmd .= ' -x' . $exclude ;
环境:Ubuntu 22.04.5 LTS,ksh 93u+m/1.0.0-beta.2,Zip 3.0。
原因分析
命令拼接的空格缺失
代码中拼接排除项时,-x选项与后续的排除路径之间没有添加空格,导致最终命令中出现-x'/home/...'的格式。zip命令无法识别这种连写格式——它期望-x作为独立选项,后续路径作为单独参数,因此忽略了排除规则。而终端执行的命令带有空格(-x '/home/...'),zip能正确解析。Shell环境差异
PHP的exec()默认使用系统的/bin/sh(Ubuntu 22.04中为dash),而终端使用ksh。dash与ksh对这种连写选项的解析行为不同:ksh可能会将-x'/path'拆分为-x和/path两个参数,而dash会将其作为单个参数传递给zip,进一步导致排除规则失效。
解决方法
修正命令拼接逻辑
在-x和排除路径之间添加空格,确保选项与参数分离:foreach ($exclusions as $exclude) $backupCmd .= ' -x ' . $exclude ; // -x后添加空格统一Shell环境(可选)
强制PHP使用ksh执行命令,确保与终端环境一致:// 将原命令用ksh执行 $execCmd = 'ksh -c ' . escapeshellarg($backupCmd); $rc = 0 ; $OK = exec($execCmd, $output, $rc) ;
内容的提问来源于stack exchange,提问作者Dennis
相关产品推荐
相关产品推荐

