我希望在逐行的基础上在我的文件中找到额外的分隔符。 但是,我想忽略文件中的标题行(第一行)和页脚行(最后一行),只关注文件详细信息。
我不确定如何使用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 1
和get-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()
}
答案 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()
}