Get-ChildItem -Recurse 报错 System.OutOfMemoryException

3

我在一个大目录上运行了这个函数:

定义

# (searches for all ips within files in the current directory recursively)

function searchips
{
    param(
        [Parameter(Mandatory=$false)][string]$dir = $(pwd)
    )

    ls -Recurse -Force `
    | Select-String -Pattern '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' -AllMatches `
    | ? { 
        $matches = ($_.Matches | Select-Object -Unique)
        return $matches.Count -gt 1 -or $matches[0].Value -ne '127.0.0.1' 
    } `
    | select Path,Matches,FileName,LineNumber,Line `
    | Format-Table -AutoSize `
    | Out-String -Width 4096
}

呼叫

PS C:\path\to\huge> searchips hugeDirectory >> outfile.txt

但是我每次都会遇到这个错误:

out-lineoutput : 引发了类型为 'System.OutOfMemoryException' 的异常。
At C:\Users\myUserName\Documents\WindowsPowerShell\profile.ps1:73 char:2
+     ls -Recurse `
+     ~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [out-lineoutput], OutOfMemoryException
    + FullyQualifiedErrorId : System.OutOfMemoryException,Microsoft.PowerShell.Commands.OutLineOutputCommand

目前,我的PS内存设置如下:

   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Shell
Name Value Type ---- ----- ---- AllowRemoteShellAccess true System.String IdleTimeout 7200000 System.String MaxConcurrentUsers 10 System.String MaxShellRunTime 2147483647 System.String MaxProcessesPerShell 25 System.String MaxMemoryPerShellMB 9000000 System.String MaxShellsPerUser 30 System.String

有什么想法吗?


1
你发布的代码不是72行。错误信息也与代码不符。 - Ansgar Wiechers
2
我同意@AnsgarWiechers的观点,错误信息引用了某种“out-lineoutput”函数,而您既没有显示其定义也没有调用它。此外,您展示的“内存设置”是针对WSMan的,如果您正在使用PowerShell远程控制,则会有所影响,但您并没有(并且该错误消息与此处显示的不同)。 - briantist
1
请创建一个 [mcve] 并展示样例代码以及它所产生的错误信息。 - Ansgar Wiechers
1
这个问题无法验证,因为a)错误信息仍然与您的代码不匹配(不仅仅是因为行号),b)我无法在两个不同的系统上重现您描述的行为(Windows 7和Server 2012 R2,都使用PowerShell v4)。您是否尝试在纯净的PowerShell中运行您发布的代码(没有配置文件)? - Ansgar Wiechers
@AnsgarWiechers,我会快速用普通代码尝试一下。 - Kellen Stuart
显示剩余7条评论
1个回答

3
问题是由 Out-String 引起的。该 cmdlet 将它的输入合并成一个字符串(然后将该字符串返回给调用者)。为此,它必须在内存中收集所有输出。现在想想还是很明显的。
我建议使用 ConvertTo-Csv 而不是 Format-Table | Out-String。这样可以避免内存耗尽,并且更容易将输出用于进一步处理。
function Find-IPAddresses {
    Param(
        [Parameter(Mandatory=$false)]
        [ValidateScript({Test-Path -LiteralPath $_})]
        [string]$dir = $PWD.Path
    )

    Get-ChildItem $dir -Recurse -Force |
        Select-String -Pattern '\d{1,3}(?:\.\d{1,3}){3}' -AllMatches |
        Where-Object {
            $matches = ($_.Matches | Select-Object -Unique)
            $matches.Count -gt 1 -or $matches[0].Value -ne '127.0.0.1'
        } |
        Select-Object Path, Matches, FileName, LineNumber, Line |
        ConvertTo-Csv -NoType
}

Find-IPAddresses 'C:\some\folder' > 'outfile.csv'

或者根本不返回文本输出。只需返回对象列表,并在调用函数并知道要对数据执行什么操作时进行所有格式化/输出:
function Find-IPAddresses {
    Param(
        [Parameter(Mandatory=$false)]
        [ValidateScript({Test-Path -LiteralPath $_})]
        [string]$dir = $PWD.Path
    )

    Get-ChildItem $dir -Recurse -Force |
        Select-String -Pattern '\d{1,3}(?:\.\d{1,3}){3}' -AllMatches |
        Where-Object {
            $matches = ($_.Matches | Select-Object -Unique)
            $matches.Count -gt 1 -or $matches[0].Value -ne '127.0.0.1'
        } |
        Select-Object Path, Matches, FileName, LineNumber, Line
}

Find-IPAddresses 'C:\some\folder' | Export-Csv 'outfile.csv' -NoType

它奏效了。原来是因为它找到了350,000多个IP地址,可能将所有这些都保存在内存中而导致崩溃。谢谢,现在脚本运行速度快多了。 - Kellen Stuart
另外,对于该函数的参数部分我搞错了,我以为如果你提供一个目录它会使用那个目录而不是默认使用 pwd。必须修复这个问题。 - Kellen Stuart
没关系。我应该更早地注意到这个问题。 - Ansgar Wiechers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接