压缩归档并保留相对路径

23

我正在尝试使用compress-archive,但遇到了一些挑战...

我有一个根项目文件夹,我想将其中一些子目录下的文件进行压缩,并保留相对路径。例如: / ├── _scripts ├── ├─_module1 | | └── filex.js | └─_module2 | ├── file1.js | └── file2.txt

因此,我想从我的根目录创建一个zip文件,其中包含module2/*,并且我希望保留文件夹结构。 我想让我的zip包含: scripts/module2/file1.js scripts/module2/file2.txt

但是当我从根目录运行以下命令时: Compress-Archive -Path "scripts\module2\*" -DestinationPath tmp.zip

Zip文件的内容只包含: /file1.js /file2.txt

4个回答

15

看起来Compress-Archive(截至Windows PowerShell v5.1)不支持您想要的功能:

递归地针对一个文件夹添加该文件夹的子树到归档文件中,但仅使用目标文件夹的名称(成为归档文件内的子文件夹),而不是其路径

具体来说,

Compress-Archive -Path scripts\module2 -DestinationPath tmp.zip

scripts\module2的内容将被递归地存储在tmp.zip中,但不会带有归档内部路径.\scripts\module2,仅使用.\module2 - 目标文件夹的名称(即输入路径的最后一个组成部分)。

这意味着您需要传递文件夹scripts以获得所需的归档内部路径,但考虑到Compress-Archive没有包含/排除机制,这将不可避免地包括整个scripts子树。


一种繁琐的选择是在例如$env:TEMP文件夹中重新创建所需的层次结构,将目标文件夹复制到那里,在重建层次结构的根处运行Compress-Archive,然后清理:

New-Item -Force -ItemType Directory $env:TEMP/scripts
Copy-Item -Recurse -Force scripts/module2 $env:TEMP/scripts
Compress-Archive -LiteralPath $env:TEMP/scripts -DestinationPath tmp.zip
Remove-Item $env:TEMP/Scripts -Recurse -Whatif

否则,您可能能够找到解决方案:

  • 直接使用 .NET v4.5+ 的 [System.IO.Compression.ZipFile];您可以使用 Add-Type -Assembly System.IO.Compression.FileSystem 将其加载到会话中(在 PowerShell Core 中不需要)。

  • 使用外部程序,例如 7-Zip


1
谢谢。我最终做的就是创建一个临时文件夹,将文件复制到其中以便将它们放置于正确的路径中。然后将该临时文件夹压缩起来。 - Dave Baghdanov

6

我希望不必将整个结构复制到临时目录中就能完成此操作。

#build list of files to compress
$files = @(Get-ChildItem -Path .\procedimentos -Recurse | Where-Object -Property Name -EQ procedimentos.xlsx);
$files += @(Get-ChildItem -Path .\procedimentos -Recurse | Where-Object -Property Name -CLike procedimento_*_fs_*_d_*.xml);
$files += @(Get-ChildItem -Path .\procedimentos -Recurse | Where-Object -Property FullName -CLike *\documentos_*_fs_*_d_*);

# exclude directory entries and generate fullpath list
$filesFullPath = $files | Where-Object -Property Attributes -CContains Archive | ForEach-Object -Process {Write-Output -InputObject $_.FullName}

#create zip file
$zipFileName = 'procedimentos.zip'
$zip = [System.IO.Compression.ZipFile]::Open((Join-Path -Path $(Resolve-Path -Path ".") -ChildPath $zipFileName), [System.IO.Compression.ZipArchiveMode]::Create)

#write entries with relative paths as names
foreach ($fname in $filesFullPath) {
    $rname = $(Resolve-Path -Path $fname -Relative) -replace '\.\\',''
    echo $rname
    $zentry = $zip.CreateEntry($rname)
    $zentryWriter = New-Object -TypeName System.IO.BinaryWriter $zentry.Open()
    $zentryWriter.Write([System.IO.File]::ReadAllBytes($fname))
    $zentryWriter.Flush()
    $zentryWriter.Close()
}

# clean up
Get-Variable -exclude Runspace | Where-Object {$_.Value -is [System.IDisposable]} | Foreach-Object {$_.Value.Dispose(); Remove-Variable $_.Name};

1
我同意这个过程 - 避免使用临时文件更快,保留相对路径更好。我写了一个类似的脚本,加入了一些错误处理来报告进度和跳过的文件,并始终Dispose() ZipFile对象以使其可读。如果运行空间中存在其他非ZipFileIDisposable变量,则您的“清理”部分可能会产生一些副作用。 - Rich Moss
如果有人能为此创建一个命令行工具就太好了。我有Linux背景,基本上我正在寻找的是类似于“tar czf”的东西。关于清理:是的,我是PowerShell的初学者,还不太了解它。感谢您的建议。 - Rui Fartaria
这个答案中的两个函数可能是你正在寻找的。 - Rich Moss
我会在这行代码 $rname = $(Resolve-Path -Path $fname -Relative) -replace '^\.\\','' 中添加 ^,以避免像这样的路径出现错误结果:.\test.\test.txt - Rich Moss

3

这是一个有点老的线程,但我认为这将帮助人们通过 PowerShell 5.1 创建 zip 文件,在当今的 Windows 10 安装中,这已经是标准了。脚本允许您保持原始子目录结构,并排除一些不必要的子树 / 文件。这就是我用来归档我的 Visual Studio 解决方案源代码的方法:

Write-Output "Zipping Visual Studio solution..."

# top level from where to start and location of the zip file
$path = "C:\TheSolution"
# top path that we want to keep in the source code zip file
$subdir = "source\TheSolution"
# location of the zip file
$ZipFile = "${path}\TheSolution.zip"

# change current directory
Set-Location "$path"

# collecting list of files that we want to archive excluding those that we don't want to preserve
$Files  = @(Get-ChildItem "${subdir}" -Recurse -File | Where-Object {$_.PSParentPath -inotmatch "x64|packages|.vs|Win32"})
$Files += @(Get-ChildItem "${subdir}\packages" -Recurse -File)
$Files += @(Get-ChildItem "${subdir}\.git" -Recurse -File)
$FullFilenames = $files | ForEach-Object -Process {Write-Output -InputObject $_.FullName}

# remove old zip file
if (Test-Path $ZipFile) { Remove-Item $ZipFile -ErrorAction Stop }

#create zip file
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::Open(($ZipFile), [System.IO.Compression.ZipArchiveMode]::Create)

# write entries with relative paths as names
foreach ($fname in $FullFilenames) {
    $rname = $(Resolve-Path -Path $fname -Relative) -replace '\.\\',''
    Write-Output $rname
    $zentry = $zip.CreateEntry($rname)
    $zentryWriter = New-Object -TypeName System.IO.BinaryWriter $zentry.Open()
    $zentryWriter.Write([System.IO.File]::ReadAllBytes($fname))
    $zentryWriter.Flush()
    $zentryWriter.Close()
}

# release zip file
$zip.Dispose()

1

mklement0提到的繁琐技巧对我很有用。下面是我创建的脚本,支持混合了文件和文件夹的各种列表。

# Compress LFS based files into a zip
# To use
#  1. place this script in the root folder
#  2. modify the contents of $lfsAssetFiles to point to files relative to this root folder
#  3. modify $zipDestination to be where you want the resultant zip to be placed
# based off of https://dev59.com/21UK5IYBdhLWcg3wuB14#51394271

# this should match files being .gitignored
$lfsAssetFiles = 
"\Assets\Project\Plugins\x32",
"\Assets\Project\Plugins\x64\HugePlugin.dll"

# This is where the contents of the zip file will be structured, because placing them inside of a specific folder of the zip is difficult otherwise
$zipStruct = $PSScriptRoot + "\zipStruct"

# the actual zip file that will be created
$zipDestination = "C:\Dropbox\GitLfsZip\ProjectNameLfs.zip"

# remove files from previous runs of this script
If(Test-path $zipStruct) {Remove-item $zipStruct -Recurse}
If(Test-path $zipDestination) {Remove-item $zipDestination}

Foreach ($entry in $lfsAssetFiles)
{
  # form absolute path to source each file to be included in the zip
  $sourcePath = $PSScriptRoot + $entry;

  # get the parent directories of the path. If the entry itself is a directory, we still only need the parent as the directory will be created when it is copied over.
  $entryPath = Split-Path -Parent $entry

  # form what the path will look like in the destination
  $entryPath = $zipStruct + $entryPath

  # ensure the folders to the entry path exist
  $createdPath = New-Item -Force -ItemType Directory $entryPath

  # copy the file or directory
  Copy-Item -Recurse -Force $sourcePath $createdPath
}

# create a zip file https://blogs.technet.microsoft.com/heyscriptingguy/2015/page/59/
Add-Type -AssemblyName "system.io.compression.filesystem"
[io.compression.zipfile]::CreateFromDirectory($zipStruct, $zipDestination)
# Compress-Archive doesn't work here because it includes the "zipStruct" folder: Compress-Archive -Path $zipStruct -DestinationPath $zipDestination

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