对管道中的 Powershell 对象进行分组和排序

Group and rank Powershell objects in the pipeline

我正在尝试对 powerhell 管道中的一组对象进行排名。

Get-ChildItem "*.csv" | 
Select-object fullname, Length, @{N = "Environment"; E = {
    if ($_.fullname -like "*Dev*") {
        "DEV"
    }
    elseif ($_.fullname -like "*PROD*") {
        "PROD"
    }
    elseif ($_.fullname -like "*UAT*") {
        "UAT"
    }
}} | Sort-Object -Property Environment, Length 

我想根据组(环境和长度)在管道中添加具有以下值的 Rank 属性。有没有像SQL ranking function

这样的内置powershell
FullName                               Length Environment Rank 
--------                               ------ ----------- ----
C:\Temp\ReportDEV-20171210_210653.csv    3065 DEV         1
C:\Temp\ReportDEV-20171210_211041.csv    9116 DEV         2
C:\Temp\ReportDEV-20171210_190100.csv   76286 DEV         3
C:\Temp\ReportDEV-20171210_200229.csv  511546 DEV         4
C:\Temp\ReportPROD-20171210_210349.csv   2835 PROD        1
C:\Temp\ReportPROD-20171210_210754.csv   8897 PROD        2
C:\Temp\ReportPROD-20171210_184729.csv  43850 PROD        3
C:\Temp\ReportPROD-20171210_191133.csv 213202 PROD        4
C:\Temp\ReportUAT-20171210_210554.csv    3065 UAT         1
C:\Temp\ReportUAT-20171210_210920.csv    9116 UAT         2
C:\Temp\ReportUAT-20171210_185308.csv   57244 UAT         3
C:\Temp\ReportUAT-20171210_193211.csv  306154 UAT         4

您可以使用 for 循环简单地将排名添加到输出对象。

ForEach-Object 接受脚本块作为第一个参数中的开始脚本。我用它来声明计数器,然后是用于处理脚本的第二个参数,除了创建所需的输出对象外,我还增加了计数器。

示例 1 - 行的行号:

Get-ChildItem "C:\SomeDirectory" -Recurse -File | Sort-Object Name |
    ForEach-Object {$i = 1} {
        New-Object psObject -Property @{Name= $_.Name; Rank= $i++;}
}

结果

Name           Rank
----           ----
Product1-1.txt    1
Product1-2.txt    2
Product1-3.txt    3
Product2-1.txt    4
Product2-2.txt    5

示例 2 - 组中行的行号:

Get-ChildItem "C:\SomeDirectory" -Recurse -File | Group-Object DirectoryName | 
    ForEach-Object {
        $_.Group | ForEach-Object {$i = 1} {
            New-Object psObject -Property @{
                 GroupName= $_.Directory.Name; Name= $_.Name;Rank= $i++;
            }
        }
    }

结果

GroupName Name           Rank
--------- ----           ----
Category1 Product1-1.txt    1
Category1 Product1-2.txt    2
Category1 Product1-3.txt    3
Category2 Product2-1.txt    1
Category2 Product2-2.txt    2

遗憾的是,PowerShell 提供开箱即用的排名功能,因此需要额外的工作:

$rank = 0
$prevEnv = ''
Get-ChildItem *.csv | Select-Object FullName, Length, @{ 
    n='Environment'; e={ $_.Name -replace '.*(DEV|PROD|UAT).*', '' } } |
  Sort-Object Environment, Length | Select-Object *, @{
      n='Rank'; e={
        ++$rank
        if ($prevEnv -ne $_.Environment) { 
          $rank = 1
          Set-Variable -Scope 1 prevEnv $_.Environment
        }
        Set-Variable -Scope 1 rank $rank
        $rank
    }
  }

注意在 .Environment 属性 中将输入文件名映射到环境名称的简化方法,使用 -replace 和正则表达式和捕获组来从文件名中提取感兴趣的标记。

  • 通过Select-Object添加.Environment属性后,对象先按.Environment排序,再按.Length排序(文件大小(以字节为单位)。

  • 然后通过另一个 Select-Object 调用为生成的对象提供 .Rank 属性,其值是基于 1 的序列号每当遇到新的 .Environment 值时都会重置,实际上相当于每个环境的文件大小排名。

  • 注意需要 Set-Variable 才能修改 $rank$prevEnv 变量,因为这些变量必须在管道范围内维护一个整体,以便它们在连续的 Select-Object 调用中持续存在(相比之下,例如,在分配给键 e 的脚本块内,一个本地 $rank 变量将被限定为该脚本仅块)。


或者,使用Group-Object(更简洁,但效率较低):

Get-ChildItem *.csv | Select-Object FullName, Length, @{ 
    n='Environment'; e={ $_.Name -replace '.*(DEV|PROD|UAT).*', '' }
} |
    Group-Object Environment | ForEach-Object {
      $rank = 0
      $_.Group | Sort-Object Length | Select-Object *, @{ 
       n='Rank'; e={ Set-Variable -Scope 1 rank ($rank+1); $rank } 
      }
    }
  • Group-Object Environment 按新添加的 .Environment 属性.

  • 对输入对象进行分组
  • ForEach-Object 然后遍历所有结果组。

    • 每个组的 ($_) 个成员通过 .Group 属性 枚举,并按 .Length.
    • 排序
    • 第二个 Select-Object 调用然后循环遍历长度排序的组成员并生成具有 .Rank 属性 反映基于 1 的扩充输出对象基于文件大小(长度)的组相对排名按升序排列。

    • 注意需要 Set-Variable 来递增 $rank 变量,因为该变量必须在 ForEach-Object 主体级别维护存在于连续的 Select-Object 次调用中(在分配给键 e 的脚本块内,本地 $rank 变量的范围仅限于该脚本块)。