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

时间:2016-02-15 17:54:50

标签: c# zipfile

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

这是我最初想要使用的内容:

ZipFile.CreateFromDirectory(folderToZip, backupFileName);

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

非常感谢任何帮助!

4 个答案:

答案 0 :(得分:10)

不幸的是,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\")
);

答案 1 :(得分:1)

在我看来,你可以把文件夹中的每个文件都排除在&#34; backups&#34;之外。文件夹并用它们制作一个临时文件夹来制作你所说的拉链。

使用此LINQ查询,您可以以一种非常简单的方式执行此操作:

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

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

答案 2 :(得分:0)

您可以为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 );
     }
}

答案 3 :(得分:0)

就我而言,我需要在 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