选择的 cmdlet 不接受计算属性中生成的字符串名称



我想生成下表:

AAA BBB CCC
--- --- ---
10  10  10
10  10  10
10  10  10
10  10  10
10  10  10

因此,我使用foreach循环编写以下代码来生成列名:

$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', 'BBB', 'CCC') | foreach {
@{ name = [string]$_; expression = { 10 } }
}
@(1..5) | select -Property $property

或类似以下内容:

$property = @('AAA', 'BBB', 'CCC') | foreach {
@{ name = $_; expression = { 10 } }
}
$property | foreach { $_.name = [string]$_.name }
@(1..5) | select -Property $property

问题是:$_已经是string了。为什么我必须再次将其转换为string?为什么select认为name就是PSObject

为了确认它已经是string,我写了以下代码来打印name的类型:

$property = @('AAA', 'BBB', '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不认为stringstring。就其价值而言,我的$PSVersionTable.PSVersion是:

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      18362  1474    

您看到了PowerShell在幕后使用的附带的、通常不可见的[psobject]包装器的不幸影响。

在您的情况下,因为输入字符串是通过管道提供的,所以它们被封装在中,并作为[psobject]实例存储在哈希表中,这就是问题的原因。

解决方法是通过访问.psobject.BaseObject:来丢弃包装器,这既不明显,也不应该是必要的

$property = 'AAA', 'BBB', 'CCC' | ForEach-Object {
@{ name = $_.psobject.BaseObject; expression = { 10 } }
}
1..5 | select -Property $property

注:

  • 在您的情况下,.psobject.BaseObject的一个更简单的替代方案(请参阅概念性的about_Itrisic Members帮助主题(是调用.ToString(),前提是您需要一个字符串。

  • 要测试给定值/变量是否存在这样的包装,请使用-is [psobject];对于原始代码,以下生成$true,例如:

    • $property[0].name -is [psobject]
    • 但是,请注意,此测试对于[pscustomobject]实例没有意义,因为它总是$true(自定义对象本质上是没有.NET基对象的[psobject]实例,它们只有动态属性(

通常不可见的[psobject]包装器在特定情况下、模糊地导致行为差异,这可以说是一个错误,也是GitHub第5579期的主题


使用.ForEach()阵列方法的更简单、更快的替代方案

$property = ('AAA', 'BBB', 'CCC').ForEach({
@{ name = $_; expression = { 10 } }
})
1..5 | select -Property $property

与管道不同,.ForEach()方法不会将$_封装在[psobject]中,因此不会出现问题,也不需要解决方法。

使用该方法也更快,不过请注意,与管道不同,它必须提前在内存中收集所有输入(显然不是数组文字的问题(。

最新更新