使用System.IO.Compression强制替换现有文件来提取文件?

40
我正在使用以下代码从文件夹中提取所有文件。

我正在使用以下代码从文件夹中提取所有文件

        using (ZipArchive archive = new ZipArchive(zipStream))
        {
            archive.ExtractToDirectory(location);
        }

但是,如果一个文件已经存在,则会引发异常。有没有办法告诉压缩API替换现有的文件。

我发现一种方法是先获取所有文件名,然后检查文件是否存在并将其删除。但这对我来说有点昂贵。

12个回答

64

我已经创建了一个扩展程序。欢迎提出任何意见以改进它,谢谢!

public static class ZipArchiveExtensions
{
    public static void ExtractToDirectory(this ZipArchive archive, string destinationDirectoryName, bool overwrite)
    {
        if (!overwrite)
        {
            archive.ExtractToDirectory(destinationDirectoryName);
            return;
        }

        DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
        string destinationDirectoryFullPath = di.FullName;

        foreach (ZipArchiveEntry file in archive.Entries)
        {
            string completeFileName = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, file.FullName));

            if (!completeFileName.StartsWith(destinationDirectoryFullPath, StringComparison.OrdinalIgnoreCase))
            {
                throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability");
            }

            if (file.Name == "")
            {// Assuming Empty for Directory
                Directory.CreateDirectory(Path.GetDirectoryName(completeFileName));
                continue;
            }
            file.ExtractToFile(completeFileName, true);
        }
    }
}

1
使用ZipFile内置类是否可以实现这个功能? - Pedro77

23

当文件夹不存在时,这段代码不会抛出异常,而是会创建该文件夹。

public static class ZipArchiveExtensions
{
    public static void ExtractToDirectory(this ZipArchive archive, string destinationDirectoryName, bool overwrite)
    {
        if (!overwrite)
        {
            archive.ExtractToDirectory(destinationDirectoryName);
            return;
        }
        foreach (ZipArchiveEntry file in archive.Entries)
        {
            string completeFileName = Path.Combine(destinationDirectoryName, file.FullName);
            string directory = Path.GetDirectoryName(completeFileName);

            if (!Directory.Exists(directory))
                Directory.CreateDirectory(directory);

            if (file.Name != "")
                file.ExtractToFile(completeFileName, true);
        }
    }
}

2
JABFreeware,正如@user960567所说“任何改进的评论都将不胜感激”,我只是添加了几行代码以防止抛出异常! 如果(!Directory.Exists(directory)) Directory.CreateDirectory(directory); - Mohy66
3
这个方案比被采纳的答案更为强大。谢谢。 - Chris Fremgen
Directory.CreateDirectory 已经检查了文件夹是否存在并且不会抛出异常。现在你却进行了两次检查。 - HackSlash

7

从 .NET Standard 2.1 开始,在以下语句中将 overwriteFiles 设置为 true 即可轻松实现:ZipFile.ExtractToDirectory(string sourceFile, string destDir, Encoding entryNameEncoding, bool overwriteFiles)

Example:
ZipFile.ExtractToDirectory("c:\\file.zip","c:\\destination_folder", Encoding.UTF8, true);

最好用简短的话解释答案是如何解决问题的 :) - Not a bug
2
据我所知,ExtractToDictionary方法不接受布尔值以允许覆盖。 - sxeros
那比其他所有答案都容易多了。 - Sean H. Worthington
@sxeros .NET Standard 2.1 版本中提供了使用布尔值的重载,建议在答案中加以说明。 - jsabrooke
5
只有在.NET Core上才能使用,而在.NET Framework上不行,供您参考。 - Scott
这是所有答案中最好的一个。 - Shuvo Amin

7

6

作为一个完全的Linq粉丝,以下是类似Linq的方式供参考:

using (var strm = File.OpenRead(zipPath))
using (ZipArchive a = new ZipArchive(strm))
{
    a.Entries.Where(o => o.Name == string.Empty && !Directory.Exists(Path.Combine(basePath, o.FullName))).ToList().ForEach(o => Directory.CreateDirectory(Path.Combine(basePath, o.FullName)));
    a.Entries.Where(o => o.Name != string.Empty).ToList().ForEach(e => e.ExtractToFile(Path.Combine(basePath, e.FullName), true));
}

5
你可以将文件提取到某个临时目录,然后使用 "File.Copy" 并将覆盖选项设置为 true 将文件复制到目标目录。我知道这不是一个完美的解决方案,但这样做你就不需要检查文件是否存在。

1
我也有这个想法,但对我来说成本太高了。 - Imran Qadir Baksh - Baloch
我认为除了使用其他框架,你没有其他选择。 - Gregory Nozik

5
这是一个接收zip文件路径的方法。 基于已被接受的答案。
    public void ExtractZipFileToDirectory(string sourceZipFilePath, string destinationDirectoryName, bool overwrite)
    {
        using (var archive = ZipFile.Open(sourceZipFilePath, ZipArchiveMode.Read))
        {
            if (!overwrite)
            {
                archive.ExtractToDirectory(destinationDirectoryName);
                return;
            }

            DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
            string destinationDirectoryFullPath = di.FullName;

            foreach (ZipArchiveEntry file in archive.Entries)
            {
                string completeFileName = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, file.FullName));

                if (!completeFileName.StartsWith(destinationDirectoryFullPath, StringComparison.OrdinalIgnoreCase))
                {
                    throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability");
                }

                if (file.Name == "")
                {// Assuming Empty for Directory
                    Directory.CreateDirectory(Path.GetDirectoryName(completeFileName));
                    continue;
                }
                file.ExtractToFile(completeFileName, true);
            }
        }
    }

1
我注意到的是,这将保留原始文件的创建时间。您可以在 file.ExtractToFile(completeFileName, true); 之后添加 File.SetCreationTimeUtc(completeFileName, file.LastWriteTime.UtcDateTime); 来解决这个问题。 - mrexodia
1
还有一个错误,即嵌套目录未正确创建。添加 Directory.CreateDirectory(Path.GetDirectoryName(completeFileName)); 可以解决此问题。 - mrexodia

2

你好,我正在使用从NuGet下载的DotNetZip。 我只是简单地使用了这段代码。 如果存在,它将自动替换目录中的文件。 "OverwriteSilently"!

using (ZipFile archive = new ZipFile(@"" + System.Environment.CurrentDirectory + "\\thezipfile.zip"))
{
     archive.ExtractAll(@"" + System.Environment.CurrentDirectory, ExtractExistingFileAction.OverwriteSilently);
}

1

当你有zip文件路径时,这很有用。

public static class ZipArchiveHelper
    {
        public static void ExtractToDirectory(string archiveFileName, string destinationDirectoryName, bool overwrite)
        {
            if (!overwrite)
            {
                ZipFile.ExtractToDirectory(archiveFileName, destinationDirectoryName);
            }
            else
            {
                using (var archive = ZipFile.OpenRead(archiveFileName))
                {

                    foreach (var file in archive.Entries)
                    {
                        var completeFileName = Path.Combine(destinationDirectoryName, file.FullName);
                        var directory = Path.GetDirectoryName(completeFileName);

                        if (!Directory.Exists(directory) && !string.IsNullOrEmpty(directory))
                            Directory.CreateDirectory(directory);

                        if (file.Name != "")
                            file.ExtractToFile(completeFileName, true);
                    }

                }
            }            
        }
    }

0

稍微修改了一下创建所有文件夹的答案中的方法

public static void ExtractToDirectory(this ZipArchive archive, string destinationDirectoryName, bool overwrite)
{
    if (!overwrite)
    {
        archive.ExtractToDirectory(destinationDirectoryName);
        return;
    }
    foreach (ZipArchiveEntry file in archive.Entries)
    {
        string completeFileName = Path.Combine(destinationDirectoryName, file.FullName);
        if (file.Name == "")
        {// Assuming Empty for Directory
            Directory.CreateDirectory(Path.GetDirectoryName(completeFileName));
            continue;
        }
        // create dirs
        var dirToCreate = destinationDirectoryName;
        for (var i = 0; i < file.FullName.Split('/').Length - 1; i++)
        {
            var s = file.FullName.Split('/')[i];
            dirToCreate = Path.Combine(dirToCreate, s);
            if (!Directory.Exists(dirToCreate))
                Directory.CreateDirectory(dirToCreate);
        }
        file.ExtractToFile(completeFileName, true);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接