web-dev-qa-db-fra.com

PowerShell obtient le nombre de lignes d'un gros (gros) fichier

L'une des façons d'obtenir le nombre de lignes d'un fichier est cette méthode dans PowerShell:

PS C:\Users\Pranav\Desktop\PS_Test_Scripts> $a=Get-Content .\sub.ps1
PS C:\Users\Pranav\Desktop\PS_Test_Scripts> $a.count
34
PS C:\Users\Pranav\Desktop\PS_Test_Scripts> 

Cependant, lorsque j'ai un gros fichier texte de 800 Mo, comment puis-je obtenir le numéro de ligne sans lire le fichier entier?

La méthode ci-dessus consommera trop RAM entraînant le plantage du script ou trop de temps pour terminer).

32
Pranav

Utilisation Get-Content -Read $nLinesAtTime pour lire votre fichier partie par partie:

$nlines = 0;

# Read file by 1000 lines at a time
gc $YOURFILE -read 1000 | % { $nlines += $_.Length };
[string]::Format("{0} has {1} lines", $YOURFILE, $nlines)

Et voici un script simple mais lent pour valider le travail sur un petit fichier:

gc $YOURFILE | Measure-Object -Line
28
Akim

Voici un script PowerShell que j'ai bricolé ensemble qui montre quelques méthodes différentes de comptage des lignes dans un fichier texte, ainsi que le temps et la mémoire requis pour chaque méthode. Les résultats (ci-dessous) montrent des différences claires dans les exigences de temps et de mémoire. Pour mes tests, il semble que l'endroit idéal était Get-Content, en utilisant un paramètre ReadCount de 100. Les autres tests ont nécessité beaucoup plus de temps et/ou d'utilisation de la mémoire.

#$testFile = 'C:\test_small.csv' # 245 lines, 150 KB
#$testFile = 'C:\test_medium.csv' # 95,365 lines, 104 MB
$testFile = 'C:\test_large.csv' # 285,776 lines, 308 MB

# Using ArrayList just because they are faster than Powershell arrays, for some operations with large arrays.
$results = New-Object System.Collections.ArrayList

function AddResult {
param( [string] $sMethod, [string] $iCount )
    $result = New-Object -TypeName PSObject -Property @{
        "Method" = $sMethod
        "Count" = $iCount
        "Elapsed Time" = ((Get-Date) - $dtStart)
        "Memory Total" = [System.Math]::Round((GetMemoryUsage)/1mb, 1)
        "Memory Delta" = [System.Math]::Round(((GetMemoryUsage) - $dMemStart)/1mb, 1)
    }
    [void]$results.Add($result)
    Write-Output "$sMethod : $count"
    [System.GC]::Collect()
}

function GetMemoryUsage {
    # return ((Get-Process -Id $pid).PrivateMemorySize)
    return ([System.GC]::GetTotalMemory($false))
}

# Get-Content -ReadCount 1
[System.GC]::Collect()
$dMemStart = GetMemoryUsage
$dtStart = Get-Date
$count = 0
Get-Content -Path $testFile -ReadCount 1 |% { $count++ }
AddResult "Get-Content -ReadCount 1" $count

# Get-Content -ReadCount 10,100,1000,0
# Note: ReadCount = 1 returns a string.  Any other value returns an array of strings.
# Thus, the Count property only applies when ReadCount is not 1.
@(10,100,1000,0) |% {
    $dMemStart = GetMemoryUsage
    $dtStart = Get-Date
    $count = 0
    Get-Content -Path $testFile -ReadCount $_ |% { $count += $_.Count }
    AddResult "Get-Content -ReadCount $_" $count
}

# Get-Content | Measure-Object
$dMemStart = GetMemoryUsage
$dtStart = Get-Date
$count = (Get-Content -Path $testFile -ReadCount 1 | Measure-Object -line).Lines
AddResult "Get-Content -ReadCount 1 | Measure-Object" $count

# Get-Content.Count
$dMemStart = GetMemoryUsage
$dtStart = Get-Date
$count = (Get-Content -Path $testFile -ReadCount 1).Count
AddResult "Get-Content.Count" $count

# StreamReader.ReadLine
$dMemStart = GetMemoryUsage
$dtStart = Get-Date
$count = 0
# Use this constructor to avoid file access errors, like Get-Content does.
$stream = New-Object -TypeName System.IO.FileStream(
    $testFile,
    [System.IO.FileMode]::Open,
    [System.IO.FileAccess]::Read,
    [System.IO.FileShare]::ReadWrite)
if ($stream) {
    $reader = New-Object IO.StreamReader $stream
    if ($reader) {
        while(-not ($reader.EndOfStream)) { [void]$reader.ReadLine(); $count++ }
        $reader.Close()
    }
    $stream.Close()
}

AddResult "StreamReader.ReadLine" $count

$results | Select Method, Count, "Elapsed Time", "Memory Total", "Memory Delta" | ft -auto | Write-Output

Voici les résultats pour un fichier texte contenant ~ 95k lignes, 104 Mo:

Method                                    Count Elapsed Time     Memory Total Memory Delta
------                                    ----- ------------     ------------ ------------
Get-Content -ReadCount 1                  95365 00:00:11.1451841         45.8          0.2
Get-Content -ReadCount 10                 95365 00:00:02.9015023         47.3          1.7
Get-Content -ReadCount 100                95365 00:00:01.4522507         59.9         14.3
Get-Content -ReadCount 1000               95365 00:00:01.1539634         75.4         29.7
Get-Content -ReadCount 0                  95365 00:00:01.3888746          346        300.4
Get-Content -ReadCount 1 | Measure-Object 95365 00:00:08.6867159         46.2          0.6
Get-Content.Count                         95365 00:00:03.0574433        465.8        420.1
StreamReader.ReadLine                     95365 00:00:02.5740262         46.2          0.6

Voici les résultats d'un fichier plus volumineux (contenant environ 285 000 lignes, 308 Mo):

Method                                    Count  Elapsed Time     Memory Total Memory Delta
------                                    -----  ------------     ------------ ------------
Get-Content -ReadCount 1                  285776 00:00:36.2280995         46.3          0.8
Get-Content -ReadCount 10                 285776 00:00:06.3486006         46.3          0.7
Get-Content -ReadCount 100                285776 00:00:03.1590055         55.1          9.5
Get-Content -ReadCount 1000               285776 00:00:02.8381262         88.1         42.4
Get-Content -ReadCount 0                  285776 00:00:29.4240734        894.5        848.8
Get-Content -ReadCount 1 | Measure-Object 285776 00:00:32.7905971         46.5          0.9
Get-Content.Count                         285776 00:00:28.4504388       1219.8       1174.2
StreamReader.ReadLine                     285776 00:00:20.4495721           46          0.4
21
Pseudothink

Voici un one-liner basé sur le post de Pseudothink.

Lignes dans un fichier spécifique:

"the_name_of_your_file.txt" |% {$n = $_; $c = 0; Get-Content -Path $_ -ReadCount 1000 |% { $c += $_.Count }; "$n; $c"}

Tous les fichiers dans le répertoire courant (individuellement):

Get-ChildItem "." |% {$n = $_; $c = 0; Get-Content -Path $_ -ReadCount 1000 |% { $c += $_.Count }; "$n; $c"}

Explication:

"the_name_of_your_file.txt" -> ne fait rien, fournit simplement le nom du fichier pour les étapes suivantes, doit être entre guillemets
|% -> alias ForEach-Object, itère sur les éléments fournis (un seul dans ce cas), accepte le contenu redirigé en entrée, l'élément actuel enregistré dans $_
$n = $_ -> $ n comme nom du fichier fourni est enregistré pour plus tard à partir de $_, en fait, cela peut ne pas être nécessaire
$c = 0 -> initialisation de $c comme nombre
Get-Content -Path $_ -ReadCount 1000 -> lire 1000 lignes du fichier fourni (voir les autres réponses du fil)
|% -> foreach ajoute le nombre de lignes réellement lues à $c (sera comme 1000 + 1000 + 123)
"$n; $c" -> une fois la lecture du fichier terminée, imprimer le nom du fichier; nombre de lignes
Get-ChildItem "." -> ajoute simplement plus d'éléments au tube que le nom de fichier unique

13
Honza

La première chose à essayer est de diffuser Get-Content et construire le nombre de lignes un par un, plutôt que de stocker toutes les lignes dans un tableau à la fois. Je pense que cela donnera un comportement de streaming approprié - c'est-à-dire que le fichier entier ne sera pas en mémoire à la fois, juste la ligne actuelle.

$lines = 0
Get-Content .\File.txt |%{ $lines++ }

Et comme le suggère l'autre réponse, en ajoutant -ReadCount pourrait accélérer cela.

Si cela ne fonctionne pas pour vous (trop lent ou trop de mémoire), vous pouvez aller directement à un StreamReader:

$count = 0
$reader = New-Object IO.StreamReader 'c:\logs\MyLog.txt'
while($reader.ReadLine() -ne $null){ $count++ }
$reader.Close()  # Don't forget to do this. Ideally put this in a try/finally block to make sure it happens.
12
latkin

Voici une autre solution qui utilise .NET:

[Linq.Enumerable]::Count([System.IO.File]::ReadLines("FileToCount.txt"))

Ce n'est pas très interruptible, mais c'est très facile à mémoriser.

5
greenjaed

Voici quelque chose que j'ai écrit pour essayer de réduire l'utilisation de la mémoire lors de l'analyse de l'espace blanc dans mon fichier txt. Cela dit, l'utilisation de la mémoire est toujours élevée, mais le processus prend moins de temps à s'exécuter.

Juste pour vous donner un aperçu de mon fichier, le fichier avait plus de 2 millions d'enregistrements et comportait un espace blanc à l'avant et à l'arrière de chaque ligne. Je crois que le temps total était de 5+ minutes.

$testing = 'C:\Users\something\something\test3.txt'

$filecleanup =  Get-ChildItem $testing

foreach ($file in $filecleanup)
{ 
    $file1 = Get-Content $file -readcount 1000 | foreach{$_.Trim()}
    $file1 > $filecleanup
}
1
user2176024