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

Generated string names in Calculated Properties aren't accepted by select cmdlet

我想生成以下内容 table:

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 认为 namePSObject

为了确认它已经是一个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       

我知道还有许多其他更简单的方法可以生成 table。但我想了解为什么我必须将 string 转换为 string 才能使代码正常工作,以及为什么 select 不认为 stringstring。对于它的价值,我的 $PSVersionTable.PSVersion 是:

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

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

在您的情况下,因为输入字符串是通过管道提供的,所以它们被包装并存储为 作为 [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] 包装器 在特定情况下,模糊地 导致行为差异可以说是一个 bugGitHub issue #5579.

的主题

更简单、更快速的替代方法,使用 .ForEach() array method:

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

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

使用该方法也更快,但请注意,与管道不同,它必须预先将所有输入收集到内存中(数组文字显然不是问题) .