问题描述
我想生成下表:
AAA BBB CCC
--- --- ---
10 10 10
10 10 10
10 10 10
10 10 10
10 10 10
$property = @('AAA','BBB','CCC') | foreach {
@{ name = $_; expression = { 10 } }
}
@(1..5) | select -Property $property
但我收到以下错误,指出 name
不是 string
:
select : The "name" key has a type,System.Management.Automation.PSObject,that is not valid; expected type is System.String.
At line:4 char:11
+ @(1..5) | select -Property $property
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Select-Object],NotSupportedException
+ FullyQualifiedErrorId : DictionaryKeyIllegalValue2,Microsoft.PowerShell.Commands.SelectObjectCommand
要使代码正常工作,我必须将 $_
转换为 string
,如下所示:
$property = @('AAA','CCC') | foreach {
@{ name = [string]$_; expression = { 10 } }
}
@(1..5) | select -Property $property
或者像下面这样:
$property = @('AAA','CCC') | foreach {
@{ name = $_; expression = { 10 } }
}
$property | foreach { $_.name = [string]$_.name }
@(1..5) | select -Property $property
问题是:$_
已经是 string
。为什么我必须再次将其转换为 string
?为什么 select
认为 name
是 PSObject
?
为了确认它已经是 string
,我编写了以下代码来打印 name
的类型:
$property = @('AAA','CCC') | foreach {
@{ name = $_; expression = { 10 } }
}
$property | foreach { $_.name.GetType() }
以下结果确认它已经是一个 string
:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
True True String System.Object
True True String System.Object
我知道还有许多其他更简单的方法来生成表格。但我想了解为什么我必须将 string
转换为 string
才能使代码工作,以及为什么 select
不认为 string
是 {{ 1}}。就其价值而言,我的 string
是:
$PsversionTable.Psversion
解决方法
您会看到 PowerShell 在幕后使用的偶然的、通常不可见 [psobject]
包装器的不幸影响。
在您的情况下,由于输入字符串是通过管道提供的,因此它们被包裹起来并存储在您的哈希表中作为 [psobject]
实例,这就是问题的原因。
解决方法 - 这既不明显也不应该是必要的 - 是通过访问 .psobject.BaseObject
来丢弃包装器:
$property = 'AAA','BBB','CCC' | ForEach-Object {
@{ name = $_.psobject.BaseObject; expression = { 10 } }
}
1..5 | select -Property $property
注意:
在您的情况下,考虑到您想要一个字符串,一个更简单的
.psobject.BaseObject
替代方法(请参阅概念性的 about_Intrinsic Members 帮助主题)是调用.ToString()
。-
要测试给定值/变量是否存在此类包装器,请使用
-is [psobject]
;使用您的原始代码,例如,以下产生$true
:$property[0].name -is [psobject]
- 但是请注意,此测试对于
[pscustomobject]
实例毫无意义,因为它always$true
(自定义对象本质上是[psobject]
实例,没有.NET 基础对象 - 它们只有动态属性)。
通常不可见的 [psobject]
包装器在情况下、模糊地导致行为差异可以说是一个错误和 GitHub issue #5579 。
更简单、更快的替代方案,使用 .ForEach()
array method:
$property = ('AAA','CCC').ForEach({
@{ name = $_; expression = { 10 } }
})
1..5 | select -Property $property
与管道不同,.ForEach()
方法不将$_
包裹在[psobject]
中,所以问题不会出现,也不需要解决方法。
使用该方法也更快,但请注意,与管道不同的是,它必须预先收集内存中的所有输入(显然不是数组文字的问题)。