2017-10-03 1 views
1

Ich suche nach zusätzlichen Trennzeichen in meiner Datei auf einer Zeile für Zeile Basis. Ich möchte jedoch die Kopfzeile (erste Zeile) und die Fußzeile (letzte Zeile) in der Datei ignorieren und nur auf das Dateidetail konzentrieren.Powershell - Schwierigkeiten beim Ignorieren der Kopfzeile (erste Zeile) und Fußzeile (letzte Zeile) in Datei

Ich bin nicht sicher, wie die erste und letzte Zeile mit der ReadLine() Methode zu ignorieren. Ich möchte die Datei in keiner Weise ändern, dieses Skript wird nur verwendet, um Zeilen in der CSV-Datei zu identifizieren, die zusätzliche Trennzeichen haben.

Bitte beachten Sie: Die Datei, nach der ich suchen möchte, hat Millionen von Zeilen. Um das zu tun, muss ich mich auf die Methode ReadLine() anstatt auf Get-Content verlassen.

Ich habe versucht, Select-Object -Skip 1 | Select-Object -SkipLast 1 in meiner Get-Content Anweisung zu verwenden, die den Wert in $measure eingibt, aber ich erhielt nicht das gewünschte Ergebnis.

Zum Beispiel:

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 

Grundsätzlich möchte ich mein Skript die Kopf- und Fußzeile, ignorieren und die ersten Datenreihe (D|918...) als Beispiel für eine korrekte Aufzeichnung und die anderen Detailaufzeichnungen nutzen zu Vergleich gegen sie für Fehler (in diesem Beispiel sollte die zweite Detailzeile zurückgeführt werden, da es einen ungültiger Begrenzer in dem das Feld (EX|SRE-68...).

Wenn I unter Verwendung -skip 1 und -skiplast 1 in der Anweisung get-content versuchte, ist das Verfahren noch immer Verwenden der Kopfzeile als Vergleich und Rückgabe aller Detailsätze als ungültige Datensätze.

Hier ist, was ich bisher haben ...

Anmerkung der Redaktion: Trotz der angegebenen Absicht, dieser Code nicht verwendet die Header Linie (die erste Zeile) die Referenzspaltenanzahl zu bestimmen.

$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() 
} 

Antwort

0

Jetzt, da wir wissen, dass Leistung wirklich, hier ist eine Lösung, die nur [System.IO.TextFile].ReadLine() verwendet (als schnellere Alternative zu Get-Content) der großen Eingabedatei zu lesen, und zwar nur einmal:

  • No Upfront-Zählung der Anzahl der Leitungen über Get-Content ... | Measure-Object,

  • Keine separate Instanz zum Öffnen der Datei, nur um die Kopfzeile zu lesen; Das Öffnen der Datei nach dem Lesen der Kopfzeile hat den zusätzlichen Vorteil, dass Sie einfach weiterlesen können (es ist keine Logik erforderlich, um die Kopfzeile zu überspringen).


$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() 

} 
1

Gibt es einen Grund, warum Sie Read Line verwenden? Der Get-Content wird die gesamte CSV-Datei bereits in den Speicher laden, also würde ich das in eine Variable speichern und dann eine Schleife verwenden (beginnend bei 1, um die erste Zeile zu überspringen).

So etwas wie folgt aus:

$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

Danke the_sw und mklement0 für Ihre Hilfe! Dateien, die ich durchsuchen möchte, sind manchmal Millionen von Zeilen groß und bei Verwendung der Get-Content-Methode scheint es eher schlecht zu funktionieren. Zum Beispiel dauert es etwa eine halbe Stunde für eine Datei, die 500.000 Zeilen groß ist, mit der Get-Content-Methode. – Pavan

0

Hinweis: Diese Antwort wurde geschrieben, bevor die OP stellte klar, dass die Leistung war von größter Bedeutung, und dass eine Get-Content -basierte Lösung war daher keine Option. Mein other answer adressiert das jetzt.
Diese Antwort noch kann für ein langsamer, aber prägnante, Powershell-idiomatische Lösung von Interesse sein.

the_sw's helpful answer zeigt, dass Sie Powershell eigene Get-Content Cmdlets bequem eine Datei verwenden können, um zu lesen, ohne direkte Nutzung des .NET Framework zurückgreifen zu müssen.

pSV5 + ermöglicht eine idiomatische Single-Pipeline Lösung, die prägnanten und speichereffizienter ist - es Zeilen nacheinander verarbeitet - wenn auch auf Kosten der Leistung; Vor allem bei großen Dateien möchten Sie sie jedoch möglicherweise nicht auf einmal lesen, daher ist eine Pipeline-Lösung vorzuziehen.

PSv5 + wird aufgrund der Verwendung von Select-Object s -SkipLast Parameter benötigt.

$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." 
    } 
    } 
} 
Verwandte Themen