【问题标题】:PowerShell script efficiency advicePowerShell 脚本效率建议
【发布时间】:2020-02-13 14:20:33
【问题描述】:

我有一个电话 .csv,其中包含 2020 年 1 月和 2 月的某些日子的编译数据,每一行都有每个状态花费的日期和时间,因为有人在一天内使用不同的状态,文件每个状态都有一行,我的脚本应该遍历文件,找到最短日期,然后开始在新文件上保存同一天的所有数据,所以我将以 01-01-2020、02-01-2020 和 02-01-2020 的一个文件结束以此类推,但它运行了 15 个小时,仍然是 1/22。

我用于日期的列称为“DateFull”,这是脚本

write-host "opening file" 
$AT= import-csv “C:\Users\xxxxxx\Desktop\SignOnOff_20200101_20200204.csv” 
write-host "parsing and sorting file" 
$go= $AT| ForEach-Object {
        $_.DateFull= (Get-Date $_.DateFull).ToString("M/d/yyyy")
        $_
        }
    
Write-Host "prep day"
$min = $AT | Measure-Object -Property Datefull  -Minimum  

Write-Host $min
$dateString =  [datetime] $min.Minimum
Write-host $datestring

write-host "Setup dates"
$start = $DateString - $today
$start = $start.Days

For ($i=$start; $i -lt 0; $i++)  {
$date = get-date
$loaddate = $date.AddDays($i) 
$DateStr = $loadDate.ToString("M/d/yyyy")
$now = Get-Date -Format HH:mm:ss
write-host $datestr " " $now

#Install-Module ImportExcel #optional import if you dont have the module already
$Check = $at | where {$_.'DateFull' -eq $datestr} 
write-host $check.count
if ($check.count -eq 0 ){}
else {$AT | where {$_.'DateFull' -eq $datestr} | Export-Csv "C:\Users\xxxxx\Desktop\signonoff\SignOnOff_$(get-date (get-date).addDays($i) -f yyyyMMdd).csv" -NoTypeInformation}
}

$at = '' 

【问题讨论】:

  • CSV 有多大? @Jawad 除非源数据很大,否则代码可能有无限循环或意外的 O.
  • 这是一个 50 MB 的文件
  • 您能否从 CSV 中添加几行示例数据以获得所需的结果?
  • 我看到您在几个地方使用了Get-Date,只是为了在for loop 中获取今天的日期。您可以将Get-Date 的值分配给循环外的变量,然后调用该变量来代替cmdlet
  • 另外,[datetime]"11/11/2019"Get-date "11/11/2019" 更有效。 else loop 内部的 IO 操作也可能会减慢你的速度。如果你能重构这个逻辑,那就更好了。

标签: powershell csv


【解决方案1】:

第一个循环没有多大意义。它遍历 CSV 内容并将每一行的日期转换为不同的格式。之后,$go 就不再使用了。

$go= $AT| ForEach-Object {
        $_.DateFull= (Get-Date $_.DateFull).ToString("M/d/yyyy")
        $_
        }

稍后,尝试从未初始化的变量中计算值。 $today 从未定义过。

$start = $DateString - $today

不过,您似乎想以天为单位计算最老记录的年龄。

然后有一个从负天数到零的循环。在每次迭代期间,都会搜索整个 CSV:

$Check = $at | where {$_.'DateFull' -eq $datestr} 

如果有 30 天和 15 000 行,则有 30*15000 = 450 000 次迭代。这具有 O(n^2) 的复杂性,这意味着即使在相对较少的天数和行数内,运行时间也会变得非常高。

下一部分是再次处理相同的数组:

else {$AT | where {$_.'DateFull' -eq $datestr

好吧,搜索条件完全一样,但现在结果被发送到一个文件中。这具有使您的工作加倍的副作用。尽管如此,O(2n^2) => O(n^2),所以至少运行时间并没有以三次或更糟的方式增长。

至于如何解决这个问题,有几件事。如果您根据日期对 CSV 进行排序,则只需一次运行即可对其进行处理。

$at = $at | sort -Property datefull

然后,迭代每一行。由于行是按升序排列的,所以第一个是最旧的。对于每一行,检查日期是否已更改。如果没有,请将其添加到缓冲区。如果有,请保存旧缓冲区并创建一个新缓冲区。

示例不转换 yyyyMMdd 格式的文件名,它假设只有两列 foodatefull 像这样,

$sb = new-object text.stringbuilder
# What's the first date?
$current = $at[0]

# Loop through sorted data
for($i = 0; $i -lt $at.Count; ++$i) {

    # Are we on next date?
    if ($at[$i].DateFull -gt $current.datefull) {
        # Save the buffer
        $file = $("c:\temp\OnOff_{0}.csv" -f ($current.datefull -replace '/', '.') )
        set-content $file $sb.tostring()
        # Pick the current date
        $current = $at[$i]

        # Create new buffer and save data there
        $sb = new-object text.stringbuilder
        [void]$sb.AppendLine(("{0},{1}" -f $at[$i].foo, $at[$i].datefull))    
    } else {
        [void]$sb.AppendLine(("{0},{1}" -f $at[$i].foo, $at[$i].datefull))    
    }
}
# Save the final buffer
$file = $("c:\temp\OnOff_{0}.csv" -f ($current.datefull -replace '/', '.') )
set-content $file $sb.tostring()

【讨论】:

  • 我知道我有一些问题,但没有意识到有那么多哈哈。感谢您的观察,我会在未来考虑到它们,我有一个问题,我试图运行这个脚本只是更改 $file 路径并将我的文件中的列添加到字符串中,但我注意到它重写原始文件,我需要保持原始文件不变并每天创建单独的文件。我怎样才能做到这一点?还是我做错了什么?
  • @Jhilbran 由于示例在c:\temp\OnOff_... 中写入文件,并且您的源具有完全不同的名称和位置,因此罪魁祸首可能在您的代码中。如果您发布一个演示问题的新问题,则更容易调试该问题。不要改变这个,因为 SO 的想法是有 Q&A 对。更改原始问题会使答案变得无关紧要。
猜你喜欢
  • 2019-04-06
  • 2010-10-17
  • 1970-01-01
  • 1970-01-01
  • 2020-04-17
  • 2020-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多