PowerShell获取大(大)文件的行数

时间:2012-08-23 04:13:53

标签: powershell line

从文件中获取行数的方法之一是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 MB的大文本文件时,如何在不读取整个文件的情况下从中获取行号?

上述方法会消耗太多内存,导致脚本崩溃或完成时间过长。

8 个答案:

答案 0 :(得分:24)

使用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

答案 1 :(得分:20)

这是一个我拼凑在一起的PowerShell脚本,它演示了一些计算文本文件中行的不同方法,以及每种方法所需的时间和内存。结果(如下)显示了时间和内存要求的明显差异。对于我的测试,使用ReadCount设置为100,看起来好点是Get-Content。其他测试需要更多的时间和/或内存使用。

#$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

以下是包含~95k行,104 MB:

的文本文件的结果
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

以下是较大文件的结果(包含~285k行,308 MB):

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

答案 2 :(得分:12)

这是一个基于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" - &gt;什么也不做,只提供后续步骤的文件名,需要双引号 |% - &gt;别名ForEach-Object,迭代提供的项目(在这种情况下只有一个),接受管道内容作为输入,当前项目保存到$_
$n = $_ - &gt; $ n作为所提供文件的名称保存以供稍后$_实际上可能不需要
$c = 0 - &gt;初始化$c作为计数
Get-Content -Path $_ -ReadCount 1000 - &gt;从文件中读取1000行(见线程的其他答案)
|% - &gt; foreach确实将实际读取的行数添加到$c(将像1000 + 1000 + 123) "$n; $c" - &gt;一旦读完文件,打印文件名;行数
Get-ChildItem "." - &gt;只是添加了比单个文件名更多的项目

答案 3 :(得分:11)

要尝试的第一件事是流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.

答案 4 :(得分:5)

这是另一个使用.NET的解决方案:

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

它不是很容易被打断,但是在内存上却很容易。

答案 5 :(得分:1)

在解析我的txt文件中的空白区域时,我写了一些尝试减少内存使用量的内容。话虽如此,内存使用率仍然很高,但这个过程花费的时间更少。

为了给你一些我文件的背景信息,该文件有超过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
}

答案 6 :(得分:1)

对于我的一些大文件(GB +), SWITCH 在内存上更快,更容易。

注意:以下时间以分钟:秒为单位。测试是在一个14,564,836行的文件中进行的,每行906个字符。

1:27 SWITCH
$count = 0; switch -File $filepath { default { ++$count } }
1:39 IO.StreamReader
$reader = New-Object IO.StreamReader $filepath
while($reader.ReadLine() -ne $null){ $count++ }
1:42 Linq
$count = [Linq.Enumerable]::Count([System.IO.File]::ReadLines($filepath))
1:46 Get-Content based
$filepath |% {$file_line_count = 0; Get-Content -Path $_ -ReadCount 1000 |% { $file_line_count += $_.Count }}

如果您发现对最快的方法或其他方法进行了优化,请分享。

答案 7 :(得分:0)

MS DOS命令FIND$fileName = 'C:\dirname\filename.txt'
CMD /C ('find /v /c "" "' + $fileName + '"')
docs上可以找到find命令的其他变化形式。