der*_*rek 4 powershell closures scriptblock
$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
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,我想通过一个变量名访问每个对象的属性。但它不能按预期工作。所以就我而言,如何制作
Expression = {$_.$p}
Run Code Online (Sandbox Code Playgroud)
在职的?
OP 的代码和此答案使用PSv3+语法。[pscustomobject]PSv2 不支持将哈希表转换为 ,但您可以替换[pscustomobject] $_为New-Object PSCustomObject -Property $_.
与过去的许多情况一样,PetSerAl对这个问题提供了简洁(但非常有帮助)的答案;让我详细说明:
您的问题不是您使用变量 ( $p) 来访问属性本身,它确实有效(例如,$p = 'Year'; Get-Date | % { $_.$p })。
相反,问题在于$p脚本块{ $_.$p }直到稍后才会在Format-Table调用的上下文中进行评估,这意味着所有输入对象都使用相同的固定值- 即$p 那个点的值(恰好是$p在foreach循环中分配给的最后一个值)。
最干净和最通用的解决方案是调用.GetNewClosure()脚本块以在脚本块中绑定$p到当时当前的、循环迭代特定的值。
$format += @{ Label = $p; Expression = { $_.$p }.GetNewClosure() }
Run Code Online (Sandbox Code Playgroud)
从文档中(强调添加;更新:引用的段落已被删除,但仍然适用):
在这种情况下,新脚本块在定义闭包的范围内的局部变量上被关闭。换句话说,局部变量的当前值被捕获并包含在绑定到模块的脚本块中。
请注意,自动变量$_在foreach循环内未定义(PowerShell 仅在某些上下文中将其定义为手头的输入对象,例如在管道中传递给 cmdlet 的脚本块中),因此它根据需要保持未绑定。
注意事项:
虽然.GetNewClosure()上面使用的很方便,但它有一个效率低下的缺点,即总是捕获所有局部变量,而不仅仅是需要的局部变量;此外,返回的脚本块在为该场合创建的动态(内存中)模块中运行。
一个更有效率的选择是避免了这个问题-尤其是和也避免了错误(如Windows PowerShell中v5.1.14393.693和PowerShell核心V6.0.0-alpha.15的),其中关闭了局部变量可以突破,即当包围脚本/函数具有 参数与验证特性如[ValidateNotNull()] 和该参数未结合(没有值被传递)[1] -如下,显著更复杂的表达式的帽子提示再次PetSerAl,和Burt_Harris的回答这里
:
$format += @{ Label = $p; Expression = & { $p = $p; { $_.$p }.GetNewClosure() } }
Run Code Online (Sandbox Code Playgroud)
& { ... }创建一个具有自己的局部变量的子作用域。$p = $p然后从其继承的值创建一个局部 $p变量。
要推广此方法,您必须为脚本块中引用的每个变量包含这样的语句。 { $_.$p }.GetNewClosure()然后输出关闭子作用域的局部变量的脚本块(仅$p在本例中)。对于简单的情况,mjolinor 的答案可能会这样做:它通过扩展字符串间接创建一个脚本块,该字符串从字面上合并了当时的当前$p值,但请注意,该方法很难概括,因为仅将变量值字符串化通常不能保证它作为 PowerShell源代码的一部分工作(扩展后的字符串必须计算为它才能转换为脚本块)。
把它们放在一起:
# 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
Run Code Online (Sandbox Code Playgroud)
这产生:
$format += @{ Label = $p; Expression = { $_.$p }.GetNewClosure() }
Run Code Online (Sandbox Code Playgroud)
根据需要:输出列使用指定的列标签,$properties同时包含正确的值。
请注意,为了清楚起见,我如何删除不必要的;实例并替换内置别名%和ft底层 cmdlet 名称。我还分配了不同的age值以更好地证明输出是正确的。
更简单的解决方案,在这种特定情况下:
要按原样引用属性值,无需转换,使用属性名称作为Expression计算属性(列格式哈希表)中的条目就足够了。换句话说:在这种情况下,您不需要[scriptblock]包含表达式的实例( { ... }),只需要[string]包含属性名称的值。
因此,以下方法也有效:
# Use the property *name* as the 'Expression' entry's value.
$format += @{ Label = $p; Expression = $p }
Run Code Online (Sandbox Code Playgroud)
请注意,这种方法恰好避免了原始问题,因为在赋值时$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,这违反了其验证属性。
传递有效值-bar会使问题消失;例如,foo -bar ''。
对于考虑这个的理由的错误:如果函数本身对待$bar在没有的-bar参数值不存在的,所以应该.GetNewClosure()。