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

How can I use tar and tee in PowerShell to do a read once, write many, raw file copy

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

我已经确定了速度的一个严重界限,当制作多个副本时(例如 4 根棍子,来自 2 个相机,即 8 次传输 * 8Gb )多次读取使用大量带宽,特别是因为相机是USB2.0接口(两个口),容量有限

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

内存条一般都在单USB3.0接口的集线器上,驱动在不同的通道上,所以写入速度足够快。

由于某些原因,我一直在使用当前的 Win10 PowerShell。

我目前正在将整个命令写入一个字符串(连接各种来源和各种 targets),然后在娱乐和购买时使用 Invoke-Process 执行复制过程拍摄后在酒吧巡视。 (因此有必要挂机)。

我可以tar cf - | tar xf 单个文件,但似乎无法使 T 恤正常工作。

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

编辑:我刚刚开始玩 Get-Content -raw |发球台 \stick1\f1 |发球台 \stick2\f1 |出空。还没有完成计时或文件验证....

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

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

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

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

一些参考资料:

-- 2021-12-09 更新:对代码进行了一些修改以反映评论中的建议。

# $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()
}