如何初始化一个自定义对象数组

80

首先,由于这与我的问题有关,我将注意到我已经在PowerShell中使用过XML,并且喜欢如何快速地将数据从XML文件读入自定义对象的数组中。例如,如果我有以下XML文件:

<stuff>
 <item name="Joe" age="32">
  <info>something about him</info>
 </item>
 <item name="Sue" age="29">
  <info>something about her</info>
 </item>
 <item name="Cat" age="12">
  <info>something else</info>
 </item>
</stuff>

如果我简单地阅读它,就像这样:

[xml]$myxml = Get-Content .\my.xml

那么我可以这样获取我的项目数组:

[array]$myitems = $myxml.stuff.Item
$myitems

name   age  info
----   ---  ----
Joe    32   something about him
Sue    29   something about her
Cat    12   something else

那么,现在我的问题是:

如何创建自定义对象数组的类似结构,并在我的脚本中对它们进行初始化,而不需要读取一个文件?

我可以通过大量循环和/或大量创建/初始化单个对象,然后逐个添加到数组中来实现这一点……

但似乎应该有一种更简单的方式来执行此创建/初始化。请注意,关键在于我的自定义对象具有两个以上的元素(否则,我将使用哈希)。

我甚至尝试过创建一个大的XML字符串,并使用Select-XML,但我无法得到正确的语法(如果那是正确的方法的话)。


2
所以我认为你在问的是如何将你动态创建的对象数组以整齐的表格形式显示,就像你在上面使用xml时所做的那样? - Joel Smith
很遗憾,您的要求包括不从文件中读取。Import-Csv会产生一个pscustom对象数组。 - Walter Mitty
9个回答

124
我会按照这个思路做一些事情。
$myitems =
@([pscustomobject]@{name="Joe";age=32;info="something about him"},
[pscustomobject]@{name="Sue";age=29;info="something about her"},
[pscustomobject]@{name="Cat";age=12;info="something else"})

请注意,这仅适用于PowerShell 3版本。但由于您在问题中没有提及版本,我假设对您来说这并不重要。
更新:
评论中提到,如果您执行以下操作:
$younger = $myitems | Where-Object { $_.age -lt 20 } 
Write-Host "people younger than 20: $($younger.Length)" 

你不会得到你期望的1。当返回一个单独的pscustomobject时,就会发生这种情况。对于PowerShell中的大多数其他对象来说,这并不是一个问题,因为它们有用于Length和Count的代理属性。不幸的是,pscustomobject没有这些属性。这个问题在PowerShell 6.1.0中已经修复。你可以通过使用@()运算符来解决这个问题。
$younger = @($myitems | Where-Object { $_.age -lt 20 })

更多背景信息请参见这里这里

更新 2

在 PowerShell 5 中,可以使用来实现类似的功能。例如,您可以像这样定义一个类:

class Person {
    [string]$name
    [int]$age
    [string]$info; `
`
    Person(
    [string]$name,
    [int]$age,
    [string]$info
    ){
        $this.name = $name
        $this.age = $age
        $this.info = $info
    }
}

这里的反引号是为了让你可以将其复制粘贴到命令行中,但在脚本中并不需要。一旦类被定义,你可以按照通常的方式创建一个数组:
$myitems =@([Person]::new("Joe",32,"something about him"),
[Person]::new("Sue",29,"something about her"),
[Person]::new("Cat",12,"something else"))

请注意,即使在PowerShell 5中也不会出现先前更新中提到的缺点。
第3次更新:
您还可以使用哈希表初始化类对象,与第一个示例类似。为此,您需要确保定义了默认构造函数。如果您没有提供任何构造函数,系统将为您创建一个默认构造函数,但如果您提供了非默认构造函数,将不会有默认构造函数,因此您需要明确定义它。以下是使用自动创建的默认构造函数的示例:
class Person {
    [string]$name
    [int]$age
    [string]$info;
}

使用这个,你可以:
$person = @([Person]@{name='Kevin';age=36;info="something about him"}
[Person]@{name='Sue';age=29;info="something about her"}
[Person]@{name='Cat';age=12;info="something else"})

这样说比较啰嗦,但也更加明确。感谢@js2010指出这一点。


啊,我漏了 [pscustomobject] 转换 - 这看起来是我的解决方案...... 但是... 在需要运行我的脚本的一台机器上,我遇到了新问题:“错误:无法将值转换为类型“... InternalPSCustomObject”。此语言模式仅支持核心类型。”我在其他机器上看不到这个错误,所以也许它与某些其他配置问题有关?(它是 PS 3.0 - 但它是运行 Windows 8 的 ARM 处理器 - 好吧,它是 Surface RT)。 - SteveDJ
顺便说一句,奇怪的是,我的初始XML示例在Surface RT上运行良好,而您的答案却给了我那个错误(我知道它们不同,只是奇怪的是创建自定义对象的方式可以有一种方式可行,但另一种方式不行??) - SteveDJ
对于Surface RT我没有任何经验,很抱歉。我想它应该有包括PowerShell在内的精简版本的所有东西。无论如何,如果我的答案解决了你的原始问题(其中从未提到Windows RT),请考虑接受它。 - Andrew Savinykh
在上面的代码之后运行以下代码: $older = $myitems | Where-Object { $_.age -gt 20 }; Write-Host "people older than 20: $($older.Length)"; $younger = $myitems | Where-Object { $_.age -lt 20 }; Write-Host "people younger than 20: $($younger.Length)"; [pscustomobject[]]$younger2 = $younger; Write-Host "people younger than 20 (array): $($younger2.Length)";请查看我的答案以获取更正确的解决方案。(分号只是为了注释中的可读性...) - Kody
1
@Kody,感谢您发现了这个问题,已经更新了答案。 - Andrew Savinykh
显示剩余3条评论

45

这是在PowerShell中初始化哈希表数组的简洁方式。

> $body = @( @{ Prop1="1"; Prop2="2"; Prop3="3" }, @{ Prop1="1"; Prop2="2"; Prop3="3" } )
> $body

Name                           Value
----                           -----
Prop2                          2
Prop1                          1
Prop3                          3
Prop2                          2
Prop1                          1
Prop3                          3  

6
正是我在寻找的。谢谢! - FuzzyWuzzy
这让我改变了从New-Object PsObject -Property @{ Prop1 = "1"; Prop2 = "2"; etc... }[PsObject]@{ Prop1 = "1"; Prop2 = "2"; etc... } 的方法,谢谢! :) - Kody
4
当然,这是一个哈希表数组,而不是一个自定义对象数组。 - Andrew Savinykh
太棒了,使用数组创建并初始化我的 ARM 密钥保管库,其中包含 Azure 中的所有机密 $body = @( @{ SecretName="A"; SecretValue="2" }, @{ SecretName="B"; SecretValue=4 } ) - Luis Raúl Espinoza Barboza

31

也许您的意思是这样的?我喜欢制作一个对象并使用 Format-Table:

> $array = @()
> $object = New-Object -TypeName PSObject
> $object | Add-Member -Name 'Name' -MemberType Noteproperty -Value 'Joe'
> $object | Add-Member -Name 'Age' -MemberType Noteproperty -Value 32
> $object | Add-Member -Name 'Info' -MemberType Noteproperty -Value 'something about him'
> $array += $object
> $array | Format-Table

Name                                                                        Age Info
----                                                                        --- ----
Joe                                                                          32  something about him

这将根据对象的属性将数组中所有对象放在列中。

提示:使用-auto可以更好地调整表格大小。

> $array | Format-Table -Auto

Name Age Info
---- --- ----
Joe   32 something about him

您还可以指定要在表格中显示的属性。只需用逗号分隔每个属性名称:

> $array | Format-Table Name, Age -Auto

Name Age
---- ---
Joe   32

26

初始化数组的最简单的方法

创建数组

$array = @()

创建您的页眉

$line = "" | select name,age,phone

请填写这一行

$line.name = "Leandro"
$line.age = "39"
$line.phone = "555-555555"

将行添加到 $array 中

$array += $line

结果

$array

name                                                     age                                                      phone
----                                                     ---                                                      -----
Leandro                                                  39                                                       555-555555

1
@BenSewards 不错吧?非常容易做到。非常合乎逻辑。非常干净。当我在提取数据时,我喜欢构建/导入一个数组,执行添加列或修改现有列的操作,并像这样输出到一个新数组中。然后,当我完成时,我可以将所有结果导出到CSV中,非常干净。 - Jeter-work

10
以下是更简洁的答案,避免重复使用NoteProperty标识符和[pscustomobject]-转换:

$myItems =  ("Joe",32,"something about him"), ("Sue",29,"something about her")
            | ForEach-Object {[pscustomobject]@{name = $_[0]; age = $_[1]; info = $_[2]}}

结果:

> $myItems

name           age         info
----           ---         ----
Joe            32          something about him
Sue            29          something about her

1
它有效。但如果数组中只有一个项目呢? - Yiping
2
@Yiping:你必须正确使用逗号运算符,请参见这里。基本上,它是$myItems = , ("Sue",29,"something about her") | ... - davidhigh

3

根据以上数据,这是我的做法:

# initialize the array
[PsObject[]]$people = @()

# populate the array with each object
$people += [PsObject]@{ Name = "Joe"; Age = 32; Info = "something about him" }
$people += [PsObject]@{ Name = "Sue"; Age = 29; Info = "something about her" }
$people += [PsObject]@{ Name = "Cat"; Age = 12; Info = "something else" }

以下的代码即使在Where-Object后只有一个项目也能正常工作:

原始答案

# display all people
Write-Host "People:"
foreach($person in $people) {
    Write-Host "  - Name: '$($person.Name)', Age: $($person.Age), Info: '$($person.Info)'"
}

# display with just 1 person (length will be empty if using 'PSCustomObject', so you have to wrap any results in a '@()' as described by Andrew Savinykh in his updated answer)
$youngerPeople = $people | Where-Object { $_.Age -lt 20 }
Write-Host "People younger than 20: $($youngerPeople.Length)"
foreach($youngerPerson in $youngerPeople) {
    Write-Host "  - Name: '$($youngerPerson.Name)'"
}

结果:

People:
  - Name: 'Joe', Age: 32, Info: 'something about him'
  - Name: 'Sue', Age: 29, Info: 'something about her'
  - Name: 'Cat', Age: 12, Info: 'something else'
People younger than 20: 1
  - Name: 'Cat'

你所观察到的真正原因是PSCustomObject没有像这里这里所描述的那样具有CountLength的代理属性。你使用了哈希表而不是问题中要求的方法,从而规避了这个问题。不幸的是,哈希表的使用并不总是理想的。 - Andrew Savinykh
如果您想使用PSCustomObject进行测试,它仍将按预期工作。但实际上不行,因为我已经解释过了。您可以自己测试并查看结果。我已经测试过了。 - Andrew Savinykh
你说得对。我刚刚验证了一下,除非“Cat”是一个PSObject,否则它将不起作用。如果可以使用PSObject,为什么要使用PSCustomObject呢?此外,在这种情况下,为什么Where-Object的行为会像Select-Object一样呢?Where不是查找操作,它始终是一个集合过滤操作。 - Kody
我已经删除了帖子/评论中的错误信息,以免混淆他人。 - Kody

2
使用"Here-String"并转换为XML。
[xml]$myxml = @"
<stuff>
 <item name="Joe" age="32">
  <info>something about him</info>
 </item>
 <item name="Sue" age="29">
  <info>something about her</info>
 </item>
 <item name="Cat" age="12">
  <info>something else</info>
 </item>
</stuff>
"@

[array]$myitems = $myxml.stuff.Item

$myitems

1

一个关于类的小变化。使用哈希表进行初始化。

class Point { $x; $y }

$a = [Point[]] (@{ x=1; y=2 },@{ x=3; y=4 })

$a

x y        
- -          
1 2
3 4

$a.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Point[]                                  System.Array

$a[0].gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    Point                                    System.Object

0

我必须创建一个预定义类型的数组,以下是我成功完成的方法:

[System.Data.DataColumn[]]$myitems = ([System.Data.DataColumn]("col1"), 
                [System.Data.DataColumn]("col2"),  [System.Data.DataColumn]("col3"))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接