从 ZIP 存档中提取单个文件

Ser*_*mos 8 powershell zip unzip system.io.compression

我正在使用下面的代码下载一些 zip 存档:

$client = new-object System.Net.WebClient
$client.DownloadFile("https://chromedriver.storage.googleapis.com/$LatestChromeRelease/chromedriver_win32.zip","D:\MyFolder.zip")
Run Code Online (Sandbox Code Playgroud)

结果我得到了包含所需文件的 ZIP 存档“MyFolder.zip”(让我们想象一下“test.txt”)。

如何将这个特定文件从 ZIP 存档提取到给定文件夹中?

zet*_*t42 10

PowerShell 4+ 有一个Expand-Archive命令,但从 PS 7.2.3 开始,它只能完全提取存档。因此,将其解压到临时目录并复制您感兴趣的文件。

如果您有 PS 5.1+ 可用,请向下滚动以获取使用 .NET 类的更高效的解决方案。

$archivePath = 'D:\MyFolder.zip'
$destinationDir = 'D:\MyFolder'

# Relative path of file in ZIP to extract. 
$fileToExtract = 'test.txt'

# Create destination dir if not exist.
$null = New-Item $destinationDir -ItemType Directory -Force

# Create a unique temporary directory
$tempDir = Join-Path ([IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString('n'))
$null = New-Item $tempDir -ItemType Directory

try {
    # Extract archive to temp dir
    Expand-Archive -LiteralPath $archivePath -DestinationPath $tempDir

    # Copy the file we are interested in
    $tempFilePath = Join-Path $tempDir $fileToExtract
    Copy-Item $tempFilePath $destinationDir 
}
finally {
    # Remove the temp dir
    if( Test-Path $tempDir ) { 
        Remove-Item $tempDir -Recurse -Force -EA Continue 
    }
}
Run Code Online (Sandbox Code Playgroud)

使用PS 5.1+,您可以使用.NET 类直接提取单个文件(无需提取整个存档):

# Load required .NET assemblies. Not necessary on PS Core 7+.
Add-Type -Assembly System.IO.Compression.FileSystem

$archivePath = 'D:\MyFolder.zip'
$destinationDir = 'D:\MyFolder'

# Relative path of file in ZIP to extract.
# Use FORWARD slashes as directory separator, e. g. 'subdir/test.txt'
$fileToExtract = 'test.txt'

# Create destination dir if not exist.
$null = New-Item $destinationDir -ItemType Directory -Force

# Convert (possibly relative) paths for safe use with .NET APIs
$resolvedArchivePath    = Convert-Path -LiteralPath $archivePath
$resolvedDestinationDir = Convert-Path -LiteralPath $destinationDir

$archive = [IO.Compression.ZipFile]::OpenRead( $resolvedArchivePath )
try {
    # Locate the desired file in the ZIP archive.
    # Replace $_.Fullname by $_.Name if file shall be found in any sub directory.
    if( $foundFile = $archive.Entries.Where({ $_.FullName -eq $fileToExtract }, 'First') ) {
    
        # Combine destination dir path and name of file in ZIP
        $destinationFile = Join-Path $resolvedDestinationDir $foundFile.Name
        
        # Extract the file.
        [IO.Compression.ZipFileExtensions]::ExtractToFile( $foundFile[ 0 ], $destinationFile )
    }
    else {
        Write-Error "File not found in ZIP: $fileToExtract"
    }
}
finally {
    # Dispose the archive so the file will be unlocked again.
    if( $archive ) {
        $archive.Dispose()
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • Convert-Path将可能是相对路径的 PowerShell 路径传递给 .NET API 时应使用。.NET 框架有自己的当前目录,该目录不一定与 PowerShell 的目录匹配。使用Convert-Path我们转换为绝对路径,因此 .NET 的当前目录不再相关。
  • .Where和是可用于所有对象的.ForEachPowerShell内部方法。它们与Where-ObjectForEach-Object命令类似,但效率更高。一旦我们找到文件,作为第二个参数传递就停止搜索'First'.Where
  • 请注意,即使只有一个元素匹配,也.Where始终输出一个集合。这与如果只有单个元素匹配则Where-Object返回单个对象相反。因此,我们必须$foundFile[ 0 ]在将其传递给 function 时编写ExtractToFile,而不仅仅是$foundFilethis 将是一个数组。

  • 使用 PS 5.1 版本时,出现错误“方法调用失败,因为 [System.IO.Compression.ZipArchive] 不包含名为“Close”的方法。” 但是当“$archive.Close()”行被删除时,它的效果很好。 (2认同)