【问题标题】:Fast and simple binary concatenate files in PowershellPowershell 中快速简单的二进制连接文件
【发布时间】:2014-04-20 01:47:27
【问题描述】:

使用 Powershell 连接二进制文件的最佳方法是什么? 我更喜欢一种简单易记且执行速度快的单行代码。

我想到的最好的是:

gc -Encoding Byte -Path ".\File1.bin",".\File2.bin" | sc -Encoding Byte new.bin

这似乎工作正常,但对于大文件来说非常慢。

【问题讨论】:

    标签: powershell


    【解决方案1】:

    您采用的方法是我在 PowerShell 中采用的方法。但是,您应该使用 -ReadCount 参数来提高性能。您还可以利用位置参数来进一步缩短它:

    gc File1.bin,File2.bin -Encoding Byte -Read 512 | sc new.bin -Encoding Byte
    

    编者注:在跨平台的PowerShell (Core) 版本(版本6 及以上)中,现在必须使用-AsByteStream 而不是-Encoding Byte;此外,Set-Content cmdlet 的 sc 别名已被删除。

    关于 -ReadCount 参数的使用,我不久前写了一篇博文,大家可能会觉得有用 - Optimizing Performance of Get Content for Large Files

    【讨论】:

    • 我刚刚在我的示例文件上运行了它,并且包含 -read 参数的命令从 9 分钟缩短到 3 秒。这是在 x25m 驱动器上。好的。你得到我的接受。
    • 刚刚用你的单行加入了一个跨越 23 个文件的 4.4gb iso。很好地重新组装了文件,并在我的笔记本电脑上使用 1024 字节块花费了 35 分钟。
    • 我猜这是因为管道正在向 sc 发送 .net 对象?当我尝试将二进制数据通过管道传输到 c 程序时,我注意到我只得到了每个字节的前 7 位,因为“|”是调用编码。
    • 不再适用于 PowerShell 6/7。字节不是可接受的编码。 Get-Content: Cannot process argument transformation on parameter 'Encoding'. 'Byte' is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method. (Parameter 'name')
    【解决方案2】:

    它不是 Powershell,但如果你有 Powershell,你也有命令提示符:

    copy /b 1.bin+2.bin 3.bin
    

    正如 Keith Hill 指出的,如果你真的需要从 Powershell 内部运行它,你可以使用:

    cmd /c copy /b 1.bin+2.bin 3.bin 
    

    【讨论】:

    • copy 是 cmd.exe 中的固有命令。您必须执行 cmd /c copy /b 1.bin+2.bin 3.bin
    • 不错的简单解决方案,适用于任何 Windows 计算机。赞成但接受基思,因为我要求提供 PS 版本。谢谢
    • 还要注意copy 支持通配符。所以copy /b *.bin out.bin 将连接你所有的 bin 文件,输出会非常快(即比使用 PowerShell 快得多)。
    • 谢谢...它比公认的答案快十亿倍;)。尝试从 PowerShell 运行它时,我错过了“cmd /c”。有时旧方法仍然是最好的。
    【解决方案3】:

    我最近遇到了类似的问题,我想将两个大 (2GB) 文件附加到一个文件 (4GB) 中。

    我尝试调整 Get-Content 的 -ReadCount 参数,但无法通过它来提高我对大文件的性能。

    我采用了以下解决方案:

    function Join-File (
        [parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [string[]] $Path,
        [parameter(Position=1,Mandatory=$true)]
        [string] $Destination
    )
    {
        write-verbose "Join-File: Open Destination1 $Destination"
        $OutFile = [System.IO.File]::Create($Destination)
        foreach ( $File in $Path ) {
            write-verbose "   Join-File: Open Source $File"
            $InFile = [System.IO.File]::OpenRead($File)
            $InFile.CopyTo($OutFile)
            $InFile.Dispose()
        }
        $OutFile.Dispose()
        write-verbose "Join-File: finished"
    } 
    

    性能:

    • cmd.exe /c copy file1+file2 File3 大约 5 秒(最佳)
    • gc file1,file2 |sc file3 大约 1100 秒(糟糕)
    • join-file File1,File2 File3 大约 16 秒(OK)

    【讨论】:

    • cmd.exe 复制速度比原生 PS cmdlet 快很多倍 - 1.2MB/s 与 >120Mb/s。考虑到即使使用 -ReadCound 参数,Get-Content 的工作方式也不足为奇
    【解决方案4】:

    性能很大程度上取决于所使用的缓冲区大小。默认情况下,它们相当小。连接 2x2GB 文件我会占用大约 256kb 的缓冲区。变大有时可能会失败,变小可能会导致吞吐量低于驱动器的能力。

    gc 将与 -ReadCount 一起使用,而不仅仅是 -Read (PowerShell 5.0):

    gc -ReadCount 256KB -Path $infile -Encoding Byte | ...
    

    另外,我发现 Add-Content 更好,并且可以逐个文件处理大量小文件,因为只传输适量的数据 (200MB) .

    虽然Add-Content 随机失败了几次,因为几百个文件出现了关于目标文件正在使用的错误,所以我添加了一个while循环和一个try catch:

    # Empty the file first
    sc -Path "$path\video.ts" -Value @() -Encoding Byte 
    $tsfiles | foreach {    
        while ($true) {
            try { # I had -ReadCount 0 because the files are smaller than 256KB
                gc -ReadCount 0 -Path "$path\$_" -Encoding Byte | `
                    Add-Content -Path "$path\video.ts" -Encoding Byte -ErrorAction Stop
                break;
            } catch {
            }
        }
    }
    

    使用文件流仍然更快。您不能使用 [System.IO.File]::Open 指定缓冲区大小,但可以使用 new [System.IO.FileStream] 如下所示:

    # $path = "C:\"
    $ins = @("a.ts", "b.ts")
    $outfile = "$path\out.mp4"
    $out = New-Object -TypeName "System.IO.FileStream" -ArgumentList @(
        $outfile, 
        [System.IO.FileMode]::Create,
        [System.IO.FileAccess]::Write,
        [System.IO.FileShare]::None,
        256KB,
        [System.IO.FileOptions]::None)
    try {
        foreach ($in in $ins) {
            $fs = New-Object -TypeName "System.IO.FileStream" -ArgumentList @(
                "$path\$in", 
                [System.IO.FileMode]::Open,
                [System.IO.FileAccess]::Read,
                [System.IO.FileShare]::Read,
                256KB,
                [System.IO.FileOptions]::SequentialScan)
            try {
                $fs.CopyTo($out)
            } finally {
                $fs.Dispose()
            }
        }
    } finally {
        $out.Dispose()
    }
    

    【讨论】:

    • 有人猜测这是cmd.exe复制命令使用的一种非常相似的方法
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-23
    • 1970-01-01
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    相关资源
    最近更新 更多