【问题标题】:Adding multiple rows to CSV file at once through PowerShell通过 PowerShell 一次将多行添加到 CSV 文件
【发布时间】:2017-01-15 16:13:25
【问题描述】:

背景
我一直在查看 Stack 上的几篇帖子,只能找到“如何将单行数据添加到 CSV 文件”的答案(尤其是 this one)。虽然它们很好,但它们仅指从内存中添加单个条目的特定情况。假设我有 100,000 行要添加到 CSV 文件中,那么如果我将每一行都写入文件,查询速度将会慢几个数量级。我想将所有内容保存在内存中会更快,并且一旦我构建了一个包含我要添加的所有数据的变量,然后才将其写入文件。

现状
我有从客户那里收到的包含大约一百万行的日志文件。其中一些行以日期时间和服务器使用的内存量开头。为了更好地了解内存使用情况,我想使用此信息绘制一段时间内的内存使用情况。 (注意:是的,最好的解决方案是要求开发人员添加此信息,因为我们需要此信息很常见,但由于我们还没有,我需要使用我得到的信息)

我能够读取日志文件,提取内容,创建两个变量 $timeStamp 和 $memoryUsage 来查找所有相关条目。当我尝试将其添加到自定义PSObject 时出现问题。似乎使用 $csvObject += $newRow only adds a pointer to the $newRow variable 而不是实际的行本身。这是我目前得到的代码:

$header1 = "Time Stamp"
$header2 = "Memory Usage"

$csvHeaders = @"
$header1;$header2
"@

# The following two lines are a workaround to make sure that the $csvObject becomes a PSObject that matches the output I'm trying to achieve.
$csvHeaders | Out-File -FilePath $csvFullPath
$csvObject = Import-Csv -Path $csvFullPath -Delimiter ";"

foreach ($TraceFile in $traceFilesToLookAt) {
    $curTraceFile = Get-Content $TraceFile.FullName
    Write-Host "Starting on file: $($TraceFile.Name)`n"

    foreach ($line in $curTraceFile) {
        try {
            if (($line.Substring(4,1) -eq '-') -and ($line.Substring(7,1) -eq '-')) {
                $TimeStamp = $line.Split("|",4)[0]
                $memoryUsage = $($line.Split("|",4)[2]).Replace(",","")

                $newRow = New-Object PSObject -Property @{
                    $header1 = $TimeStamp;
                    $header2 = $memoryUsage
                }
                $reorderedRow = $newRow | Select-Object -Property $header1,$header2

                $reorderedRow | Export-Csv -Path $csvFullPath -Append -Delimiter ";"
            }
        } catch {
            Out-Null
        }

这很好用,因为它每次在 CSV 文件中找到一行时都会附加该行。问题是效率不高。

最终目标
理想情况下,我想通过以下方式解决它:

$newRow = New-Object PSObject -Property @{
    $header1 = $TimeStamp;
    $header2 = $memoryUsage
}
$rowsToAddToCSV += $newRow 

然后在最后一步做:

$rowsToAddToCSV | Export-Csv -Path $csvFullPath -Append -Delimiter ";"

我无法为此创建任何形式的解决方法。除其他外,PowerShell 告诉我 op_Addition 不是对象的一部分,我尝试导出的对象(行的集合)与 CSV 文件等不匹配。

【问题讨论】:

    标签: powershell csv


    【解决方案1】:

    在循环中将数千个项目附加到数组中的任何事情都必然会表现不佳,因为每次附加一个项目时,都会重新创建数组,其大小会增加一,复制所有现有项目,并且然后将新项目放入新的空闲槽中。

    您不能简单地做这样的事情有什么特别的原因吗?

    $traceFilesToLookAt | ForEach-Object {
        Get-Content $_.FullName | ForEach-Object {
            if ($_.Substring(4, 1) -eq '-' -and $_.Substring(7, 1) -eq '-') {
                $line = $_.Split('|', 4)
                New-Object PSObject -Property @{
                    'Time Stamp'   = $line[0]
                    'Memory Usage' = $line[2].Replace(',', '')
                }
            }
        }
    } | Export-Csv -Path $csvFullPath -Append -Delimiter ";"
    

    正则表达式匹配可能是从输入文件中提取时间戳和内存使用情况的更优雅的方法,但我将把它作为练习留给你。

    【讨论】:

    • 我能想到的唯一原因是我完全忘记了使用管道,我对此没有任何解释:) 非常感谢!我将在我的环境中验证这一点,但我有一个问题:我认为在创建 New-Object PSObject 时必须指定名称或变量,但我发现这里不是这种情况。这是因为它正在通过管道传输,还是您可以始终在内存中使用 New-Object PSObject 而不将其保存/存储到变量中?
    • 在完成工作时标记为已回答。为了好玩,我在两个版本中使用 Measure-Command 尝试了您的建议,一个带有 $ErrorActionPreference = 'SilentlyContinue' 没有 try/catch,一个带有 'Continue' 使用 try/catch。查询和输出行的时间相同,但是使用 SilentlyContinue 而不使用 try/catch 文件大小是原来的两倍。我猜如果没有 try/catch 它 - 无论出于何种原因 - 都会变成一个文本文件而不是 .csv 文件(try/catch 在第一行获得#TYPE System.Management.Automation.PSCustomObject),但这可能是另一个讨论:)
    • 您始终可以使用New-Object 而不捕获其输出。但是,如果您不在管道中,则必须在变量中捕获对象,否则它只会显示在控制台上,您以后将无法使用它。 #TYPE ... 标头来自 Export-Csv。添加参数-NoType 来抑制它。
    • 我见过很多包含 -NoTypeInformation 的示例,但从未反映过它的作用。再次感谢您,稍后我将考虑使用某种形式的 RegEx 进行字符串操作:)
    猜你喜欢
    • 2012-04-06
    • 1970-01-01
    • 1970-01-01
    • 2019-10-05
    • 1970-01-01
    • 1970-01-01
    • 2017-06-07
    • 1970-01-01
    • 2019-11-26
    相关资源
    最近更新 更多