【问题标题】:Powershell Large File Creation to Size with specified input Data使用指定输入数据创建大小的 Powershell 大文件
【发布时间】:2021-05-07 05:23:00
【问题描述】:

我正在尝试确定哪个 Powershell 命令等效于以下 Linux 命令,以便在合理的时间内创建一个大小准确的大文件并填充给定的文本输入。

给定:

$ cat line.txt
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ZZZZ

$ time yes `cat line.txt` | head -c 10GB > file.txt  # create large file
real    0m59.741s

$ ls -lt file.txt
-rw-r--r--+ 1 k None 10000000000 Feb  2 16:28 file.txt

$ head -3 file.txt
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ZZZZ
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ZZZZ
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ZZZZ

什么是最有效、最紧凑的 Powershell 命令,它允许我指定大小、文本内容并像上面的 Linux 命令一样创建文件?谢谢! Original ask here was automatically closed for some reason

【问题讨论】:

  • 这在网上几个地方都讲过:powershell 'create a large file with specific data'
  • 不是自动关闭的,是因为重复而故意关闭的。在那篇文章上是这样说的。它还建议您将其编辑为适当的、有主题的、不重复的方式来重新打开它。
  • 我很抱歉。我编辑了原件,它看起来不像重新打开。我认为唯一的办法就是问另一个问题。我显然没有把问题说得足够清楚,无法将测试文件输入作为要求包含在内,而且答案也没有反映出来,所以我添加了它。我读过的任何内容都没有表明这个问题是重复的,所以我一定遗漏了一些东西。似乎是一个足够简单的问题,如果我能得到一个符合要求的链接,我一定会很感激。
  • @DougMaurer,是的,之前的问题已根据用户投票关闭,但它不恰当地关闭,因为它包含特定要求而不是由所谓的重复解决。鉴于原始问题的唯一答案是也忽略了特定要求并且只回答了所谓的重复问题,因此创建一个使特定要求更加明确的新问题是有意义的,就像这里发生的那样。
  • @mklement0 感谢您的课外帮助!作为代码的明显发起者,我选择了您的答案。实际上,这两个答案都是完美的,我相信大家都会非常感激。太糟糕了,我只能选择一个答案。谢谢你们俩。! cc @zett42

标签: windows powershell


【解决方案1】:

继续我的评论。

没有执行此操作的命令。您必须对其进行编码。

仅根据我通过搜索指向的信息。在 PowerShell 中,快速了解您的用例就像采用这种方法一样。

Function New-EmptyFile
{
<#
.Synopsis
    Create a new empty file 
.DESCRIPTION
    This function creates a new file of the given size
.EXAMPLE
    New-EmptyFile -FilePath 'D:\Temp\nef.txt' -Size 10mb

.EXAMPLE
    nef 'D:\Temp\nef.txt' 10mb

.NOTES
    You can modify data in the file this way
    (Get-Content -path 'D:\Temp\nef.txt' -Raw) -replace '\.*','white' | 
    Set-Content -Path 'D:\Temp\nef.txt'    
#>

    [cmdletbinding(SupportsShouldProcess)]
    [Alias('nef')]
    param
    (
        [string]$FilePath,
        [double]$Size
    )
 
    $file = [System.IO.File]::Create($FilePath)
    $file.SetLength($Size)
    $file.Close()

    Get-Item $file.Name
}

你可以拿这个:

(Get-Content -path 'D:\Temp\nef.txt' -Raw) -replace '\.*','white' | 
Set-Content -Path 'D:\Temp\nef.txt'

... 并使其成为功能的一部分。像这样的:

Function New-EmptyFile
{
<#
.Synopsis
    Create a new empty file 
.DESCRIPTION
    This function creates a new file of the given size
.EXAMPLE
    New-EmptyFile -FilePath 'D:\Temp\nef.txt' -Size 10mb

.EXAMPLE
    nef 'D:\Temp\nef.txt' 10mb

.NOTES
    Other notes here
 
#>

    [cmdletbinding(SupportsShouldProcess)]
    [Alias('nef')]
    param
    (
        [string]$FilePath,
        [double]$Size,
        [string]$FileData
    )
 
    $file = [System.IO.File]::Create($FilePath)
    $file.SetLength($Size)
    $file.Close()

    Get-Item $file.Name

    If ($FileData)
    {
        (Get-Content -Path (Get-Item $file.Name).FullName -Raw) -replace '\.*',$FileData | 
        Set-Content -Path (Get-Item $file.Name).FullName   
    }
}

New-EmptyFile -FilePath 'D:\Temp\nef.txt' -Size 10mb -FileData 'The quick brown fox.'

但是,在处理大文件时,性能特别意味着使用 .Net 命名空间。

以上内容都不能完全替代您发布的内容,因此,您需要根据需要进行调整。

看这篇文章

Reading large text files with Powershell

【讨论】:

    【解决方案2】:

    没有与您的命令直接等效的 PowerShell。

    事实上,对于这种大小的文件,最好的办法是避免使用 PowerShell 自己的 cmdlet 和管道,而是直接使用 .NET 类型:

    & {
      param($outFile, $size, $content)
    
      # Add a newline to the input string, if needed.
      $line = $content + "`n"
    
      # Calculate how often the line must be repeated (including trailing newline)
      # to reach the target size.
      [long] $remainder = 0
      $iterations = [math]::DivRem($size, $line.Length, [ref] $remainder)
    
      # Create the output file.
      $outFileInfo = New-Item -Force $outFile
      $fs = [System.IO.StreamWriter] $outFileInfo.FullName
    
      # Fill it with duplicates of the line.
      foreach ($i in 1..$iterations) {
        $fs.Write($line)
      }
    
      # If a partial line is needed to reach the exact target size, write it now.
      if ($remainder) {
        $fs.Write($line.Substring(0, $remainder))
      }
    
      $fs.Close()
      
    } file.txt 1e10 (Get-Content line.txt)
    

    注意:1e10 使用 PowerShell 对科学计数法的支持作为10000000000 的简写(10,000,000,000,即[Math]::Pow(10, 10)。请注意,PowerShell 还内置支持字节乘数后缀 - kbmbgbtb - 但它们是 二进制 乘数,因此10gb 等价于10,737,418,240 (10 * [math]::Pow(1024, 3)),而不是十进制 10,000,000,000

    注意

    • 传递的大小(在这种情况下为1e10)是一个字符计数,而不是一个字节计数。鉴于 .NET 的文件 I/O API 默认使用 BOM-less UTF-8 编码,只有当您限制输入字符串以使用 ASCII 范围内的字符填充文件时,这两个计数才会相等(代码点 0x0 - 0x7f )。

    • 如果总字符数不是输入字符串长度的精确倍数,则输入字符串的 last 实例可能会被 截断(没有尾随换行符) + 1(换行符)。

    • 通过结合写入 字节 和输出缓冲,可以将该代码的性能优化多达 20%,如 zett42's helpful answer 所示。

    按照 PowerShell 标准,上述性能相当不错。

    一般而言,PowerShell 的面向对象特性永远无法与原生 Unix 实用程序/shell 提供的原始字节处理速度相提并论。

    把上面的代码变成一个可重用的函数并不难;在
    简而言之,将&amp; { ... } 替换为function New-FileOfSize { ... } 并调用New-FileOfSize file.txt 1gb (Get-Content line.txt) - 请参阅概念性about_Functions 帮助主题,以及about_Functions_Advanced 以了解如何使函数更复杂。

    【讨论】:

    • 为了可能的性能改进,可以尝试:1)指定更大的缓冲区大小(默认仅为 4 KiB AFAIK)和 2)只对字符串编码一次并直接使用 FileStream
    • 我做了some measurements。有显着但不引人注目的性能改进。
    • 调用New-Item而不是让.NET API创建文件有优势吗?前者产生更清晰的错误消息,但我不喜欢潜在的竞争条件,例如。 G。在调用 New-Item 和打开它的 .NET API 之间,该文件可能会被删除(或被另一个进程打开,阻止写入访问)。
    • @zett42,我使用它的唯一原因是我可以可靠地获得调用 .NET 方法所需的 完整路径,因为 .NET 的当前目录通常与 PowerShell 不同。理想情况下,可以使用Convert-Path,但遗憾的是,它仅适用于现有 文件或文件夹——请参阅GitHub issue #2993。仅在 .NET Core 中(因此不在 Windows PowerShell 中),您可以使用 [System.IO.Path]::GetFullPath($outFile, $PWD.ProviderPath)
    • @zett42,是的,但我认为这并不重要:该函数不保证何时尝试声明文件,使用 New-Item 的唯一原因是确定完整路径。当然,通过尝试 两次 声明文件 - 首先是 NewItem,然后是 System.IO.StreamWriter] 构造函数,假设存在额外的潜在故障点,但我不认为并发问题在这里。我同意技术上 $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath() 是更好的方法,但它是如此晦涩......
    【解决方案3】:

    mklement0's script 的略微优化版本。

    • 在开头只对字符串编码一次。
    • 使用System.IO.FileStream 而不是System.IO.StreamWriter 写入原始字节而不是必须首先编码的字符串。
    • 使用比StreamWriter which is rather small 的默认值更大的缓冲区。 1 MiB 的大小似乎是我机器上的最佳选择。 2 MiB 的缓冲区已经很慢了,这可能是由于更糟糕的缓存行为。它可能因您的机器而异。
    • 与性能无关,换行符不再添加到输入字符串$content。如果需要,它可以由用户添加到参数中。为了实现这一点,我在Get-Content 调用中添加了参数-raw
    & {
        param($outFile, $size, $content)
      
        # Encode the input string as UTF-8
        $encoding = [Text.UTF8Encoding]::new()
        $contentBytes = $encoding.GetBytes( $content )
      
        # Calculate how often the content must be repeated (including trailing newline)
        # to reach the target size.
        [long] $remainder = 0
        $iterations = [math]::DivRem($size, $contentBytes.Length, [ref] $remainder)
      
        # Convert the PowerShell path to a full path for use by .NET API.
        # .NET can't use a relative PowerShell path as its current directory may differ from
        # PowerShells current directory.
        $fullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $outFile )
    
        # Create a file stream with a large buffer size for improved performance.
        $bufferSize = 1MB
        $stream = [IO.FileStream]::new( $fullPath, [IO.FileMode]::Create, [IO.FileAccess]::Write, 
                                        [IO.FileShare]::Read, $bufferSize )
    
        try {
            # Fill it with duplicates of the content.
            foreach ($i in 1..$iterations) {
                $stream.Write($contentBytes, 0, $contentBytes.Length)
            }
          
            # If a sub string of the content is needed to reach the exact target size, write it now. 
            # Note this may create an invalid UTF-8 code point at the end, depending on
            # the input. Basic ASCII is no problem.
            if ($remainder) {
                $stream.Write($contentBytes, 0, $remainder)
            } 
        }
        finally {
            # Close the stream even when an exception has been thrown.
            $stream.Close()
        }    
    } file.txt 1gb (Get-Content -raw line.txt) 
    

    为了测试,该脚本用于创建一个 1 GB 文件,其中包含 OP 测试内容(99 个字符 + LF)。对于每个测试,计算 100 次运行的平均 MiB/s:

    $duration = (1..100 | %{ (Measure-Command { .\Test.ps1 }).TotalSeconds } | Measure-Object -Average).Average
    "$(1024 / $duration) MiB/s"
    

    测试结果:

    Script Buffer size MiB/s
    mklement0's script default 438
    optimized script 4 KiB 434
    optimized script 16 KiB 483
    optimized script 64 KiB 521
    optimized script 256 KiB 524
    optimized script 1 MiB 528
    optimized script 2 MiB 526

    因此,在最好的情况下,我们的性能会提高约 20%。不壮观,但仍然引人注目。

    与 winsat 测量的 SSD 性能相比,这些值看起来相当不错:

    > winsat disk -seq -write -drive x
    Disk  Sequential 64.0 Write                  496.03 MB/s
    

    【讨论】:

    • 这是一项很棒的工作,可以让我在公司工作站上创建一个大文件进行测试。我能够在不到 60 秒的时间内创建一个 > 10GB 的文件。我想知道文件大小。我创建了 line.txt 作为 100 字节的输入文件,并且能够创建一个精确大小的 10GB 文件,如问题所示。解决方案 file.txt 中的最后一行似乎不完整,file.txt 的总文件大小为 10737418240。这背后有什么原因吗?你提到 96 个字符,应该是 100。line.txt 的 ASCII 32-127 ..抱歉我可能造成的任何混乱。
    • @mojoa 96 个字符是我这边的一个错误。我还从代码中删除了添加的换行符,并使用Get-Content -raw 来包含文件中的LF。最后不完整的行是正确的,因为 10 GB 不能被 100 整除。在 PowerShell 控制台中输入:10GB / 100
    • 不确定 PowerShell 如何计算字节。如问题所示,我能够通过附加 100 字节的 line.txt 来精确创建 10000000000 字节的 file.txt。 Linux wc 命令和 Windows DIR 也验证了这一点。
    • @mojoa 看看 mklement0 在他的代码下的注释中写了什么。将“10GB”替换为“1e10”。
    • 那么,一个完整的精确字节计数的 PowerShell 解决方案是否需要像 Linux head 一样包含一个计算行数和截断无关的函数?也许作为后处理步骤?这会增加我认为的实际文件创建时间。
    猜你喜欢
    • 2017-06-04
    • 1970-01-01
    • 2021-06-12
    • 1970-01-01
    • 1970-01-01
    • 2015-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多