Powershell 中的原生 .tar 提取

Native .tar extraction in Powershell

我有一个 .tar.gz 文件需要提取。我已经使用 System.IO.Compression 中的 GzipStream 对象处理了 gunzip 位,但我找不到任何用于处理该命名空间中的 tarball 的内容。有没有办法在 Powershell 中本地处理 .tar 文件?请注意,唯一重要的是我能够从 Powershell 脚本 调用 任何此类 function/method/object construction/system 二进制文件;它实际上不需要用 powershell 编写。 (如果重要的话,我使用的是 64 位 windows 10)

P.S。请不要说"use 7zip";那不是本地人

所以我问这个问题已经十一天了,普遍的共识是:"No, there are no native tools in a vanilla window install that can handle tar extraction 'for you'"。

这个答案来自 Matthias R. Jensen and TessellatingHeckler,他们都拒绝在评论之外回答(我怀疑是因为不想在不了解整个 Windows 系统的情况下说 "no"架构,这是公平的)。

当然可以安装脚本和 类 和程序,但没有 "native"。

这个代码段对我有用,我认为我没有安装它正在使用的任何东西。我仍然需要弄清楚如何提取这个拉出的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 

可能的解决方法:

我有 Windows 10,我想在 Powershell 中 运行 curl+tar 但它缺乏支持。幸运的是,我有 Ubuntu bash on Windows 10 预装了 curltar 并且 运行 它在那个环境中,然后在下载和解压之后,我切换回 Powershell 继续我的工作正在做。

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

这些变通办法不能解决您的特定问题,但对于那些卡在 Windows 中的问题可能会有用。

这是 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实用程序

的代码

2019年更新:

正如其他答案中指出的那样,2018 年 BSD tar was added to Windows 10 as a built-in command。如果您需要支持早期版本的 Windows,此答案仍然适用。


我相当确定 Windows 10 在 2018 年之前默认不支持提取 tar 文件。

1。 Windows 10

(早期版本)的快速简单解决方案

如果您希望脚本能够访问活动的 Internet 连接,那么您可以使用内置包管理器简单地获得 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
  • 的早期版本

一个。下载 7Zip4PowerShell 包的副本

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

Save-Module -Name 7Zip4Powershell -Path .

这会将其下载到当前目录。在 Windows 10 上执行此操作最简单,但上面的 link 中有关于如何在早期版本的 Windows 上将 PowerShell Gallery 添加到系统的说明。

b。需要提取tar文件时导入模块

我做了一个简单的函数来演示如何基于第一个解决方案来做到这一点:

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

    $pathToModule = ".Zip4Powershell.9.0Zip4PowerShell.psd1"

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

    Expand-7Zip $tarFile $dest
}

您需要将 $pathToModule 调整为脚本运行时捆绑模块的实际存储位置,但您应该明白了。我已经编写了这个示例,这样您就可以简单地将上面的两个代码块粘贴到 PowerShell window 中并获得一个有效的 Expand-Tar cmdlet:

Expand-Tar archive.tar dest

Windows 10 自 2017 年以来已经 tar 内置,所以可能 运行 它像往常一样 exepowershell 喜欢 tar -xkf $archivePath -C $outDir,或者pwsh.

更好

我相信 tar 已作为本机功能添加到 Windows 10 中,因为它已发布。

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

tar -xvzf .\whatever.tar.gz

请注意,.\ 是在 PowerShell 中使用 tab 自动完成后添加的,但我认为没有它应该也能正常工作。

此函数与其 Unix 实现之间可能存在一些根本差异(毕竟它在 Windows 上),但它对我有用。

我花了一些时间,但这是我做出的解决方案。基本上 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