在Powershell中进行本地.tar文件的解压缩

68
我有一个需要解压的.tar.gz文件。我已经使用System.IO.Compression中的GzipStream对象处理了gunzip部分,但我在那个命名空间中找不到处理tarball的任何东西。是否有一种方法可以在Powershell中原生地处理.tar文件?请注意,重要的是我能够从Powershell脚本中“调用”任何此类函数/方法/对象构造/系统二进制文件;它不需要实际编写在powershell中。(如果这很重要,我正在使用64位的Windows 10)

P.S. 请不要说“使用7zip”; 那不是本地的


有这个必要的原因吗?我问这个问题是因为我也找不到答案。 - kayleeFrye_onDeck
也许吧,但我不这么认为,我也不指望它会出现。Gzip被HTTP压缩使用,至少ASP.Net、Invoke-WebRequest和IE/Edge需要支持它,但在Windows生态系统中基于“tar”的是什么? - TessellatingHeckler
1
你要么得在PowerShell中编写一个tar解析器,要么导入第三方库,比如SharpCompress或SharpZipLib。 - Mathias R. Jessen
1
@kayleeFrye_onDeck 我试图在Windows 10电脑上安装gnu Make和gnu FindUtils,以替换慢速的gulp任务为快速的make recipes(Windows“FIND”对于习惯了gnu版本的人来说无法使用,因此需要findutils)。这些的源仅作为tarballs或sourceforge上的zip文件可用,但是sourceforge不支持静态文件托管,因此我无法从那里下载,除非使用像cURL这样的高级工具(在Windows的原始安装中不可用)。希望这能帮助你解决任何你需要的问题。 - bfieck
1
截至2018年1月,Windows已经包含了tarcurl。最初这只对Windows Insiders可用,但我相信现在所有的Windows 10用户都可以使用了(https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/)。它的工作方式就像你期望的那样,但我还是发了一个答案以防万一。 - Broper
显示剩余3条评论
8个回答

78

我相信自从本篇文章发布以来,tar已经成为Windows 10中的本地功能。

在Windows 10的命令提示符或PowerShell中,我可以运行以下命令:

tar -xvzf .\whatever.tar.gz

注意,在PowerShell中使用tab自动补全后添加了.\,但我认为即使没有它也应该能正常工作。

由于这是在Windows上执行,所以这个函数与其Unix实现之间可能存在一些基本差异,但对我而言它是有效的。


1
这似乎是对我来说最好、最简单的解决方案!谢谢 - SPYBUG96
5
这很有效!我有多个tar.gz文件在同一个文件夹中,我使用了这个命令:gci *.tar.gz | ForEach-Object {tar -xvzf $_} 来解压缩它们。它也为每个tar文件创建了一个单独的文件夹。顺便说一下,这是在运行Powershell v.5.1的Windows Server 2019 Standard上完成的。 - senpai
1
Tar和Curl来到Windows! - derekbaker783
这是来自libarchive.org的BSD tar。我认为它和任何UNIX tar一样好。 - caoanan

28

2019年更新:

正如其他答案所指出的,BSD tar 已于 2018 年作为内置命令添加到 Windows 10 中。如果您需要支持早期版本的 Windows,则本答案仍然适用。


我相当确定在 2018 年之前的 Windows 10 中默认没有支持提取 tar 文件的功能。

1.(早期版本的)Windows 10 的快速简单解决方案

如果您可以确保脚本可以访问活动的互联网连接,那么您可以通过使用内置的软件包管理器来轻松获取 7Zip 支持。这个解决方案:

  • 通过脚本完全自动化(不需要用户交互)
  • 不需要管理员权限
  • 仅在 7Zip 支持模块不存在时才安装该模块

这就是它:

function Expand-Tar($tarFile, $dest) {

    if (-not (Get-Command Expand-7Zip -ErrorAction Ignore)) {
        Install-Package -Scope CurrentUser -Force 7Zip4PowerShell > $null
    }

    Expand-7Zip $tarFile $dest
}

如何使用:

Expand-Tar archive.tar dest

虽然它可以完成任务,但是这会对客户系统进行持久性更改。有一个更好的解决方案,但涉及到的步骤稍微多一些:

2. 更好的解决方案

更好的解决方案是将 7Zip4PowerShell 与您的脚本捆绑在一起。此解决方案具有以下优点:

  • 完全通过脚本自动化(不需要用户交互)
  • 不需要管理员权限
  • 不会在客户端系统上安装任何软件
  • 不需要互联网连接
  • 应该适用于早期版本的Windows

a. 下载7Zip4PowerShell软件包副本

它根据LGPL-2.1许可证分发,因此将软件包与您的产品捆绑在一起是可以的。您应该在文档中添加一条注释说明它使用了该软件包并提供一个链接,因为您需要为GNU许可证产品提供访问源代码的权限。

Save-Module -Name 7Zip4Powershell -Path .

这将会下载文件至当前目录。在Windows 10上执行此操作最为简单,但你可以通过上方链接中提供的说明,在早期版本的Windows中添加PowerShell Gallery到系统中。

b. 当你需要解压缩tar文件时导入该模块

我创建了一个简单的函数,只是为了演示如何基于第一个解决方案进行操作:

function Expand-Tar($tarFile, $dest) {

    $pathToModule = ".\7Zip4Powershell\1.9.0\7Zip4PowerShell.psd1"

    if (-not (Get-Command Expand-7Zip -ErrorAction Ignore)) {
        Import-Module $pathToModule
    }

    Expand-7Zip $tarFile $dest
}

您需要调整$pathToModule的路径,以使其指向当您运行脚本时模块实际存储的位置,但是您应该已经明白了。我编写了这个示例,使您可以将上面两个代码块简单地粘贴到 PowerShell 窗口中,并得到一个可用的Expand-Tar cmdlet:

Expand-Tar archive.tar dest

1
不错的帮手!需要注意的是,7Zip4Powershell的1.9.0版本已经发布。您可以更改$pathToModule或在Save-Module命令中添加-RequiredVersion '1.8.0' - Aidan Ryan
@AidanRyan 谢谢 - 我已经更新到1.9.0,因为我看不到任何简单的解决方法,并且建议使用早期版本感觉不对。 - Don Cruickshank
2
我相信在2018年初左右,Windows 10添加了对tarcurl的实现(https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/)。它的工作方式类似于Unix命令,但我也发布了一个答案。 - Broper
@Broper 谢谢 - 我已经在我的答案中添加了一条注释。 - Don Cruickshank
1
特别构建的17063已经加入。 - Slate

6

可能的解决方法:

我使用的是Windows 10,并且我想在PowerShell中运行curl+tar,但它不支持。幸运的是,我有Ubuntu Bash on Windows 10,它预装了curltar并在该环境下运行。下载和提取后,在切换回PowerShell继续其他操作。

或者如@Don Cruickshank在下面的评论中提到的那样,您可以直接在PowerShell中安装7zip支持,使用Install-Package 7Zip4Powershell命令。 Expand-7zip cmdlet将提取多个存档格式,包括tar。

这些解决方法不能解决您特定的问题,但对于那些被困在Windows上的用户可能会很有用。


2
在Windows 10中,您可以直接从PowerShell中使用“Install-Package 7Zip4Powershell”安装7zip支持。 “Expand-7zip” cmdlet将提取多个存档格式,包括tar。 - Don Cruickshank
@DonCruickshank 感谢您的建议。 - Kunok

4

自2017年以来,Windows 10已经内置了tar,因此您可以像使用常规的exe文件一样从powershell中运行它,例如:tar -xkf $archivePath -C $outDir,或者更好地从pwsh中运行。


2

我花了一些时间,但这是我做出的解决方案。基本上,TAR是一种非常简单的格式,但即使如此,处理它也需要一些代码行。

Function ConvertTo-ByteString
{
    Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True )][byte[]]$Buffer
    )

    # Note: Codepage 28591 returns a 1-to-1 char to byte mapping
    $Encoding = [System.Text.Encoding]::GetEncoding(28591)
    #$BinaryText = [System.Text.Encoding]::Convert('ascii', '28591', $Buffer)
    $BinaryText = [System.Text.Encoding]::ASCII.GetString($Buffer)
    $BinaryText = $BinaryText.Trim(0)
    $BinaryText = $BinaryText.Trim(32)
    $BinaryText = $BinaryText.Trim(0)
    $BinaryText = $BinaryText.Trim(32)
    return $BinaryText
}

Function New-USTARHeaderObject
{
    #Write-Host('[New-USTARHeaderObject]','Creating new header object') -ForegroundColor Cyan
    $Header = New-Object -Type PSObject
    $Header | Add-Member -MemberType NoteProperty -Name name -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name mode -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name uid -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name gid -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name size -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name mtime -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name cksum -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name typeflag -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name linkname -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name magic -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name version -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name uname -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name gname -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name devmajor -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name devminor -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name prefix -Value $null

    return $Header
   
}

Function Fill-USTARHeaderObject
{
    Param(
    [Parameter(Mandatory=$True)][PSObject]$Header,
    [Parameter(Mandatory=$True)][byte[]]$Buffer
    )
    
    #Write-Host('[Fill-USTARHeaderObject]','Filling header object with bytes') -ForegroundColor Cyan
    $Header.name = [string](ConvertTo-ByteString $Buffer[0..99])
    $Header.mode = ConvertTo-ByteString $Buffer[100..107]
    $Header.uid = ConvertTo-ByteString $Buffer[108..115]
    $Header.gid = ConvertTo-ByteString $Buffer[116..123]
    $Header.size = ConvertTo-ByteString $Buffer[124..135]
    $Header.mtime = ConvertTo-ByteString $Buffer[136..147]
    $Header.cksum = ConvertTo-ByteString $Buffer[148..155]
    $Header.typeflag = ConvertTo-ByteString $Buffer[156]
    $Header.linkname = ConvertTo-ByteString $Buffer[157..256]
    $Header.magic = ConvertTo-ByteString $Buffer[257..262]
    $Header.version = ConvertTo-ByteString $Buffer[263..264]
    $Header.uname = ConvertTo-ByteString $Buffer[265..296]
    $Header.gname = ConvertTo-ByteString $Buffer[297..328]
    $Header.devmajor = ConvertTo-ByteString $Buffer[329..336]
    $Header.devminor = ConvertTo-ByteString $Buffer[337..344]
    $Header.prefix = ConvertTo-ByteString $Buffer[345..499]
}


Function Check-IsUSTARHeaderObject
{
    Param(
    [Parameter(Mandatory=$True, Position = 0, ValueFromPipeline = $True)][PSObject]$Header
    )
    $Regex_Numeric = [regex]"^\d+$"
    #Write-Host('[Check-IsUSTARHeaderObject]','Checking if object is actual header') -ForegroundColor Cyan

    #Write-Host("[Mode    ]",$Header.mode,($Regex_Numeric.Matches($Header.mode).Success)) -ForegroundColor Magenta
    #Write-Host("[Size    ]",$Header.size,($Regex_Numeric.Matches($Header.size).Success)) -ForegroundColor Magenta
    #Write-Host("[MTime   ]",$Header.mtime,($Regex_Numeric.Matches($Header.mtime).Success)) -ForegroundColor Magenta
    #Write-Host("[CKSum   ]",$Header.cksum,($Regex_Numeric.Matches($Header.cksum).Success)) -ForegroundColor Magenta
    #Write-Host("[TypeFlag]",$Header.typeflag,($Regex_Numeric.Matches($Header.typeflag).Success)) -ForegroundColor Magenta
    #Write-Host("[Magic   ]",$Header.magic,($Header.magic -eq 'ustar')) -ForegroundColor Magenta

    if (
        ($Regex_Numeric.Matches($Header.mode).Success) -and
        ($Regex_Numeric.Matches($Header.size).Success) -and
        ($Regex_Numeric.Matches($Header.mtime).Success) -and
        ($Regex_Numeric.Matches($Header.cksum).Success) -and
        ($Regex_Numeric.Matches($Header.typeflag).Success) -and
        ($Header.magic -eq 'ustar')
    )
    {
        #Write-Host('[Check-IsUSTARHeaderObject]','TRUE') -ForegroundColor DarkCyan
        return $true
    } else {
        #Write-Host('[Check-IsUSTARHeaderObject]','FALSE') -ForegroundColor DarkCyan
        return $false
    }
}


Function UnTar-File
{
    Param(
    [Parameter(Mandatory=$True)][String]$Source,
    [Parameter(Mandatory=$False)][String]$Destination
    )
   
    #$Source = 'D:\temp\th-dfbase-blacklist.tar'
    $SourceBaseName = (Get-Item $Source).BaseName
    Write-Host('[UnTar-File]','Setting Source',$Source) -ForegroundColor Cyan

    #if destination parameter not given
    if ($Destination -eq "")
    {
        $SourcePath = Split-Path $Source
        $OutFolder = Join-Path -Path $SourcePath -ChildPath $SourceBaseName
    }
    else
    {
        $OutFolder = Join-Path -Path $Destination -ChildPath $SourceBaseName
    }

    Write-Host('[UnTar-File]','Destination Folder',$OutFolder) -ForegroundColor DarkCyan

    $FileStream = New-Object System.IO.FileStream $Source, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read)

    $BufferSize = 512
    $Buffer = New-Object byte[]($BufferSize)

    Write-Host('[UnTar-File]','Reading Source by', $BufferSize,'bytes') -ForegroundColor Cyan
    while($true)
    {
    
        $read = $FileStream.Read($Buffer, 0, $BufferSize)
        #Write-Host($read) -ForegroundColor Cyan

        if ($read -le 0){break}

        #Try Header
        $Header = New-USTARHeaderObject
        Fill-USTARHeaderObject -Header $Header -Buffer $Buffer

        #if header section
        if (Check-IsUSTARHeaderObject($Header))
        {
            $ItemPath = [IO.Path]::Combine($Header.prefix, $Header.name)
            $ItemPath = $ItemPath.Replace('/','\')

            #if folder type
            if ($Header.typeflag -eq '5')
            {
                $FolderPath = Join-Path -Path $OutFolder -ChildPath $ItemPath
                Write-Host('[UnTar-File]','Creating Folder',$FolderPath) -ForegroundColor Yellow
                New-Item -Path $FolderPath -ItemType Directory -Force | Out-Null
            }
            #if file type
            else
            {
                $FilePath = Join-Path -Path $OutFolder -ChildPath $ItemPath
                Write-Host('[UnTar-File]','Creating File',$FilePath) -ForegroundColor DarkYellow
                if ($OutputFile -ne $null)
                {
                    $OutputFile.Close()
                }
                $OutputFileInfo = New-Object PSObject
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name name -Value $Header.name
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name path -Value (Split-Path -Path $FilePath)
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name size -Value ([Convert]::ToInt32($Header.size, 8))
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name left2write -Value ([Convert]::ToInt32($Header.size, 8))
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name mtime -Value ([Convert]::ToInt32($Header.mtime, 8))
                $OutputFile = New-Object System.IO.FileStream $FilePath, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None)
            }
        }
        #if data section
        else
        {
            if ($OutputFileInfo.left2write -ge $BufferSize)
            {
                $WriteSize = $read
            } else {
                $WriteSize = $OutputFileInfo.left2write
            }
            $OutputFile.Write($Buffer, 0, $WriteSize)
            $OutputFileInfo.left2write = ($OutputFileInfo.left2write - $WriteSize)
        }
    }
    $FileStream.Close()
    if ($OutputFile -ne $null)
    {
        $OutputFile.Close()
    }
}


UnTar-File -Source $Tar

1
所以自从我问了这个问题已经过去了十一个天,普遍的共识是:“没有原生工具可以在纯净的Windows安装中为您处理tar提取。” 这个答案来自Matthias R. JensenTessellatingHeckler,他们都拒绝在评论之外回答(我怀疑是因为不想在没有全面了解Windows系统架构的情况下说“不”,这是公平的)。当然,您可以安装脚本、类和程序,但没有“本地”的东西。

1
这是一个PowerShell脚本的片段:

try
{
    Add-Type -AssemblyName "ICSharpCode.SharpZLib"

    $file = [IO.File]::OpenRead($gzArchiveName)
    $inStream=New-Object -TypeName ICSharpCode.SharpZipLib.GZip.GZipInputStream $file
    $tarIn = New-Object -TypeName ICSharpCode.SharpZipLib.Tar.TarInputStream $inStream
    $archive = [ICSharpCode.SharpZipLib.Tar.TarArchive]::CreateInputTarArchive($tarIn)
    $archive.ExtractContents($WORKDIR)
}
finally
{
    $archive.Close
}

这相当于TarTool.exe实用程序的代码。


Add-Type -AssemblyName "ICSharpCode.SharpZLib" Fails right away. Add-Type : Cannot add type. The assembly 'ICSharpCode.SharpZLib' could not be found. - Andy

1
这段代码对我很有效,我认为它没有使用任何已安装的内容。我还需要弄清楚如何提取这个tar文件。
Function DeGZip-File{
    Param(
        $infile
        )
    $outFile = $infile.Substring(0, $infile.LastIndexOfAny('.'))
    $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read)
    $output = New-Object System.IO.FileStream $outFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None)
    $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress)

    $buffer = New-Object byte[](1024)
    while($true){
        $read = $gzipstream.Read($buffer, 0, 1024)
        if ($read -le 0){break}
        $output.Write($buffer, 0, $read)
        }

    $gzipStream.Close()
    $output.Close()
    $input.Close()
}

$infile='D:\[Directory]\[File].tar.gz'

DeGZip-File $infile 

谢谢,但是就像我说的,我已经到了那个点;我已经解压缩了它并且有一个.tar文件。 - bfieck

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