powershell - 将数组属性组合在一起

时间:2018-02-18 12:32:44

标签: powershell

我有一些包含大量行(数千甚至更多)的数组。它有像“列 XXXXX

这样的列

行示例:

Group_name = "proxy_users"
Column1   = "domain\Igor"
Column2   = null
.......
Column989 = 'domain\Andrew'
Column999 = 'domain\Mike'

创建新变量的正确和快速方法是什么,它将是'ColumnXXX'忽略'null'值的总和?

喜欢“domain \ igor,domain \ Andrew,domain \ mike”

我可以像$ group一样使用smth select -Property“Column *”...但是如何求和以及如何忽略null?

2 个答案:

答案 0 :(得分:2)

您可以使用ex列出所有属性。 $_.psobject.properties,过滤掉您想要的内容并使用-join来组合这些值。实施例

$o = [pscustomobject]@{
    Group_Name = "Test"
    Column1 = "Foo"
    Column2 = $null
    Column3 = "1"
}, [pscustomobject]@{
    Group_Name = "Test2"
    Column1 = $null
    Column2 = "Bar"
    Column3 = "2"
}

#Add property as constant
$o | ForEach-Object {
    $_ | Add-Member -NotePropertyName Summed -NotePropertyValue (($_.psobject.Properties | Where-Object { $_.Name -ne 'Group_name' -and $_.Value -ne 'null' } | Select-Object -ExpandProperty Value) -join '' )
}
$o | ft

或者您可以使用ScriptProperty计算每次调用的值

#Remember to exclude itself to avoid infinite recursion
$o | Add-Member -MemberType ScriptProperty -Name Summed -Value {($this.psobject.Properties | Where-Object { $_.Name -ne 'Group_name' -and $_.Name -ne 'Summed' -and $_.Value -ne 'null' } | Select-Object -ExpandProperty Value) -join '' }
$o | ft

结果:

Group_Name Column1 Column2 Column3 Summed
---------- ------- ------- ------- ------
Test       Foo             1       Foo1
Test2              Bar     2       Bar2

作为替代方案,如果实际调用$_.Name -like 'Column*' -and $_.Value -ne 'null',则可以使用ColumnXXX作为属性过滤器。

答案 1 :(得分:0)

使用更简洁的PSv4 +版本补充Frode F.'s elegant ScriptProperty-based solution,由于避免在属性定义脚本块中使用cmdlet(管道)而更有利于运算符(包含大量输入对象,这可能很重要):

$o | Add-Member -PassThru -MemberType ScriptProperty -Name Joined -Value { 
 @($this.psobject.Properties.Where({$_.Name -match 'Column*'}).Value) -ne 'null' -join ', ' 
}

请注意使用:

  • .Where()集合运算符(PSv4 +),是Where-Object cmdlet的更快替代。

  • 成员枚举(PSv3 +),其中访问集合级别的属性会生成元素的数组 '属性值;
    例如,$this.psobject.Properties.Name会生成.Name集合中所有元素的$this.psobject.Properties属性值。

  • 将比较运算符-ne应用于数组值 LHS,在这种情况下,运算符充当过滤器:运算符应用于每个元素,匹配元素作为数组返回;请注意LHS周围的@(...),这可确保将其视为数组,即使它恰好只返回一个值。

根据问题中的样本数据,上述结果(寻找属性Joined):

Group_name : proxy_users
Column1    : domain\Igor
Column2    : null
Column989  : domain\Andrew
Column999  : domain\Mike
Joined     : domain\Igor, domain\Andrew, domain\Mike

通过上述优化,您甚至可以考虑构建包含所有输入对象属性(Select-Object)的 new 自定义对象实例的更简单的*解决方案一个新的计算属性,其中包含感兴趣的组合列值:

$o | Select-Object *, @{ n='Joined'; e={ 
  @(($_.psobject.Properties.Where({$_.Name -match 'Column*'})).Value) -ne 'null' -join ', ' } 
}

这个解决方案比上面那个慢,但不是很多 需要注意的是,如果收集内存中的所有输出,新构造的对象会占用原始空间。

可选阅读:性能比较;管道使用的影响:

底部的代码来自Frode和我的答案的各种方法。

这是我的机器的时间示例 - 绝对数字并不重要,但它们的比例是(虽然我不知道CPU核心数和磁盘速度等因素会起作用) - 输入集大小是1000个对象:

Approach                                 TotalSeconds
--------                                 ------------
add-member w/ scriptproperty - operators 0.35
select-object w/ noteproperty            0.40
add-member w/ scriptproperty - pipeline  0.72
add-member w/ noteproperty - pipeline    0.98

结论:

  • 在属性定义中使用管道的解决方案明显较慢,大约为2倍,即使输入计数较大,也不会发生变化。

  • 基于优化Select-Object的解决方案创建对象(而不是向输入对象添加属性)仅比优化的Add-Member解决方案稍慢

测试的源代码:

# The number of input objects to test with.
# Adjust to experiment.
$objCount = 1000

Write-Verbose -Verbose "# of input objects: $objCount"

$ndx = 0; $approaches = 'select-object w/ noteproperty', 
                        'add-member w/ scriptproperty - operators', 
                        'add-member w/ scriptproperty - pipeline', 
                        'add-member w/ noteproperty - pipeline'
$tempFile = New-TemporaryFile # requires PSv5+
$(foreach($approach in $approaches) {

  # Create test collection (recreate it in every iteration to support repeated Add-Member operations)
  $al = [System.Collections.ArrayList]::new()
  for ($i = 0; $i -lt $objCount; ++$i) {
    $null = $al.add([pscustomobject] @{ one = 1; Column1 = 'col1'; Column2 = 'col2'; Column3 = 'null'; Column4 = 'col4' })
  }

  Measure-Command {

    Write-Verbose -Verbose "Running: $approach..."
    switch ($ndx) {
      0 { # select-object w/ noteproperty
        $al | Select-Object *, @{ n='Joined'; e={ @(($_.psobject.Properties.Where({ $_.Name -match 'Column*'})).Value) -ne 'null' -join ', ' } } | 
          Export-Csv $tempFile  
        break
      }

      1 { # add-member w/ scriptproperty - operators
        $al | Add-Member -PassThru -MemberType ScriptProperty -Name Joined -Value { @($this.psobject.Properties.Where({ $_.Name -match 'Column*'}).Value) -ne 'null' -join ', ' } |
          Export-Csv $tempFile          
        break
      }

      2 { # add-member w/ scriptproperty - pipeline
        $al | Add-Member -PassThru -MemberType ScriptProperty -Name Joined -Value { ($this.psobject.Properties | Where-Object { $_.Name -match 'Column*'  -and $_.Value -ne 'null' } | Select-Object -ExpandProperty Value) -join ', ' }  | 
         Export-Csv $tempFile
         break
        }

        3 { # add-member w/ noteproperty - pipeline; requires an intermediate ForEach-Object call
          $al | ForEach-Object {
            $_ | Add-Member -PassThru -NotePropertyName Joined -NotePropertyValue (($_.psobject.Properties | Where-Object { $_.Name -match 'Column*' -and $_.Value -ne 'null' } | Select-Object -ExpandProperty Value) -join ', ' )
          } |
            Export-Csv $tempFile
        break
      }
      default { Throw "What are you doing here?" }
    }

    # Import-Csv $tempFile | Select-Object -First 1 Joined | Write-Verbose -Verbose

    ++$ndx

  } | Select-Object @{ n='Approach'; e={ $approach }}, @{ n='TotalSeconds'; e={ '{0:N2}' -f $_.TotalSeconds } }

}) | Sort-Object { [double] $_.TotalSeconds }

Remove-Item $tempFile