Linux下使用zip命令选择性压缩子目录内容的技巧


阅读 2 次

场景需求分析

在日常开发中,我们经常需要将项目目录打包分发,但往往不需要包含所有子目录的全部内容。比如一个典型的项目结构:

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 -@