2

我想生成下表:

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

但是我收到以下错误,说nameis not a 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

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

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

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

1 回答 1

4

您会看到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 成员帮助主题)将是 call .ToString(),因为您需要一个字符串。

  • 要测试给定值/变量是否存在此类包装器,请使用-is [psobject]; 使用您的原始代码,例如,以下会产生$true

    • $property[0].name -is [psobject]
    • 但是请注意,此测试对于始终[pscustomobject]存在的实例毫无意义(自定义对象本质上是没有 .NET 基础对象的实例 - 它们只有动态属性)。 $true[psobject]

通常不可见的[psobject]包装器在情境中隐晦地导致行为差异可以说是一个错误,也是GitHub 问题 #5579的主题。


使用.ForEach()数组方法更简单快捷的替代方法:

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

与管道不同,该.ForEach() 方法不包含在 中,因此不会$_[psobject]出现问题,也不需要变通方法。

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

于 2021-06-04T20:45:40.807 回答