场景需求分析
在日常开发中,我们经常需要将项目目录打包分发,但往往不需要包含所有子目录的全部内容。比如一个典型的项目结构:
project/
project/src/
project/src/main.py
project/src/utils.py
project/docs/
project/docs/api.md
project/build/
project/build/temp/
project/README.md
我们可能希望:
- 完整打包src目录及其所有文件
- 保留docs目录结构但不包含实际文档
- 完全排除build目录
zip命令的局限性
标准的zip命令有两种工作模式:
# 仅压缩顶层文件,不处理子目录内容
zip project.zip project/
# 递归压缩所有内容
zip -r project.zip project/
这两种方式都无法满足我们的选择性压缩需求。
解决方案:组合使用find和zip
最可靠的方法是结合find命令筛选文件,然后通过管道传递给zip:
# 压缩src目录所有文件,但只保留docs空目录
find project/ $-path "project/src/*" -o -name "*.md"$ -print | zip project.zip -@
find project/ -type d $-path "project/docs"$ -empty | zip project.zip -@
更精细的控制方案
对于复杂场景,可以编写shell脚本实现更精确的控制:
#!/bin/bash
# 创建临时文件列表
FILELIST=$(mktemp)
# 添加需要包含的文件
find project/src -type f >> $FILELIST
find project -maxdepth 1 -type f >> $FILELIST
# 添加需要保留的空目录
find project/docs -type d -empty >> $FILELIST
# 执行压缩
zip project.zip -@ < $FILELIST
rm $FILELIST
Python实现方案
对于Python开发者,可以使用zipfile模块实现更灵活的控制:
import zipfile
import os
def selective_zip(output, root, include_dirs=[], exclude_dirs=[]):
with zipfile.ZipFile(output, 'w') as zf:
for root, dirs, files in os.walk(root):
# 处理包含目录
if any(root.startswith(os.path.join(root, d)) for d in include_dirs):
for file in files:
zf.write(os.path.join(root, file))
# 处理排除目录
elif not any(root.startswith(os.path.join(root, d)) for d in exclude_dirs):
relpath = os.path.relpath(root, start=os.path.dirname(root))
zf.write(root, relpath)
if not files: # 空目录
zinfo = zipfile.ZipInfo(relpath + "/")
zf.writestr(zinfo, "")
# 使用示例
selective_zip('project.zip', 'project',
include_dirs=['src'],
exclude_dirs=['build'])
实际应用建议
1. 对于持续集成场景,建议将压缩规则写入Makefile:
dist:
find src -type f | zip project.zip -@
find docs -type d -empty | zip project.zip -@
2. 对于需要保留.gitignore风格排除规则的情况,可以考虑使用git ls-files:
git ls-files | zip project.zip -@