【问题标题】:Use Get content or Import-CSV to read 1st column in 2nd line in a csv使用 Get content 或 Import-CSV 读取 csv 中第二行的第一列
【发布时间】:2020-01-30 06:21:08
【问题描述】:

所以我有一个 25MB 的 csv 文件。 我只需要获取存储在第一列第二行的值,然后在 powershell 脚本中使用它。

例如数据

File_name,INVNUM,ID,XXX....850 columns
ABCD,123,090,xxxx.....850 columns
ABCD,120,091,xxxx.....850 columns
xxxxxx5000+ rows

所以我的第一列数据总是相同的,我只需要从第一列第二行获取这个文件名。

对于这个用例,我应该尝试使用 Get-content 还是 Import-csv ?

谢谢, 米奇

【问题讨论】:

    标签: powershell powershell-4.0


    【解决方案1】:

    TessellatingHeckler's helpful answer 包含一个实用的、易于理解的解决方案,在实践中很可能足够快; Robert Cotterman's helpful answer 也是如此,它简洁(而且速度也更快)。

    如果性能真的很重要,您可以尝试以下方法,它直接使用 .NET 框架来读取行 - 但是给出你只需要阅读 2 行,这可能不值得:

    $inputFile = "$PWD/some.csv" # be sure to specify a *full* path
    $isFirstLine=$true
    $fname = foreach ($line in [IO.File]::ReadLines($inputFile)) { 
      if ($isFirstLine) { $isFirstLine = $false; continue } # skip header line
      $line -replace '^([^,]*),.*', '$1' # extract 1st field from 2nd line and exit
      break # exit
    }
    

    注意:提取第一个字段的概念上更简单的方法是使用($line -split ',')[0],但对于大量列,上述基于-replace 的方法明显更快。

    更新TessellatingHeckler 提供了 2 种方法来加快上述速度:

    • 使用$line.Substring(0, $line.IndexOf(',')) 代替$line -replace '^([^,]*),.*', '$1' 以避免相对昂贵的regex 处理。

    • 为了减少收益,请连续两次使用[System.IO.StreamReader] 实例的.ReadLine() 方法,而不是循环使用[IO.File]::ReadLines()

    这是此页面上所有答案的方法的性能比较(截至撰写本文时)。

    要自己运行,必须先下载函数New-CsvSampleDataTime-Command

    为了获得更具代表性的结果,时间是 1000 次运行的平均值:

    # Create sample CSV file 'test.csv' with 850 columns and 100 rows.
    $testFileName = "test-$PID.csv"
    New-CsvSampleData -Columns 850 -Count 100 | Set-Content $testFileName
    
    # Compare the execution speed of the various approaches:
    Time-Command -Count 1000 { 
        # Import-Csv
        Import-Csv -LiteralPath $testFileName | 
          Select-Object -Skip 1 -First 1 -ExpandProperty 'col1'
      }, {
        # ReadLines(), -replace
        $inputFile = $PWD.ProviderPath + "/$testFileName"
        $isFirstLine=$true
        foreach ($line in [IO.File]::ReadLines($inputFile)) { 
          if ($isFirstLine) { $isFirstLine = $false; continue } # skip header line
          $line -replace '^([^,]*),.*', '$1' # extract 1st field from 2nd line and exit
          break # exit
        }    
      }, {
        # ReadLines(), .Substring / IndexOf
        $inputFile = $PWD.ProviderPath + "/$testFileName"
        $isFirstLine=$true
        foreach ($line in [IO.File]::ReadLines($inputFile)) { 
          if ($isFirstLine) { $isFirstLine = $false; continue } # skip header line
          $line.Substring(0, $line.IndexOf(',')) # extract 1st field from 2nd line and exit
          break # exit
        }    
      }, {
        # ReadLine() x 2, .Substring / IndexOf
        $inputFile = $PWD.ProviderPath + "/$testFileName"
        $f = [System.IO.StreamReader]::new($inputFile,$true); 
        $null = $f.ReadLine(); $line = $f.ReadLine()
        $line.Substring(0, $line.IndexOf(','))
        $f.Close()
      }, {
        # Get-Content -Head, .Split()
        ((Get-Content $testFileName -Head 2)[1]).split(',')[1]
      } |
      Format-Table Factor, Timespan, Command
    
    Remove-Item $testFileName
    

    在最近型号的 MacBook Pro 上运行 Windows PowerShell v5.1 / PowerShell Core 6.1.0-preview.4 的单核 Windows 10 VM 的示例输出:

    Windows PowerShell v5.1:

    Factor TimeSpan         Command
    ------ --------         -------
    1.00   00:00:00.0001922 # ReadLine() x 2, .Substring / IndexOf...
    1.04   00:00:00.0002004 # ReadLines(), .Substring / IndexOf...
    1.57   00:00:00.0003024 # ReadLines(), -replace...
    3.25   00:00:00.0006245 # Get-Content -Head, .Split()...
    25.83  00:00:00.0049661 # Import-Csv...
    

    PowerShell Core 6.1.0-preview.4:

    Factor TimeSpan         Command
    ------ --------         -------
    1.00   00:00:00.0001858 # ReadLine() x 2, .Substring / IndexOf...
    1.03   00:00:00.0001911 # ReadLines(), .Substring / IndexOf...
    1.60   00:00:00.0002977 # ReadLines(), -replace...
    3.30   00:00:00.0006132 # Get-Content -Head, .Split()...
    27.54  00:00:00.0051174 # Import-Csv...
    

    结论:

    • 调用.ReadLine() 两次比::ReadLines() 循环稍快。

    • 使用 -replace 代替 Substring() / IndexOf() 会增加大约 60% 的执行时间。

    • 使用Get-Content会慢3倍以上。

    • 使用Import-Csv | Select-Object会慢近30倍(!),大概是由于列数很大;也就是说,从绝对意义上讲,我们仍然只谈论大约 5 毫秒。

    • 附带说明:总体而言,macOS 上的执行似乎明显变慢,正则表达式解决方案和 cmdlet 调用也相对较慢。

    【讨论】:

    • Measure-Command测试;我的Import-Csv 代码非常一致地在 1-2 毫秒左右。您的代码开始时大约 44 毫秒,但有时会下降到 0.7 - 1.7 毫秒并且更快,但有时不是。
    • @TessellatingHeckler:使用此类短时间运行的命令,您需要对多次运行的时间进行平均,才能真正了解相对性能。我已经为这个页面上的所有命令(包括你建议的.ReadLine() x 2 方法)添加了时间,其中一些可能令人惊讶。
    【解决方案2】:

    取决于你想优先考虑什么。

    $data = Import-Csv -LiteralPath 'c:\temp\data.csv' | 
                Select-Object -Skip 1 -First 1 -ExpandProperty 'File_Name'
    

    短而方便。 (第 2 行表示文件的第 2 行,还是数据的第 2 行?如果是第一行数据,请不要跳过任何内容)。

    带有-First 1 之类的Select-Object 将在完成后破坏整个管道,因此它不会等待在返回之前在后台读取剩余的25MB。

    如果您打开文件,寻找两个换行符,然后是一个逗号,然后读取另一个逗号,或者其他一些详细的代码,您可能会加快速度或减少内存使用量,但我非常很怀疑这是否值得。

    Get-Content 相同,它将NoteProperties 添加到输出字符串的方式意味着它可能不会比Import-Csv 更容易在内存上使用并且不会比Import-Csv 更快

    【讨论】:

    • 是的,我的意思是文件的第二行。因为第一个只是标题。还想只加载前两行,从第二行读取文件名,然后返回控制权,而不是尝试加载所有 25MB 的行。
    【解决方案3】:

    你真的可以缩短它

    (gc c:\file.txt -head 2)[1]
    

    只读取 2 行然后抓取索引 1(第二行)

    然后你可以拆分它。并抓取分割线的索引1

    ((gc c:\file.txt -head 2)[1]).split(',')[1]
    

    UPDATE:::在多次看到new post 之后,我受到启发自己做一些测试(感谢mklement0)。这是我上班最快的速度

    $check = 0
    foreach ($i in [IO.FILE]::ReadLines("$filePath")){
        if ($check -eq 2){break}
        if ($check -eq 1){$value = $i.split(',')[1]}  #$value = your answer
        $check++
    }
    

    刚刚想到这个:删除 if -eq 2 并在执行检查 1 后将 break 放在分号后。快 5 个滴答声。没有测试过。

    这是我超过 40000 次测试的结果:

    • GC 拆分平均为 1.11307622 毫秒
    • GC 拆分最小值为 0.3076 毫秒
    • GC 拆分最大值为 18.1514 毫秒
    • ReadLines 拆分平均值为 0.3836625825 毫秒
    • ReadLines split Min 为 0.2309 毫秒
    • ReadLines split Max 为 31.7407 毫秒
    • 流读取器平均为 0.4464924825 毫秒
    • Stream Reader MIN 为 0.2703 毫秒
    • Stream Reader Max 为 31.4991 毫秒
    • 导入 CSV 平均为 1.32440485 毫秒
    • Import-CSV MIN 为 0.2875 毫秒
    • Import-CSV Max 为 103.1694 毫秒

    我能够在第 2 次和第 3 次每秒运行 3000 次测试,在第一次和最后一次每秒运行 1000 次测试。 Stream Reader 是他最快的。并且导入 CSV 也不错,我想知道 mklement0 在他的测试 csv 中是否没有名为“file_name”的列?无论如何,我个人会使用 GC 命令,因为它简洁易记。但这取决于你,我祝你在脚本冒险中好运。

    我敢肯定,我们可以开始超线程处理并获得疯狂的结果,但是当您谈论千分之一秒时,这真的很重要吗?特别是要获得一个变量? :D

    这是我出于透明原因使用的流式阅读器代码...

    $inputFile = "$filePath"
    $f = [System.IO.StreamReader]::new($inputFile,$true); 
    $null = $f.ReadLine(); $line = $f.ReadLine()
    $line.Substring(0, $line.IndexOf(','))
    $f.Close()
    

    我还注意到这会拉第二行的第一个值,我不知道如何将其切换到第二个值......它似乎是在测量从点 0 到第一个逗号的宽度,然后切割那。如果您将子字符串从 0 更改为 5,它仍会测量 0 到逗号的长度,但随后会将开始抓取的位置...移动到第 6 个字符。

    我使用的 import-csv 是:

    $data = Import-Csv -LiteralPath "$filePath" | 
            Select-Object -Skip 1 -First 1 -ExpandProperty 'FileName'
    

    我在 90 meg 的 csv、21 列和 284k 行上测试了这些。而“文件名”是第二列

    【讨论】:

    • Import-Csv 在我的测试中如此缓慢的原因是输入文件有 850 列,就像 OP 的情况一样。 Substring() / IndexOf() 方法仅适用于 first 列,因为提取从第一个字符位置(行首)开始;对于任何其他人,您首先要确定该列的起始位置并将其用作Substring()IndexOf 的起点。
    猜你喜欢
    • 2019-06-10
    • 1970-01-01
    • 1970-01-01
    • 2021-12-13
    • 2015-05-07
    • 2014-09-20
    • 1970-01-01
    • 1970-01-01
    • 2019-05-03
    相关资源
    最近更新 更多