4
$var =@(  @{id="1"; name="abc"; age="1"; },
          @{id="2"; name="def"; age="2"; } );
$properties = @("ID","Name","Age") ;
$format = @();
foreach ($p  in $properties)
{
    $format += @{label=$p ; Expression = {$_.$p}} #$_.$p is not working!
}
$var |% { [PSCustomObject]$_  } | ft $format

在上面的例子中,我想通过一个变量名来访问每个对象的属性。但它不能按预期工作。所以在我的情况下,如何制作

Expression = {$_.$p}

在职的?

4

3 回答 3

6

OP 的代码和这个答案使用PSv3+语法。[pscustomobject]PSv2 不支持将哈希表转换为,但您可以替换[pscustomobject] $_New-Object PSCustomObject -Property $_.

与过去的许多情况一样,PetSerAl以简洁(但非常有帮助)的评论提供了答案;让我详细说明:

您的问题在于您使用变量 ( $p) 来访问属性本身,它确实有效(例如,$p = 'Year'; Get-Date | % { $_.$p })。

相反,问题是$pin script 块{ $_.$p }直到稍后才被评估,在Format-Table调用的上下文中,这意味着相同的固定值用于所有输入对象- 即该$p 的值(恰好是循环中分配给$p的最后一个值)。foreach

最干净和最通用的解决方案是调用.GetNewClosure()脚本块以在脚本块中绑定$pthen-current, loop-iteration-specific value

$format += @{ Label = $p; Expression = { $_.$p }.GetNewClosure() }

文档中(强调添加;更新:引用的段落已被删除,但仍然适用):

在这种情况下,新的脚本块在定义闭包的范围内的局部变量上关闭。换句话说,局部变量的当前值被捕获并包含在绑定到模块的脚本块中。

请注意,自动变量$_在循环内未定义foreach(PowerShell 仅在某些上下文中将其定义为手头的输入对象,例如在传递给管道中的 cmdlet 的脚本块中),因此它根据需要保持为unbound

注意事项

  • 虽然.GetNewClosure()上面使用的方法很方便,但它的缺点是总是捕获所有局部变量,而不仅仅是需要的局部变量;此外,返回的脚本块在为该场合创建的动态(内存中)模块中运行。

  • 避免此问题的更有效的替代方案 - 尤其是还避免了一个错误(从 Windows PowerShell v5.1.14393.693 和 PowerShell Core v6.0.0-alpha.15 开始),其中局部变量的闭包可能会中断,即当封闭的脚本/函数有一个 带有验证属性的参数例如该参数未绑定(未传递任何值)[ValidateNotNull()] [1] - 下面是更复杂的表达式,再次向 PetSerAl 致敬,以及 Burt_Harris 的答案

      $format += @{ Label = $p; Expression = & { $p = $p; { $_.$p }.GetNewClosure() } }
    
    • & { ... }创建一个具有自己的局部变量的子作用域。
    • $p = $p然后从其继承的值创建一个局部 变量。 为了概括这种方法,您必须为脚本块中引用的每个变量包含这样的语句$p
    • { $_.$p }.GetNewClosure()然后输出一个关闭子作用域的局部变量的脚本块(仅$p在本例中)。
    • 该错误已被报告为PowerShell Core GitHub 存储库中的一个问题,并且已得到修复——我不清楚该修复程序将发布哪些版本。
  • 对于简单的情况,mjolinor 的答案可能是:它通过扩展字符串间接创建一个脚本块,该字符串包含当时的当前,但请注意,该方法难以概括,因为仅对变量值进行字符串化通常不能保证它作为 PowerShell源代码的一部分工作(扩展字符串必须对其进行评估才能转换为脚本块)。$p

把它们放在一起:

# Sample array of hashtables.
# Each hashtable will be converted to a custom object so that it can
# be used with Format-Table.
$var = @(  
          @{id="1"; name="abc"; age="3" }
          @{id="2"; name="def"; age="4" }
       )

# The array of properties to output, which also serve as
# the case-exact column headers.
$properties = @("ID", "Name", "Age")

# Construct the array of calculated properties to use with Format-Table: 
# an array of output-column-defining hashtables.
$format = @()
foreach ($p in $properties)
{
    # IMPORTANT: Call .GetNewClosure() on the script block
    #            to capture the current value of $p.
    $format += @{ Label = $p; Expression = { $_.$p }.GetNewClosure() }
    # OR: For efficiency and full robustness (see above):
    # $format += @{ Label = $p; Expression = & { $p = $p; { $_.$p }.GetNewClosure() } }
}

$var | ForEach-Object { [pscustomobject] $_ } | Format-Table $format

这产生:

ID Name Age
-- ---- ---
1  abc  3  
2  def  4  

根据需要:输出列使用中指定的列标签,$properties同时包含正确的值。

请注意,为了清楚起见,我如何删除了不必要;的实例并替换了内置别名%ft底层 cmdlet 名称。我还分配了不同的age值以更好地证明输出是正确的。


更简单的解决方案,在这种特定情况下:

要按原样引用属性值,无需转换,只需使用属性名称作为Expression计算属性(列格式哈希表)中的条目即可。换句话说:在这种情况下,您不需要[scriptblock]包含表达式{ ... }( ) 的实例,只需要[string]包含属性名称的值。

因此,以下方法也可以:

# Use the property *name* as the 'Expression' entry's value.
$format += @{ Label = $p; Expression = $p }

请注意,这种方法恰好避免了原始问题,因为在赋值时进行了$p评估,因此捕获了特定于循环迭代的值。


[1] 重现:调用function foo { param([ValidateNotNull()] $bar) {}.GetNewClosure() }; foo失败,出现错误 也就是说,尝试将未绑定的参数值 -变量 - 包含在闭包中,这显然默认为,这违反了它的验证属性。 传递一个有效值会使问题消失;例如,。将此视为错误 的理由:如果函数本身在没有参数值的情况下将其视为不存在,那么应该如此。.GetNewClosure()Exception calling "GetNewClosure" with "0" argument(s): "The attribute cannot be added because variable bar with value would no longer be valid."
-bar$bar$null
-barfoo -bar ''
$bar-bar.GetNewClosure()

于 2017-02-12T15:08:50.610 回答
1

虽然对于给定的示例,整个方法似乎被误导了,但作为使其工作的练习,关键将是在正确的时间控制变量扩展。在您的foreach循环中,$_为 null ($_仅在管道中有效)。您需要等到它进入Foreach-Object循环才能尝试评估它。

这似乎只需要最少的重构:

$var =@(  @{id="1"; name="abc"; age="1"; },
      @{id="2"; name="def"; age="2"; } );
$properties = @("ID","Name","Age") ;
$format = @();
foreach ($p  in $properties)
{
    $format += @{label=$p ; Expression = [scriptblock]::create("`$`_.$p")} 
}
$var | % { [PSCustomObject] $_ } | ft $format

从可扩展字符串创建脚本块将允许$p扩展每个属性名称。转义$_会将其保留为字符串中的文字,直到将其呈现为脚本块然后在ForEach-Object循环中进行评估。

于 2017-02-12T15:31:24.743 回答
0

访问 HashTables 数组中的任何内容都会有点挑剔,但是您的变量扩展是这样更正的:

    $var =@(  @{id="1"; name="Sally"; age="11"; },
          @{id="2"; name="George"; age="12"; } );
$properties = "ID","Name","Age"
$format = @();

$Var | ForEach-Object{
    foreach ($p  in $properties){
        $format += @{
            $p = $($_.($p))
        }
    }
}

您需要另一个循环才能将其绑定到数组中的特定项目。话虽如此,我认为使用对象数组将是一种更清洁的方法 - 但我不知道你在处理什么,确切地说。

于 2017-02-12T04:20:49.133 回答