$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}
工作?
答案 0 :(得分:4)
OP的代码和此答案使用 PSv3 + 语法。 PSv2不支持将哈希表投射到[pscustomobject]
,但您可以将[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() }
从docs(强调添加):
在这种情况下,新的脚本块将在定义闭包的范围内的 local 变量上关闭。换句话说,当前 local 变量的值被捕获并包含在绑定到模块的脚本块中。
请注意,自动变量$_
在foreach
循环中未定义(PowerShell仅在某些上下文中将其定义为手头的输入对象,例如在传递的脚本块中到管道中的cmdlet),因此它根据需要保持未绑定。
警告:
虽然上面使用的.GetNewClosure()
很方便,但它具有无效的缺点,即总是捕获所有局部变量,而不仅仅是所需的那些变量。
更有效的替代可避免此问题 - 尤其是可避免错误 (自Windows PowerShell v5.1.14393起) .693和PowerShell Core v6.0.0-alpha.15)其中局部变量的闭包可以中断,即封闭脚本/函数有 参数使用验证属性,例如[ValidateNotNull()]
和,该参数未绑定(未传递任何值) [1] - 是以下,显着更复杂的表达帽子的提示再次给PetSerAl和Burt_Harris的回答here
:
$format += @{ Label = $p; Expression = & { $p = $p; { $_.$p }.GetNewClosure() } }
& { ... }
使用自己的局部变量创建子范围。$p = $p
然后从继承的值创建本地 $p
变量。{ $_.$p }.GetNewClosure()
然后输出一个脚本块,该块关闭子范围的局部变量(在这种情况下只是$p
)。对于简单案例,mjolinor's answer可能会:间接通过扩展字符串创建脚本块合并当时的$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
这会产生:
ID Name Age
-- ---- ---
1 abc 3
2 def 4
根据需要:输出列使用$properties
中指定的列标签,同时包含正确的值。
请注意我是如何删除不必要的;
个实例,并使用底层cmdlet名称替换内置别名%
和ft
以获得清晰度。我还指定了不同的age
值,以更好地证明输出是正确的。
更简单的解决方案,在特定的案例中
要引用属性值 as-is , without transformation ,只需使用属性的名称即可计算属性中的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
,违反了其验证属性
传递有效的-bar
值会使问题消失;例如,foo -bar ''
考虑这个 bug 的基本原理:如果函数本身在没有$bar
参数值的情况下将-bar
视为不存在,那么{ {1}}。
答案 1 :(得分:1)
虽然整个方法对于给定的例子似乎是错误的,但正如使其工作的关键是在正确的时间控制变量扩展。在foreach
循环中,$_
为空($_
仅在管道中有效)。您需要等到它进入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
从可扩展字符串创建scriptblock将允许$p
针对每个属性名称进行扩展。转义$_
会将其保留为字符串中的文字,直到它作为脚本块呈现,然后在ForEach-Object
循环中进行评估。
答案 2 :(得分: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))
}
}
}
您需要另一个循环才能将其绑定到数组中的特定项目。 话虽这么说,我认为使用一系列对象将是一个更清晰的方法 - 但我不知道你正在处理什么,确切。