从powershell数组/集合/哈希表中删除一行

McK*_*ing 7 arrays powershell

我有一个大型数组(从Excel电子表格导入),它有一个我要删除的"总"行.它始终是数组中的最后一项.我试图找到一种方法来删除最后一个数组项,但不能.以下数据和代码示例:

$data = Import-XLSX -Path "C:\Pathtofile\spreadsheet.xlsx" -Sheet "SheetName" -RowStart 3
$data | ? {$_.Server -eq "Total"}


Server        : Total
VLAN          :
IP Address    :
CPU           : 84
RAM           : 313
C:\           : 2840
D:\           : 17950
OS Version    :
OS Edition    :
SQL Version   :
SQL Edition   :
Datastore     :
Reboot        :
Notes         :
Run Code Online (Sandbox Code Playgroud)

我希望能够从数组中删除此行.我尝试过以下方法:

$data.Remove("Total")
Exception calling "Remove" with "1" argument(s): "Collection was of a fixed size."
At line:1 char:1
+ $ImportCSV.Remove("Total")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NotSupportedException
Run Code Online (Sandbox Code Playgroud)

如果我检查数组类型,我得到以下内容:

$data.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
Run Code Online (Sandbox Code Playgroud)

如果我像这样重新键入对象,我得到以下内容:

$data = {$data}.Invoke()
$data.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Collection`1                             System.Object

$data.Remove("Total")
False
Run Code Online (Sandbox Code Playgroud)

这不会删除该项目.我在这里错过了什么?

Tes*_*ler 19

数组在PowerShell中是不可变的,您无法添加或删除它们中的元素.你能做的是:

a)创建一个新数组并过滤你不想要的数组(例如:)-ne:

$data = $data | ? {$_.Server -ne "Total"}
Run Code Online (Sandbox Code Playgroud)

要么

b)按索引选择最后一个元素的所有内容(可能是大数据库上的资源,我不知道):

$data = $data[0..($data.Count-2)]
Run Code Online (Sandbox Code Playgroud)

要么

c)将其转换[System.Collections.ArrayList]为可变的,然后从中删除一个元素:

$data = [System.Collections.ArrayList]$data
$data.RemoveAt($data.Count-1)
Run Code Online (Sandbox Code Playgroud)


mkl*_*nt0 5

通过分析和背景信息补充TessellatingHeckler的有用答案:

  • 如所陈述,PowerShell的阵列(.NET类型[System.Object[]])是固定大小的 -你不能添加或删除的元素(可以,但是,改变现有元素).

    • 当您使用+或"添加"元素到PowerShell数组时+=,会在后台创建一个数组,这是原始数据的副本(浅层克隆).

    • 数组具有.Add(),.Remove().RemoveAt()方法的唯一原因是它们实现了通用System.Collections.IList接口,该接口由许多不同类型的集合实现,其中一些集合可调整大小的.
      尝试在固定大小的集合(如数组)上调用这些方法会导致出现"Collection was of a fixed size."错误消息.

  • 无论输入集合的类型如何,通过输出多于1个对象的管道发送任何内容都将作为(新)数组出现.(单个结果对象作为自身输出,不包含在集合中.)

    • 因此,使用命令过滤掉集合的元素,例如$data | ? {$_.Server -ne "Total"}创建一个始终是数组集合(如果有超过1个输出对象).[System.Object[]]

为什么尝试失败:

$data.Remove("Total")失败的根本,因为数组不支持拆卸,如提及.

但即使$data 一个可调整大小的集合(例如[System.Collections.ArrayList]),传递"Total"也没有用,因为你试图调用IList.Remove方法要求你传递元素来移除它自己,在你的情况下它将是其属性包含的对象,最容易作为.访问. .Server"Total"$data[-1]

另请注意,传递不是列表元素的值是安静的无操作(如果集合可调整大小).

鉴于您想要删除最后一个元素,基于索引的IList.RemoveAt方法将是更简单的选择.


{$data}.Invoke()确实会对各种类型进行重新输入,但最好避免以一种神秘的方式:

乍一看,从概念上讲,该命令应该是一个无操作:你只是将一个变量引用括在一个脚本块({ ... })中,然后调用它,它应该只输出变量的.

不是无操作的原因你绕过PowerShell对脚本块的正常处理是通过.NET .Invoke()方法调用它而不是通常调用脚本块的方式:使用call operator &.

如果.Invoke()在脚本块上使用,则始终会获得一个[System.Collections.ObjectModel.Collection[psobject]]实例(即使它只包含一个对象).

相比之下,在幕后&调用.Invoke()后使用会做额外的工作:如果集合只包含1个元素,则直接返回该元素(集合被解包).否则,该集合将转换为PowerShell数组([System.Object[]];我不清楚此转换的具体情况).

既然你用.Invoke()直接呼叫到.Remove()上运行[System.Collections.ObjectModel.Collection[psobject]],这可调整大小,并有自己的.Remove()方法,其排除逻辑是一样的IList.Remove(),它的阴影.

但是,它的不同之处IList.Remove()在于返回[bool]反射成功的移除尝试,这就是为什么你false在第二次尝试时获得了.

然而,与第一次尝试不同的是,这次失败不是基本的,并且传入最后一个元素所包含的对象(行); 同样,.RemoveAt()最高指数也会有效:

# Convert to [System.Collections.ObjectModel.Collection[psobject]]
$dataResizable = { $data }.Invoke()

# Remove last item by index.
$dataResizable.RemoveAt($dataResizable.Count-1)

# Alternative: Remove last item by object reference / value.
# Returns [bool], which we suppress.
$null = $dataResizable.Remove($dataResizable[-1])
Run Code Online (Sandbox Code Playgroud)

说了这么多,使用.Invoke()的脚本块只是为了获得一个可调整大小的集合是不明智的.
相反,选择TessellatingHeckler的答案中演示的方法之一.

此外,PSv5 +提供了一种从通过管道传递的集合中删除最后一个元素的简单方法Select-Object -SkipLast 1:

$data = Import-XLSX -Path "C:\Pathtofile\spreadsheet.xlsx" -Sheet "SheetName" -RowStart 3 |
  Select-Object -SkipLast 1
Run Code Online (Sandbox Code Playgroud)