首页 > 解决方案 > 使用 Get content 或 Import-CSV 读取 csv 中第二行的第一列

问题描述

所以我有一个 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 ?

谢谢,米奇

标签: powershellpowershell-4.0

解决方案


TessellatingHeckler 的有用答案包含一个实用、易于理解的解决方案,在实践中很可能足够快;罗伯特·科特曼( Robert Cotterman) 的有用答案也是如此,它简洁(而且速度更快)。

如果性能真的很重要,您可以尝试以下方法,它直接使用 .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'以避免相对昂贵的正则表达式处理。

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

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

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

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

# 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 核心 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 调用相对而言也较慢。


推荐阅读