实际上,/////参数首先应用于原始输入-First
,然后应用于结果输出。-Last
-Skip
-Index
-SkipIndex
-SkipLast
-Unique
简单的解决方法是使用两个 Select-Object
调用:一个查找唯一对象,另一个从唯一对象中选择所需的数字:
PS> 1, 1, 2, 3 | Select-Object -Unique | Select-Object -First 2
1
2
鉴于Select-Object -Unique
从PowerShell 7.2 开始太慢(请参阅底部部分),这是一个更快的解决方法,正如您自己发现的那样:使用 aux。System.Collections.Generic.HashSet`1
实例结合ForEach-Object
; 该示例还显示了对当前缺少的不区分大小写的支持Select-Object -Unique
(见底部):
# Create an aux. hash set that keeps tracks of what objects have
# already been seen, using case-*insensitive* comparisons.
$auxHashSet = [Collections.Generic.HashSet[string]]::new(
[StringComparer]::InvariantCultureIgnoreCase
)
# Stream to ForEach-Object, where the aux. hash set is used
# to only pass out objects that haven't previously been seen.
'a', 'A', 'B', 'c' |
ForEach-Object { if ($auxHashSet.Add($_)) { $_ } } |
Select-Object -First 2
这会'a', 'B'
根据需要输出 。请注意,您可能希望删除$auxHashSet
变量以(最终)释放其内存 - 请参阅下一个。
使用带有 的-Begin
块ForEach-Object
,您可以使管道更加独立,但请注意,所有脚本块都直接在调用者的范围内运行,因此它$auxHashSet
仍然在那里创建并在命令之后继续存在,因此您仍然必须手动删除它,从而(最终)释放它的内存。
- 注意:虽然原则上您可以在一个
-End
块中执行此操作,但这不适用于,Select-Object -First
因为过早停止管道不会让上游 cmdlet 有机会运行它们的结束块 - 请参阅GitHub 问题 #7930以了解有关此令人惊讶的讨论行为。
'a', 'A', 'B', 'c' |
ForEach-Object -Begin {
$auxHashSet = [Collections.Generic.HashSet[string]]::new([StringComparer]::InvariantCultureIgnoreCase)
} -Process {
if ($auxHashSet.Add($_)) { $_ }
} |
Select-Object -First 2
# Remove the aux. variable and (eventually) free its memory.
Remove-Variable auxHashSet
请注意,还有一个基于 LINQ 的替代方案via [System.Linq.Enumerable]::Distinct()
,但它有重要的 限制:
输出是无序的,即不保证保留输入顺序。
您不能从 PowerShell 命令流式传输方法的输入集合(要将 PowerShell 命令的输出传递给方法,它必须预先在数组中完整地收集) - 但是,LINQ 方法的输出(例如)Distinct()
是有效的流式传输,因为返回一个惰性可枚举。[1]
此外,输入数组必须是强类型的,如果不是的话。PowerShell[int[]]
使用[object[]]
诸如_ _ ,对于大型输入集合,它本身可能需要一段时间。
[Linq.Enumerable]::Distinct(
[string[]] ('a', 'A', 'B', 'c'),
[StringComparer]::InvariantCultureIgnoreCase
) | Select-Object -First 2
这也输出'a', 'B'
(尽管不能保证输出元素的顺序)。
如果约束不是问题,并且您需要在整个输入集合(或其中的大部分)中找到唯一元素,则此解决方案比哈希集辅助解决方案要快得多ForEach-Object
,特别是如果您的输入集合是已经强类型。
如果在相同的约束下,您不关心延迟输出行为,而只想获取所有不同对象的内存集合 - 再次,无序- 您可以直接使用System.Collections.Generic.HashSet`1
实例:
[Collections.Generic.HashSet[string]]::new(
[string[]] ('a', 'A', 'B', 'c'),
[System.StringComparer]::InvariantCultureIgnoreCase
)
这个输出'a', 'B', 'c'
,但特别是作为一个哈希集对象,而不是一个数组,但是,由于是可枚举的,它在 PowerShell 的枚举上下文中的行为就像一个数组,特别是在管道中。
Select-Object -Unique
陷阱,对比Sort-Object
:
虽然额外的Select-Object
调用确实增加了处理开销,但该命令总体上可能只处理所需数量的输入对象,即一旦找到所需数量的唯一对象就停止处理。
Select-Object -Unique
但是,从 PowerShell 7.2 开始,它的实现似乎效率低下并且出乎意料地在产生输出之前首先收集所有输入,即使没有概念上的理由这样做:它应该能够产生流输出,即有条件地输出输入对象因为它们正在被接收,因为它只需要考虑到目前为止已接收到哪些输入对象。
在实践中,从 PowerShell 7.2 开始,对于较大的输入集合Select-Object -Unique
来说速度过慢;当前有问题的实现在 GitHub 问题#11221和#7707中进行了讨论。
这种仅考虑到目前为止接收到的输入的概念能力与形成对比,后者也提供了一个开关,但必须在产生输出之前首先收集所有输入,因为必须考虑所有输入对象以进行正确排序。Sort-Object
-Unique
- 从 PowerShell 7.2 开始,
Sort-Object -Unique
在实践中比Select-Object -Unique
.
至于如何Select-Object -Unique
以更高效的流式方式实现:目前看到的对象可以存储在一个System.Collections.Generic.HashSet`1
实例中,以便于有效测试输入对象是否被认为等于已经输出的对象;有关 PowerShell 示例,请参阅此答案。
如果且何时 Select-Object -Unique
是固定的,则权衡如下:
从 PowerShell 7.2 开始,字符串输入Select-Object -Unique
意外地区分大小写,尽管 PowerShell默认情况下通常不区分大小写- 请参阅GitHub 问题 #12059。
测试 cmdlet 是产生流式输出还是首先收集所有输入:
没有检查 cmdlet 的源代码,这里有一种测试方法 -中间管道段是要测试的命令:
# Test Sort-Object -Unique
# Because the command cannot stream, for conceptual reasons,
# it takes a while for the one and only output object to appear.
1..1e5 | Sort-Object -Unique | Select-Object -First 1
# Test Select-Object -Unique
# The command *could* stream, conceptually speaking, in which case
# the output object would appear right away.
# However, as of PowerShell 7.2, the command isn't implemented
# in a streaming fashion, so it takes a - surprisingly long - while
# for the output object to appear.
# it takes a while for the one and only output object to appear.
1..1e5 | Select-Object -Unique | Select-Object -First 1
如果上面的给定管道立即产生它唯一的输出对象,则感兴趣的命令是流式传输;如果在输出对象出现之前需要一段时间,它会首先收集所有输入。