PowerShell Group-Object 不再将对象拆分为固定大小的集合

PowerShell Group-Object no longer splitting objects into fixed-size collections

我在脚本中使用了这个,我认为它运行良好,但似乎已停止工作:

$testList = @("object 1","object 2","object 3","object 4","object 5")
$counter = 0
$maxSize = 2
$groupedList = $testList | Group-Object { [math]::Floor($counter++ / $maxSize) }
$groupedList
$groupedList | Measure-Object

以前 Measure-Object 会给我计数 3,但现在我收到计数 1。

为什么这不再有效?计数器整数是否不再在 Group-Object 命令中递增?

我认为您的命令没有按预期工作(有关详细信息,请参阅底部部分)。

替换:

{ [math]::Floor($counter++ / $maxSize) }

与:

{ [math]::Floor((Get-Variable -Scope 1 counter).Value++ / $maxSize) }

为了确保它是 caller$counter 变量在脚本块的调用中更新。

注:

  • $script:counter++ 也有效,但前提是调用范围是脚本的 top-level 范围。

  • Get-Variable -Scope 1 counter 显式定位父范围的 $counter 定义,这是调用者的;为简洁起见,您可以省略 -Scope 1,因为默认情况下,最近的祖先作用域中的变量是 returned.

  • Santiago Squarzon 建议 另一个选项:

    • 您可以将 $counter 变量定义为 具有 .Value 属性 的 reference-type 对象而不是整数,最简单的形式是 hashtable:

      $counter = @{ Value = 0 }
      
    • 由于 $counter 现在包含一个 对象引用 ,当 [=93] 时,脚本块中的子范围看到完全相同的对象=]查询父范围的$counter变量的值,并可以更新它的.Value 属性:

      { [math]::Floor($counter.Value++ / $maxSize) }
      

问题是传递给 Group-Object 的(位置)隐含 -Property 参数的脚本块在 child 范围内运行,因此 $counter++ 在每次调用时隐式创建一个 block-local 变量:

  • 也就是说,$counter++ 作用于父作用域中的现有变量;它隐式创建 scope-local copy $counter 变量(使用调用者的当前值),并且 ++ 应用于 .

    • 这可能是令人惊讶的行为 - 当(表面上)分配 变量时隐式创建 local 变量存在于祖先范围内 - 在 .

      中有详细讨论
    • 在相关说明中,这样的脚本块(通常包括计算属性)在子作用域中运行而不是直接在调用者的作用域中运行可能令人惊讶 - 请参阅 GitHub issue #7157

  • 因为你的++是一个post增量,它是原始的0值(从parent-scope 变量)充当除法运算 /.

    的 LHS 操作数
  • 退出脚本块时,局部 $counter 变量超出范围,下一次调用从头开始。

因此,实际上,脚本块的 return 值是一个固定的 0,因此您只会得到 一个 组。


使用 Group-Object 进行“分块”的替代方法:

您使用 Group-Object 实现“分块”(批处理、分区,即将输入分成固定大小的数组)很优雅,但最终效率很低,而且它不是 streaming 解决方案,因为必须预先收集所有输入(在给定情况下,这两个方面都可能无关紧要)。

  • 如果Select-Object directly supported such a feature, which is what GitHub issue #8270-ReadCount参数的形式提议就太好了:

    # WISHFUL THINKING
    PS> 1..5 | Select-Object -ReadCount 2 | ForEach-Object { "$_" }
    1 2
    3 4
    5
    
  • 提供了一个自定义函数,Select-Chunk,它实现了这个功能:

    # Assumes function Select-Chunk from the linked answer is defined.
    PS> 1..5 | Select-Chunk -ReadCount 2 | ForEach-Object { "$_" }
    1 2
    3 4
    5