Powershell - 难以忽略文件中的标题行(第一行)和页脚行(最后一行)

时间:2017-10-03 21:24:17

标签: performance powershell csv

我希望在逐行的基础上在我的文件中找到额外的分隔符。 但是,我想忽略文件中的标题行(第一行)和页脚行(最后一行),只关注文件详细信息。

我不确定如何使用ReadLine()方法忽略第一行和最后一行。我不想以任何方式更改文件,此脚本仅用于标识CSV文件中具有额外分隔符的行。

请注意:我要搜索的文件有数百万行,为了做到这一点,我必须依赖ReadLine()方法而不是Get-Content方法。

我确实在Select-Object -Skip 1 | Select-Object -SkipLast 1语句中使用Get-Content将值输入$measure,但我没有得到所需的结果。

例如:

H|Transaction|2017-10-03 12:00:00|Vendor --> This is the Header
D|918a39230a098134|2017-08-31 00:00:00.000|2017-08-15 00:00:00.000|SLICK-2340|...
D|918g39230b095134|2017-08-31 00:00:00.000|2017-08-15 00:00:00.000|EX|SRE-68|...
T|1268698 Records --> This is Footer

基本上,我希望我的脚本忽略页眉和页脚,并使用第一个数据行(D|918...)作为正确记录的示例,并将其他详细记录与其进行比较以获得错误(在此示例应返回第二个详细信息行,因为字段中存在无效分隔符(EX|SRE-68...)。

当我尝试在-skip 1语句中使用-skiplast 1get-content时,该过程仍然使用标题行作为比较,并将所有详细记录作为无效记录返回。

这是我到目前为止所拥有的......

编者注:尽管有明确的意图,但此代码确实使用标题行(第1行)来确定参考列数。

$File = "test.csv"
$Delimiter = "|"

$measure = Get-Content -Path $File | Measure-Object
$lines = $measure.Count

Write-Host "$File has ${lines} rows."

$i = 1

$reader = [System.IO.File]::OpenText($File)
$line = $reader.ReadLine()
$reader.Close()
$header = $line.Split($Delimiter).Count

$reader = [System.IO.File]::OpenText($File)
try
{
    for()
    {
        $line = $reader.ReadLine()
        if($line -eq $null) { break }
        $c = $line.Split($Delimiter).Count
        if($c -ne $header -and $i -ne${lines})
        {
            Write-Host "$File - Line $i has $c fields, but it should be $header"
        }
        $i++
    }
}

finally
{
    $reader.Close()
}

3 个答案:

答案 0 :(得分:1)

您使用Read Line的原因是什么?您正在执行的Get-Content已经将整个CSV加载到内存中,因此我将其保存到变量中,然后使用循环(从1开始跳过第一行)。

这样的事情:

$File = "test.csv"
$Delimiter = "|"

$contents = Get-Content -Path $File
$lines = $contents.Count

Write-Host "$File has ${lines} rows."

$header = $contents[0].Split($Delimiter).count

for ($i = 1; $i -lt ($lines - 1); $i++)
{ 
    $c = $contents[$i].Split($Delimiter).Count
    if($c -ne $header)
    {
        Write-Host "$File - Line $i has $c fields, but it should be $header"
    }
}

答案 1 :(得分:0)

注意:这个答案是在OP明确表示性能至关重要之前编写的,因此基于Get-Content的解决方案不是一种选择。我的other answer现在解决了这个问题 答案仍可能对 较慢的感兴趣,但更简洁,PowerShell惯用的解决方案。

the_sw's helpful answer表明您可以使用PowerShell自己的Get-Content cmdlet方便地读取文件,而无需直接使用.NET Framework。

PSv5 + 启用惯用的单管道解决方案,它更简洁,更节省内存 - 它逐个处理行 - 尽管以牺牲性能;但是,对于大文件,您可能不想一次性读取它们,因此最好使用管道解决方案。

由于使用Select-Object s -SkipLast参数,因此需要PSv5 +。

$File = "test.csv"
$Delimiter = '|'

Get-Content $File | Select-Object -SkipLast 1 | ForEach-Object { $i = 0 } {
  if (++$i -eq 1) { 
    return # ignore the actual header row
  } elseif ($i -eq 2) { # reference row
    $refColumnCount = $_.Split($Delimiter).Count
  } else { # remaining rows, except the footer, thanks to -SkipLast 1
    $columnCount = $_.Split($Delimiter).Count
    if ($columnCount -ne $refColumnCount) {
      "$File - Line $i has $columnCount fields rather than the expected $refColumnCount."
    }
  }
}

答案 2 :(得分:0)

既然我们知道性能很重要,这里的解决方案只使用[System.IO.TextFile].ReadLine()(作为Get-Content的更快替代品)来读取大型输入文件,并且所以只有一次

  • 无法通过Get-Content ... | Measure-Object预先计算行数,

  • 没有单独的实例打开文件只是为了读取标题行;在读取标题行后保持文件打开有一个额外的好处,你可以继续阅读(跳过标题行不需要逻辑)。

$File = "test.csv"
$Delimiter = "|"

# Open the CSV file as a text file for line-based reading.
$reader = [System.IO.File]::OpenText($File)

# Read the lines.
try {

  # Read the header line and discard it.
  $null = $reader.ReadLine()

  # Read the first data line - the reference line - and count its columns.
  $refColCount = $reader.ReadLine().Split($Delimiter).Count

  # Read the remaining lines in a loop, skipping the final line.
  $i = 2 # initialize the line number to 2, given that we've already read the header and the first data line.
  while ($null -ne ($line = $reader.ReadLine())) { # $null indicates EOF

    ++$i # increment line number

    # If we're now at EOF, we've just read the last line - the footer - 
    # which we want to ignore, so we exit the loop here.
    if ($reader.EndOfStream) { break }

    # Count this line's columns and warn, if the count differs from the
    # header line's.
    if (($colCount = $line.Split($Delimiter).Count) -ne $refColCount) {
      Write-Warning "$File - Line $i has $colCount fields rather than the expected $refColCount."
    }

  } 

} finally {

  $reader.Close()

}