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

13

我有一个大数组(从Excel电子表格导入),其中有一行“总计”,我想要删除它。它总是在数组的最后一项。我尝试找到一种方法来仅删除最后一个数组项,但无法实现。下面是数据和代码示例:

我有一个大数组(从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         :

我希望能够从数组中删除这一行。我已经尝试了以下方法:

$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

如果我检查数组类型,我会得到以下结果:

$data.GetType()

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

如果我这样重新输入对象,我会得到以下结果:

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

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

$data.Remove("Total")
False

这并不会删除该项。我错过了什么?

2个回答

34

在PowerShell中,数组是不可变的,你不能添加或删除其中的元素。你可以做的是:

a)创建一个新的数组,并使用过滤器-ne将不需要的元素筛选出来:

$data = $data | ? {$_.Server -ne "Total"}

b) 通过索引选择到最后一个元素之前的所有内容(对于大型数组可能会消耗更多资源,我不确定):

$data = $data[0..($data.Count-2)]

或者

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

$data = [System.Collections.ArrayList]$data
$data.RemoveAt($data.Count-1)

我已经成功尝试了方法一和方法三!请注意,由于数组的“不可变”性质,我可以使用以下命令修剪“空白”行:$data | Where-Object { ($.PSObject.Properties | ForEach-Object { $.Value }) -ne $null }。这就是为什么我认为我可以简单地删除最后一个条目。 - McKenning
@McKenning:你的 $data | Where-Object ... 命令会创建一个 数组,因为任何输出多于 1 个对象的管道都会这样做。即使你将结果分配回同一个变量,它仍然是一个新数组,就像这个例子所演示的一样:$a = 1, 2, 3; $aToo = $a; $a = $a | ? { $_ -ne 2 }; 'new $a:'; $a; 'original $a:'; $aToo - mklement0

9
为了补充TessellatingHeckler的有用回答, 这里提供一些分析和背景信息:
如上所述,PowerShell数组(.NET类型[System.Object[]])是固定大小的 - 您不能添加或删除元素(但是可以更改现有元素的值)。 使用+或+=将元素“添加”到PowerShell数组时,幕后会创建一个新数组,该数组是原始数组的副本(浅克隆)。 数组具有.Add()、.Remove()和.RemoveAt()方法的唯一原因是它们实现了通用的System.Collections.IList接口,该接口由许多不同类型的集合实现,其中一些可调整大小。尝试在诸如数组之类的固定大小集合上调用这些方法会导致您看到的“集合大小固定”的错误消息。 通过管道发送任何输出多于1个对象的内容都会作为(新的)数组输出,而不考虑输入集合的类型。(单个结果对象作为自身输出,而不是包装在集合中。) 因此,使用诸如$data | ? {$_.Server -ne "Total"}的命令过滤集合中的元素会创建一个新集合,该集合始终是一个数组(如果有多个输出对象,则为[System.Object[]])。

为什么你的尝试失败了:

$data.Remove("Total") 失败了,因为数组不支持删除操作,正如之前提到的。

但是即使 $data 是一个可调整大小的集合(比如 [System.Collections.ArrayList]),传递 "Total" 也不会起作用,因为你尝试调用的 IList.Remove 方法要求你传递要删除的元素本身,在你的情况下,这将是包含 "Total"对象,最容易通过 $data[-1] 访问。

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

考虑到您想要删除最后一个元素,基于索引的 IList.RemoveAt 方法可能是更简单的选择。


{$data}.Invoke() 的确会进行一种类似于重新类型化的操作,但采用的方式很晦涩难懂,最好避免使用:

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

之所以它不是一个无操作,是因为你绕过了PowerShell对脚本块的正常处理,而是通过.NET的.Invoke()方法来调用它,而不是通常调用脚本块的方式: 使用调用运算符&

如果你在脚本块上使用.Invoke(),你总是会得到一个[System.Collections.ObjectModel.Collection[psobject]]实例(即使它只包含1个对象)。

相比之下,使用&在调用.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])

所有这些说法,仅仅为了获取可调整大小的集合而使用脚本块上的.Invoke()是不明智的。相反,选择TessellatingHeckler's answer中展示的方法之一。
此外,PSv5+提供了通过管道传递的集合中删除最后一个元素的简单方法,即Select-Object -SkipLast 1:
$data = Import-XLSX -Path "C:\Pathtofile\spreadsheet.xlsx" -Sheet "SheetName" -RowStart 3 |
  Select-Object -SkipLast 1

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