如何压缩一个目录的内容,除了一个子目录?

How to zip a directory's contents except one subdirectory?

我正在尝试创建一个 zip 文件作为目录的备份,并最终将该备份保存在该目录的 "backups" 文件夹中。为了说明,"folder"包括"subFolder"、"file1.txt"、"file2.txt"和"backups"。 "backups" 文件夹将包含各种早期备份文件。我想创建 "folder" 及其所有内容(包括子文件夹)的存档,但不包括 "backups".

下面是我原本想用的:

ZipFile.CreateFromDirectory(folderToZip, backupFileName);

我意识到在正在压缩的文件夹中保存 zip 文件有问题,所以我打算将它保存在其他地方然后传输它。但我不知道如何在没有备份文件夹的情况下轻松创建存档。我唯一的想法是编写自己的方法递归遍历目录并排除那个文件夹。看来一定有更简单的方法。

如有任何帮助,我们将不胜感激!

遗憾的是,ZipFile 不提供过滤条目的方法。幸运的是,您可以根据 this implementation:

轻松创建这样的方法
public static class ZipHelper {
    public static void CreateFromDirectory(
        string sourceDirectoryName
    ,   string destinationArchiveFileName
    ,   CompressionLevel compressionLevel
    ,   bool includeBaseDirectory
    ,   Encoding entryNameEncoding
    ,   Predicate<string> filter // Add this parameter
    ) {
        if (string.IsNullOrEmpty(sourceDirectoryName)) {
            throw new ArgumentNullException("sourceDirectoryName");
        }
        if (string.IsNullOrEmpty(destinationArchiveFileName)) {
            throw new ArgumentNullException("destinationArchiveFileName");
        }
        var filesToAdd = Directory.GetFiles(sourceDirectoryName, "*", SearchOption.AllDirectories);
        var entryNames = GetEntryNames(filesToAdd, sourceDirectoryName, includeBaseDirectory);
        using(var zipFileStream = new FileStream(destinationArchiveFileName, FileMode.Create)) {
            using (var archive = new ZipArchive(zipFileStream, ZipArchiveMode.Create)) {
                for (int i = 0; i < filesToAdd.Length; i++) {
                    // Add the following condition to do filtering:
                    if (!filter(filesToAdd[i])) {
                        continue;
                    }
                    archive.CreateEntryFromFile(filesToAdd[i], entryNames[i], compressionLevel);
                }
            }
        }
    }
}

此实现允许您传递拒绝来自 "backup/" 目录的所有条目的过滤器:

ZipHelper.CreateFromDirectory(
    myDir, destFile, CompressionLevel.Fastest, true, Encoding.UTF8,
    fileName => !fileName.Contains(@"\backup\")
);

在我看来,您可以将文件夹中除 "backups" 文件夹之外的所有文件都取出来,并用它们制作一个临时文件夹,以便按照您所说的那样制作 zip。

使用这个 LINQ 查询,您可以以非常简单的方式完成:

List<FileInfo> files = di.GetFiles("*", SearchOption.AllDirectories).Where(file => !file.DirectoryName.Contains("backups")).ToList();

获得此列表后,您可以复制所有这些文件,制作 zip 并删除内容。我看不到更简单的方法。

您可以为 ZipArchive 添加扩展:

    public static void ZipDirectory(this ZipArchive zipArchive, string srcDir, IEnumerable<Regex>  excludeFileList = null, IEnumerable<Regex> excludeDirList = null , string rootDir = "")
    {
        if (!Directory.Exists(srcDir)) throw new Exception("source directory for zipping doesn't exit");
        var dir = new DirectoryInfo(srcDir);

        dir.GetFiles().ToList().ForEach((file) => {
            if (excludeFileList == null || excludeFileList.Where(rule => rule.IsMatch(file.Name)).Count() == 0)
            {
                zipArchive.CreateEntryFromFile(file.FullName, string.IsNullOrEmpty(rootDir) ? file.Name : $@"{rootDir}\{file.Name}");
            }});

        dir.GetDirectories().ToList().ForEach((directory) => {
            if (excludeDirList == null || excludeDirList.Where(rule => rule.IsMatch(directory.Name)).Count() == 0)
            {
                zipArchive.ZipDirectory(directory.FullName, excludeFileList, excludeDirList, string.IsNullOrEmpty(rootDir) ? $@"{directory.Name}" : $@"{rootDir}\{directory.Name}");
            }});
    }

并像这样使用它:

var excludeFileList = new List<Regex>() { new Regex(".pdb$") };

using (var zipFileStream = new FileStream(zipFile, FileMode.Create))
{
     using (var zipArch = new ZipArchive(zipFileStream, ZipArchiveMode.Create))
     {
          zipArch.ZipDirectory(src, excludeFileList );
     }
}

就我而言,我需要在 PowerShell 中执行此操作,这是我根据上述建议创建的脚本:

Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
    

function Get-RelativePath
{
    <#
    .SYNOPSIS
    Returns the relative path of an object compared to a Base path
    #>
    param (
        [Parameter(Mandatory=$true)][string]$Path,
        [Parameter(Mandatory=$true)][string]$BasePath,
        [Parameter(Mandatory=$true,ParameterSetName="file")][switch]$File,
        [Parameter(Mandatory=$true,ParameterSetName="dir")][switch]$Directory)

    $Base = [System.IO.Path]::GetFullPath($BasePath)
    if ($File) {
        $fi = new-object System.IO.FileInfo ($Path)
        $Full = $fi.Directory
    } else {
        $fi = $null
        $Full = new-object System.IO.DirectoryInfo ($Path)
    }

    $CurLoc = $Full
    $Relative = @()
    while ($CurLoc.FullName -ne $Base -and !([string]::IsNullOrEmpty($CurLoc.Name)))
    {
        #Write-Host $CurLoc.Name
        $Relative += $CurLoc.Name
        $CurLoc = $CurLoc.Parent
    }
    if ($Relative.Count -eq 0) {return ""}
    [void][System.Array]::Reverse($Relative)
    if ($null -ne $fi) {
        $Relative += $fi.Name
    }
    return [string]::Join([System.IO.Path]::DirectorySeparatorChar,$Relative)
}

function Get-DirectoriesWithExclusion
{
    <#
    .SYNOPSIS
    Gets a list ofdirectories with optional include/exclude lists
    .PARAMETER SourceDirectory
    Folder to compress
    .PARAMETER SearchPattern
    Wildcard for OS search
    .PARAMETER Recurse
    When enabled, will evaluate child directories too
    .PARAMETER excludeDirRegex

    List of regex to use in excluding Dirs by name
    .PARAMETER excludeDirList
    List of regex strings to use in excluding Dirs by name, only used if excludeDirRegex is not supplied
    .PARAMETER includeDirRegex
    List of regex to use in including Dirs by name
    .PARAMETER includeDirList
    List of regex strings to use in including Dirs by name, only used if includeDirRegex is not supplied
    #>
    param(
        [Parameter(Mandatory=$true)][string]$SourceDirectory, 
        [string]$SearchPattern = "*",
        [switch]$Recurse,
        [System.Text.RegularExpressions.Regex[]]$excludeDirRegex,
        [string[]]$excludeDirList,
        [System.Text.RegularExpressions.Regex[]]$includeDirRegex,
        [string[]]$includeDirList
    )
    if (![System.IO.Directory]::Exists($SourceDirectory)) { throw [System.IO.FileNotFoundException] "source directory $SourceDirectory for zipping does NOT exist";}
    $di = new-object System.IO.DirectoryInfo ($SourceDirectory)
    
    #build regex
    if ($null -eq $excludeDirRegex) {
        $excludeDirRegex = @()
        $excludeDirRegex += $excludeDirList | Where-Object {$_}| ForEach-Object{ new-object System.Text.RegularExpressions.Regex $_,([System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Compiled) }
    }
    if ($null -eq $includeDirRegex) {
        $includeDirRegex = @()
        $includeDirRegex += $includeDirList | Where-Object {$_}| ForEach-Object{ new-object System.Text.RegularExpressions.Regex $_,([System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Compiled) }
    }

    #find the dirs recursively
    foreach ($srcDir in $di.EnumerateDirectories($SearchPattern,[System.IO.SearchOption]::TopDirectoryOnly))
    {

        #if directory is not excluded
        if (($includeDirRegex.Count -eq 0 -or ($includeDirRegex | Where-Object {$_.IsMatch($srcDir.Name)})) -and!($excludeDirRegex | Where-Object {$_.IsMatch($srcDir.Name)}))
        {
            Write-Output $srcDir
            if ($Recurse) 
            {
                Get-DirectoriesWithExclusion -SourceDirectory $srcDir.FullName -SearchPattern $SearchPattern -includeDirRegex $includeDirRegex -excludeDirRegex $excludeDirRegex -Recurse
            }
        }
    }
}

function Compress-Directory 
{
    <#
    .SYNOPSIS
    Creates a zip of a directory
    .DESCRIPTION 
    Creates a zip of a directory with optional include/exclude lists
    .PARAMETER SourceDirectory
    Folder to compress
    .PARAMETER ZipFile
    Zipfile to create
    .PARAMETER SearchPattern
    Wildcard for OS search
    .PARAMETER Recurse
    When enabled, will evaluate child directories too
    .PARAMETER excludeFileRegex
    List of regex to use in excluding files by name
    .PARAMETER excludeFileList
    List of regex strings to use in excluding files by name, only used if excludeFileRegex is not supplied
    .PARAMETER excludeDirRegex

    List of regex to use in excluding Dirs by name
    .PARAMETER excludeDirList
    List of regex strings to use in excluding Dirs by name, only used if excludeDirRegex is not supplied
    .PARAMETER includeFileRegex
    List of regex to use in including files by name
    .PARAMETER includeFileList
    List of regex strings to use in including files by name, only used if includeFileRegex is not supplied
    .PARAMETER includeDirRegex
    List of regex to use in including Dirs by name
    .PARAMETER includeDirList
    List of regex strings to use in including Dirs by name, only used if includeDirRegex is not supplied
    .PARAMETER rootDirInZip
    When specified will create a folder in the zip to place all files
    .PARAMETER IgnoreRootFiles
    When specified, files in the root of the source directory will be ignored
    
    #>
    param (
        [Parameter(Mandatory=$true)][string]$SourceDirectory, 
        [Parameter(Mandatory=$true)][string]$ZipFile, 
        [string]$SearchPattern = "*",
        [switch]$Recurse,
        [System.Text.RegularExpressions.Regex[]]$excludeFileRegex,
        [System.Text.RegularExpressions.Regex[]]$excludeDirRegex,
        [string[]]$excludeFileList,
        [string[]]$excludeDirList,
        [System.Text.RegularExpressions.Regex[]]$includeFileRegex,
        [System.Text.RegularExpressions.Regex[]]$includeDirRegex,
        [string[]]$includeFileList,
        [string[]]$includeDirList,
        [string]$RootDirInZip,
        [switch]$WhatIf,
        [switch]$IgnoreRootFiles,
        [System.IO.Compression.CompressionLevel]$CompressionLevel = [System.IO.Compression.CompressionLevel]::Optimal

        )
    #init/validate parms
    if (![System.IO.Directory]::Exists($SourceDirectory)) { throw [System.IO.FileNotFoundException] "source directory $SourceDirectory for zipping does NOT exist";}
    $fi = new-object System.IO.FileInfo $ZipFile
    if (!$fi.Directory.Exists) { throw [System.IO.FileNotFoundException] "ZipFile path $($fi.Directory.FullName) does NOT exist"; }
    $di = new-object System.IO.DirectoryInfo ($SourceDirectory)
    
    $sw = [System.Diagnostics.Stopwatch]::StartNew()

    #build regex
    if ($null -eq $excludeFileRegex) {
        $excludeFileRegex = @()
        $excludeFileRegex += $excludeFileList | Where-Object {$_}| ForEach-Object{ new-object System.Text.RegularExpressions.Regex $_,([System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Compiled) }
    }

    if ($null -eq $includeFileRegex) {
        $includeFileRegex = @()
        $includeFileRegex += $includeFileList | Where-Object {$_}| ForEach-Object{ new-object System.Text.RegularExpressions.Regex $_,([System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Compiled) }
    }

    $FilesZipped = 0
    #build the zip   
    if (!($WhatIf)) { 
        $FileMode = [System.IO.FileMode]::OpenOrCreate
        $ZipMode = ([System.IO.Compression.ZipArchiveMode]::Create)
        try {
            $fs = New-Object System.IO.FileStream $zipFile, $FileMode
            try
            {
                $archive = New-Object System.IO.Compression.ZipArchive $fs,$ZipMode
            } catch {
                $fs.Dispose()
                throw;
            }
        } catch {
            throw;  
        }
        
    }
    try
    {
        #handle root dir
        if (!($IgnoreRootFiles))
        {
            $RelativeRoot=""
            if ($RootDirInZip) {
                $RelativeRoot="$RootDirInZip\"
            }
            foreach ($srcFile in $di.EnumerateFiles($SearchPattern, [System.IO.SearchOption]::TopDirectoryOnly))
            {
                if (($includeFileRegex.Count -eq 0 -or ($includeFileRegex | Where-Object {$_.IsMatch($srcFile.Name)})) -and !($excludeFileRegex | Where-Object {$_.IsMatch($srcFile.Name)}))
                {
                    if ($WhatIf) {
                        Write-Host "Would add $($srcFile.FullName) -> $RelativeRoot$($srcFile.Name)"   
                    } else {
                        [void][System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive,$srcFile.FullName,"$RelativeRoot$($srcFile.Name)",$compressionLevel)
                    }
                    $FilesZipped++
                }                    
            }
        }

        if ($Recurse)
        {

            $DirList = Get-DirectoriesWithExclusion -SourceDirectory $di.FullName -SearchPattern $SearchPattern -Recurse -excludeDirList $excludeDirList -excludeDirRegex $excludeDirRegex -includeDirList $includeDirList -includeDirRegex $includeDirRegex 
            #handle child dirs
            foreach ($srcDir in $DirList)
            {
                Write-Verbose "Evaluating $($srcDir.FullName) for files"
                $RelativeRoot = Get-RelativePath -Directory -Path $srcDir.FullName -BasePath $SourceDirectory
                if ($RootDirInZip) {
                    $RelativeRoot = "$RootDirInZip$RelativeRoot\"
                }

                foreach ($srcFile in $srcDir.EnumerateFiles($SearchPattern,[System.IO.SearchOption]::TopDirectoryOnly))
                {
                    if (($includeFileRegex.Count -eq 0 -or ($includeFileRegex | Where-Object {$_.IsMatch($srcFile.Name)})) -and !($excludeFileRegex | Where-Object {$_.IsMatch($srcFile.Name)}))
                    {
                        if ($WhatIf) {
                            Write-Host "Would add $($srcFile.FullName) -> $RelativeRoot$($srcFile.Name)"   
                        } else {
                            [void][System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive,$srcFile.FullName,"$RelativeRoot$($srcFile.Name)",$compressionLevel)
                        }
                        
                    }   
                    $FilesZipped++                 
                }
                
            }
        }
        if ($WhatIf) {
            Write-Host "Found $FilesZipped files to add to $ZipFile"
        } else {
            $fs.Flush()
            $sw.Stop()
            if ($FilesZipped -eq 0) {
                Write-Warning "No files found to include!"
            } else {
                Write-Verbose "Added $FilesZipped files to $ZipFile, taking $($sw.Elapsed)"
            }
        }
    } finally {
        if (!($WhatIf)) { 
            $archive.Dispose()
            $fs.Dispose()
        }
    }
}


Compress-Directory -SourceDirectory "E:\PowershellModules" -ZipFile E:\PowershellModules\test.zip -IgnoreRootFiles -RootDirInZip "Modules" -excludeFileList "log$" -includeDirList 'common' -excludeDirList "badmodule" -includeFileList "psm1$"  -Verbose

之后删除它们:

var folderToZip = "folderName";
var file = "zipfileName";

using (ZipFile zip = new ZipFile())
{
    zip.AddDirectory(folderToZip);
    MemoryStream output = new MemoryStream();
            
    zip.RemoveSelectedEntries("FolderToExclude/*"); //Will remove all files and folder
    zip.RemoveSelectedEntries("OtherFolderToExclude/*");

    zip.Save(output);
    
    File(output.ToArray(), "application/zip", file);
}