如何在 PowerShell 中使用 tar 和 tee 进行一次读取、多次写入、原始文件副本

Posted

技术标签:

【中文标题】如何在 PowerShell 中使用 tar 和 tee 进行一次读取、多次写入、原始文件副本【英文标题】:How can I use tar and tee in PowerShell to do a read once, write many, raw file copy 【发布时间】:2022-01-07 01:19:02 【问题描述】:

我正在使用小型笔记本电脑将现场视频文件复制到多个记忆棒 (~8GB)。 复制一旦开始就必须在没有监督的情况下完成,并且必须快速。

我已经确定了速度的严重界限,即在制作多个副本时(例如,4 个棒,来自 2 个摄像头,即 8 次传输 * 8Gb),多次读取会占用大量带宽,尤其是因为摄像头是 USB2。 0 接口(两个端口),容量有限。

如果我有 unix,我可以使用 tar -cf - |三通焦油-xf /stick1 | tee 焦油 -xf /stick2 等 这意味着我只需在 USB2.0 接口上从每个摄像头提取 1 个副本 (2*8Gb) 一次。

记忆棒通常在一个USB3.0接口上的集线器上,在不同的通道上驱动,因此写入速度足够快。

由于某些原因,我无法使用当前的 Win10 PowerShell。

我目前正在将整个命令写入一个字符串(连接各种来源和各种目标),然后使用 Invoke-Process 执行复制过程,同时我正在娱乐并在拍摄后在酒吧购买回合. (因此必须 afk)。

我可以 tar cf - | tar xf 单个文件,但似乎无法使 tee 正常运行。

我还可以成功地使用 microSD 插槽做一个相机卡,它的物理性能不是很好,但在一个相机录制时速度很快,但我仍然有剩余相机的带宽问题。我们可能会同时使用 4-5 个源摄像头,这意味着一次读取、多次写入仍然是一个问题。

编辑:我刚刚升级到使用 Get-Content -raw |三通 \stick1\f1 |三通 \stick2\f1 |出空。还没有做计时或文件验证....

Edit2:看起来 Get-Content -raw 工作正常,但 PowerShell 管道的功能违反了编程的两条基本戒律:程序应该做一件事并做好,你不应该弄乱数据溪流。 由于某些未知原因,PowerShell 默认(且唯一)管道行为总是修改它应该从一个流传输到下一个流的数据流。似乎没有 -raw 选项,也没有我可以设置的 $session 或 $global 来补救残缺。

PowerShell 人员如何将原始二进制文件从一个流传输到下一个流程?

【问题讨论】:

我认为问题不在于管道,而在于Tee-Object 的编码。您使用的是哪个 PowerShell 版本(检查变量 $PSVersionTable)? 从 PowerShell 7.2 开始,外部程序的输出在进一步处理之前总是被解码为文本,这意味着原始字节输出都不能被传递通过|> 捕获 - 性能总是会受到影响。解决方法是通过cmd /c(Windows)/sh -c(类Unix平台)调用您的外部程序,并使用他们的|>运算符。见this answer。 如果您在 Windows 10 机器上安装 Linux 的 Windows 子系统 “此处显示了一组入门命令,但您可以通过以下方式为任何 Linux 命令生成包装器“ docs.microsoft.com/en-us/windows/wsl/about> @StackProtector,这绝对是管道。我可以这样做: (PS 5.1) $t = [System.Collections.ArrayList]@() foreach( $stick in $ws ) #ws 是目标驱动器的 AL $t.Add( -join( $stick, "\ " , $i) ) | Out-Null 写入输出“复制 $i” get-content -Raw -Encoding Byte -Path $s | Set-Content -Encoding Byte -Path $t Byte 将二进制转换为十进制数,例如“”=“32'r'n”,每个字符在其自己的行中。在任何环境中传输文件都非常强大,但速度却很糟糕没有-RAW,但更糟糕的是> 1Mb?刚刚锁定 @NeoTheNerd 我确实可以,但这意味着要回到旧习惯,而不是跟上新的发展。另一个因素是这台机器是一台带有 SSD 的旧笔记本电脑,而且空间非常宝贵,所以我真的希望将安装和额外的库保持在最低限度,尤其是在仅添加单个功能/cmd 时。 【参考方案1】:

可能不是您想要的(如果您坚持使用内置的 Powershell 命令),但如果您关心速度,请使用流和异步读/写。 Powershell 是一个很棒的工具,因为它可以无缝地使用任何 .NET 类。

下面的脚本可以轻松扩展为写入超过 2 个目标,并且可以处理任意流。您可能还想通过 try/catch 在那里添加一些错误处理。您也可以尝试使用各种缓冲区大小的缓冲流来优化代码。

一些参考资料:

FileStream.ReadAsync FileStream.WriteAsync CancellationToken Task.GetAwaiter

-- 2021-12-09 更新:代码稍作修改以反映 cmets 的建议。

# $InputPath, $Output1Path, $Output2Path are parameters
[Threading.CancellationTokenSource] $cancellationTokenSource = [Threading.CancellationTokenSource]::new()
[Threading.CancellationToken] $cancellationToken = $cancellationTokenSource.Token

[int] $bufferSize = 64*1024

$fileStreamIn = [IO.FileStream]::new($inputPath,[IO.FileMode]::Open,[IO.FileAccess]::Read,[IO.FileShare]::None,$bufferSize,[IO.FileOptions]::SequentialScan)
$fileStreamOut1 = [IO.FileStream]::new($output1Path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None,$bufferSize)
$fileStreamOut2 = [IO.FileStream]::new($output2Path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None,$bufferSize)

try
    [Byte[]] $bufferToWriteFrom = [byte[]]::new($bufferSize)
    [Byte[]] $bufferToReadTo = [byte[]]::new($bufferSize)
    $Time = [System.Diagnostics.Stopwatch]::StartNew()

    $bytesRead = $fileStreamIn.read($bufferToReadTo,0,$bufferSize)

    while ($bytesRead -gt 0)
        $bufferToWriteFrom,$bufferToReadTo = $bufferToReadTo,$bufferToWriteFrom    
        $writeTask1 = $fileStreamOut1.WriteAsync($bufferToWriteFrom,0,$bytesRead,$cancellationToken)
        $writeTask2 = $fileStreamOut2.WriteAsync($bufferToWriteFrom,0,$bytesRead,$cancellationToken)
        $readTask = $fileStreamIn.ReadAsync($bufferToReadTo,0,$bufferSize,$cancellationToken)
        $writeTask1.Wait()
        $writeTask2.Wait()
        $bytesRead = $readTask.GetAwaiter().GetResult()    
    
    $time.Elapsed.TotalSeconds

catch 
    throw $_

finally
    $fileStreamIn.Close()
    $fileStreamOut1.Close()
    $fileStreamOut2.Close()

【讨论】:

这适用于一些杂耍。我最终得到了 $fileStreamOut = New-Object ... 因为 PS5.1 对此更满意。我认为缓冲区大小是个大问题。第一次运行非常缓慢。还认为我在等待周期中有很大的下降,因为我循环了几个目标。为什么最后要交换缓冲区? 使用了两个缓冲区,因为一个缓冲区正被写入目标,而另一个缓冲区同时被来自输入的新数据填充。当两个写入器和一个读取器任务完成时,我交换它们,以便写入器使用最近更新的缓冲区,而最近写入的缓冲区可以被丢弃并用于获取新的数据块。 “等待周期”只是同步任务,您基本上是在等待所有写入器完成写入并且读取器完成获取新数据集的时间。重要的一点是 FileStream 的内部缓冲区大小默认为 4096,可以调整,我建议尝试different values there(最佳值通常取决于所使用的硬件)。 我使用的缓冲区大小(10,000)只是任务同步之间的字节数。可能它应该是 FileStream 内部缓冲区大小的倍数。对于大文件,默认的 4096 可能不是最佳值,将其增加到 100k 左右可能会加快传输速度。 $fileStream 传递[IO.FileOptions]::SequentialScan 提示可以提高性能。我会将缓冲区的大小调整为文件系统集群大小的倍数; 64 KB 将是一个很好的起点。我过去曾尝试从FileStream 中获得最佳性能,我认为在大约 4 MB 之后,缓冲区大小的回报会递减,尽管这是使用 RAM 驱动器。还要记住,与 .NET 调用相比,PowerShell 代码,所以运行得越少越好; 8 GB / 4 KB 缓冲区 = 2,097,152 次循环迭代与 4 MB 缓冲区 = 2,048 次循环迭代。

以上是关于如何在 PowerShell 中使用 tar 和 tee 进行一次读取、多次写入、原始文件副本的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PowerShell 在 IIS 中停止和启动各个网站?

如何在PowerShell中使用.NET Framework

如何在 PowerShell 中使用 S/MIME 对消息进行签名和加密

powershell 如何定时执行 ps1

如何在 PowerShell 中获取 MD5 校验和

如何在 Powershell 中使用文件命名约定获取文件