PowerShell получить количество строк большого (большого) файла
Один из способов получить количество строк из файла - это метод в 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>
Однако, когда у меня большой текстовый файл размером 800 МБ, как получить из него номер строки, не читая весь файл?
Приведенный выше метод потребляет слишком много оперативной памяти, что приводит к сбою сценария или занимает слишком много времени для его завершения.
Ответы
Ответ 1
Используйте Get-Content -Read $nLinesAtTime
для чтения вашего файла по Get-Content -Read $nLinesAtTime
:
$nlines = 0;
# Read file by 1000 lines at a time
gc $YOURFILE -read 1000 | % { $nlines += $_.Length };
[string]::Format("{0} has {1} lines", $YOURFILE, $nlines)
А вот простой, но медленный скрипт для проверки работы над небольшим файлом:
gc $YOURFILE | Measure-Object -Line
Ответ 2
Вот сценарий PowerShell, который я собрал вместе, который демонстрирует несколько различных методов подсчета строк в текстовом файле, а также время и память, необходимые для каждого метода. Результаты (ниже) показывают четкие различия во времени и требованиях к памяти. Для моих тестов, похоже, что лучше всего было использовать Get-Content, используя настройку ReadCount, равную 100. Другие тесты требовали значительно большего времени и/или использования памяти.
#$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
Вот результаты для текстового файла, содержащего ~ 95 тыс. Строк, 104 МБ:
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
Вот результаты для файла большего размера (содержащего ~ 285 тыс. Строк, 308 МБ):
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
Ответ 3
Вот однострочник, основанный на посте Pseudothink.
Строки в одном конкретном файле:
"the_name_of_your_file.txt" |% {$n = $_; $c = 0; Get-Content -Path $_ -ReadCount 1000 |% { $c += $_.Count }; "$n; $c"}
Все файлы в текущем каталоге (индивидуально):
Get-ChildItem "." |% {$n = $_; $c = 0; Get-Content -Path $_ -ReadCount 1000 |% { $c += $_.Count }; "$n; $c"}
Объяснение:
"the_name_of_your_file.txt"
→ ничего не делает, просто предоставляет имя файла для следующих шагов, необходимо заключить в двойные кавычки
|%
→ псевдоним ForEach-Object, итерирует по предоставленным элементам (в данном случае только по одному), принимает переданное по конвейеру содержимое в качестве входных данных, текущий элемент сохраняется в $_
$n = $_
→ $ n, поскольку имя предоставленного файла сохраняется для последующего хранения из $_
, на самом деле это может не потребоваться
$c = 0
→ инициализация $c
качестве счетчика
Get-Content -Path $_ -ReadCount 1000
→ прочитать 1000 строк из предоставленного файла (см. Другие ответы в теме)
|%
→ foreach добавить количество строк, фактически прочитанных в $c
(будет похоже на 1000 + 1000 + 123)
"$n; $c"
→ после прочтения файла выведите имя файла; количество строк
Get-ChildItem "."
→ просто добавляет больше элементов в канал, чем одно имя файла
Ответ 4
Первое, что нужно попробовать, - это потоковая Get-Content
и наращивание количества строк по одной за раз, вместо того, чтобы хранить все строки в массиве одновременно. Я думаю, что это даст правильное поведение потоковой передачи - т.е. весь файл не будет в памяти сразу, только текущая строка.
$lines = 0
Get-Content .\File.txt |%{ $lines++ }
И, как предполагает другой ответ, добавление -ReadCount
может ускорить это.
Если это не работает для вас (слишком медленно или слишком много памяти), вы можете перейти непосредственно к 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.
Ответ 5
Вот еще одно решение, которое использует .NET:
[Linq.Enumerable]::Count([System.IO.File]::ReadLines("FileToCount.txt"))
Это не очень прерываемое, но очень легкое в памяти.
Ответ 6
Вот кое-что, что я написал, пытаясь уменьшить использование памяти при разборе пробела в моем текстовом файле. С учетом вышесказанного, использование памяти все еще становится высоким, но процесс занимает меньше времени.
Просто для того, чтобы дать вам некоторое представление о моем файле, файл содержал более 2 миллионов записей и имел пробел в передней и задней частях каждой строки. Я считаю, что общее время составило 5+ минут.
$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
}