2017-10-03 4 views
1

Je cherche des délimiteurs supplémentaires dans mon fichier ligne par ligne. Je voudrais, cependant, ignorer la ligne d'en-tête (première rangée) et la rangée de pied de page (dernière rangée) dans le fichier et juste se concentrer sur les détails du fichier.Powershell - J'ai de la difficulté à ignorer la ligne d'en-tête (première rangée) et la rangée de bas de page (dernière rangée) dans le fichier

Je ne suis pas sûr de savoir comment ignorer la première et la dernière ligne en utilisant la méthode ReadLine(). Je ne veux pas modifier le fichier d'aucune façon, ce script est utilisé juste pour identifier les lignes du fichier CSV qui ont des délimiteurs supplémentaires.

Veuillez noter: Le fichier que je cherche à chercher a des millions de lignes et pour ce faire, je dois me fier à la méthode ReadLine() plutôt qu'à l'approche Get-Content.

J'ai essayé d'utiliser Select-Object -Skip 1 | Select-Object -SkipLast 1 dans mon instruction Get-Content en entrant la valeur dans $measure mais je n'ai pas obtenu le résultat souhaité.

Par exemple:

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 

Fondamentalement, je veux que mon script pour ignorer l'en-tête et pied de page, et utiliser la première ligne de données (D|918...) comme l'exemple d'un enregistrement correct et les autres documents de détail à par rapport contre l'erreur (dans cet exemple, la deuxième ligne de détail doit être retourné, parce qu'il ya un séparateur valide dans le champ (EX|SRE-68...).

Quand j'ai essayé d'utiliser -skip 1 et -skiplast 1 dans la déclaration get-content, le processus est encore en utilisant la ligne d'en-tête comme comparaison et renvoyant tous les enregistrements de détail en tant qu'enregistrements invalides.

Voici ce que j'ai jusqu'à présent ...

Note de l'éditeur: En dépit de l'intention déclarée, ce code n'utilise la ligne (1ère ligne) tête pour déterminer le nombre de colonnes de référence.

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

Répondre

0

Maintenant que nous savons que la performance est, voici une solution qui utilise uniquement [System.IO.TextFile].ReadLine() (comme une alternative plus rapide à Get-Content) pour lire le fichier d'entrée grand, et le fait que une fois:

  • n comptage à l'avance du nombre de lignes par l'intermédiaire d'Get-Content ... | Measure-Object,

  • Aucune instance séparée d'ouverture du fichier juste pour lire la ligne d'en-tête; garder le fichier ouvert après avoir lu la ligne d'en-tête a l'avantage supplémentaire que vous pouvez simplement continuer à lire (aucune logique nécessaire pour passer la ligne d'en-tête).


$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

Quelle est votre raison d'utiliser Read Line? Le Get-Content que vous faites chargera déjà la totalité du CSV en mémoire, donc je l'enregistrerais dans une variable et ensuite j'utiliserai une boucle pour passer (en commençant à 1 pour sauter la première ligne).

donc quelque chose comme ceci:

$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

Merci Vous the_sw et mklement0 pour votre aide! Les fichiers que j'essaie de parcourir sont parfois des millions de lignes volumineuses et, en cas d'utilisation de la méthode get-content, il semble plutôt mal fonctionner. Par exemple, il faut environ une demi-heure pour un fichier de 500 000 lignes, en utilisant la méthode get-content. – Pavan

0

Note: Cette réponse a été écrit avant l'OP a précisé que la performance était primordiale et qu'une solution Get-Content était donc pas à base d'une option. Mon other answer répond maintenant à cela.
Cette réponse peut encore être intéressant pour un plus lent, mais plus concise, solution PowerShell-idiomatiques.

the_sw's helpful answer montre que vous pouvez utiliser propre Get-Content cmdlet PowerShell pour lire facilement un fichier, sans avoir besoin de recourir à l'utilisation directe du .NET Framework.

pSV5 + permet une solution unique pipeline idiomatiques qui est plus concis et plus de mémoire efficace - il traite les lignes une par une - bien au détriment de la performance; En particulier avec des fichiers volumineux, vous ne voudrez peut-être pas les lire tous en même temps, donc une solution pipeline est préférable.

PSv5 + est requis en raison de l'utilisation du paramètre Select-Object s -SkipLast.

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