imm*_*le2 4 powershell unique select-object
我只是好奇我是否缺少任何文档,或者是否有不同/更好的方法来做到这一点,从而不需要文档。也许我是唯一一个尝试从一组数据中Select-Object选择唯一实例的人。-First X
根据下面的测试,看起来Select-Object与-Unique开关和某种类型的限制器(First、Last、Skip、Index等)一起使用本质上会导致在删除重复项之前应用限制器。这在概念上对我来说没有意义,而且似乎也没有记录在案。
我对这个糟糕的例子表示歉意,但考虑一个包含 20 个项目的数组,每个项目出现两次:
PS > $array = @() ; 1..10 | % { $array += $_ ; $array += $_ }
PS > $array -Join ','
1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10 ##Displaying the array on a single comma separated line
Run Code Online (Sandbox Code Playgroud)
假设有人给了你$array,但你最多只能处理 5 个对象的输入。过滤给你的内容,你可能会想使用Select-Object. 一开始你最终得到了 5 个对象,但是有重复的对象,所以你很快就想了想,只需添加开关-Unique,然后你就会意识到输出仍然不太正确。
PS > ($array | Select-Object -First 5) -Join ','
1,1,2,2,3 ##5 objects as expected, but with duplicates
PS > ($array | Select-Object -Unique -First 5) -Join ','
1,2,3 ##No duplicates, but less than the expected 5 objects...
Run Code Online (Sandbox Code Playgroud)
为了获得我期望的结果,我需要Select-Object在返回最终的对象集之前删除重复项。虽然知道这一点没有什么问题,但对我来说,使用Select-Object它所做的操作顺序似乎很奇怪,而且没有任何文档说明开关-Unique应用于cmdlet.
PS > ($array | Select-Object -Unique | Select-Object -First 5) -Join ','
1,2,3,4,5 ##This is my expected outcome, 5 objects returned without any duplicates
Run Code Online (Sandbox Code Playgroud)
事实上,-First// -Last// //参数首先应用于原始输入,-Skip然后应用于结果输出。-Index-SkipIndex-SkipLast-Unique
简单的解决方法是使用两个 Select-Object调用:一个调用查找唯一的对象,另一个调用从唯一的对象中选择所需的数量:
PS> 1, 1, 2, 3 | Select-Object -Unique | Select-Object -First 2
1
2
Run Code Online (Sandbox Code Playgroud)
鉴于从 PowerShell 7.2 开始Select-Object -Unique速度太慢(请参阅底部部分),正如您自己发现的那样,这里有一个更快的解决方法:使用 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
Run Code Online (Sandbox Code Playgroud)
'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
Run Code Online (Sandbox Code Playgroud)
请注意,还有一个基于 LINQ 的替代方案via [System.Linq.Enumerable]::Distinct(),但它有重要的 限制:
输出是无序的,即不保证保留输入顺序。
您无法从 PowerShell 命令流式传输方法的输入集合(要将 PowerShell 命令的输出传递给方法,必须预先将其完整收集在数组中) - 但是,来自 LINQ 方法的输出(例如)Distinct() 可以有效地流式传输,因为返回一个惰性枚举。[1]
此外,输入数组必须是强类型的(如果还不是的话)。PowerShell 通过诸如 之类的强制转换使此操作变得简单[int[]],但请注意,使用[object[]]基于 - 的数组作为输入(这就是常规 PowerShell 数组,例如用于收集命令输出),但请注意,这涉及创建数组的副本,对于大型输入集合来说,这本身可能需要一段时间。
[Linq.Enumerable]::Distinct(
[string[]] ('a', 'A', 'B', 'c'),
[StringComparer]::InvariantCultureIgnoreCase
) | Select-Object -First 2
Run Code Online (Sandbox Code Playgroud)
这也会输出'a', 'B'(尽管不保证输出元素的顺序)。
如果约束不是问题,并且您需要查找整个输入集合(或其大部分)中的唯一元素,则此解决方案比哈希集辅助ForEach-Object解决方案要快得多,特别是如果您的输入集合是已经强类型化了。
如果在相同的约束内,您不关心延迟输出行为,而只想获取所有不同对象的内存中集合(同样是无序的),您可以System.Collections.Generic.HashSet`1直接使用实例:
[Collections.Generic.HashSet[string]]::new(
[string[]] ('a', 'A', 'B', 'c'),
[System.StringComparer]::InvariantCultureIgnoreCase
)
Run Code Online (Sandbox Code Playgroud)
它输出'a', 'B', 'c',但特别是作为哈希集对象,而不是数组,但是,由于可枚举,它在 PowerShell 的枚举上下文中(尤其是在管道中)表现得像数组。
Select-Object -Unique陷阱,对比Sort-Object:虽然额外的Select-Object调用确实增加了处理开销,但该命令总体上有可能仅处理所需数量的输入对象,即一旦找到所需数量的唯一对象就停止处理。
然而,从 PowerShell 7.2 开始,似乎Select-Object -Unique实现效率低下,并且在生成输出之前意外地首先收集所有输入,即使没有概念上的理由这样做:它应该能够生成流输出,即有条件地输出输入对象当它们被接收时,因为它只需要考虑到目前为止已经接收到的输入对象。
实际上,从 PowerShell 7.2 开始,对于较大的输入集合来说Select-Object -Unique速度非常慢;当前有问题的实现在 GitHub issues #11221和#7707中讨论。
这种仅考虑迄今为止收到的输入的概念能力与 形成鲜明对比,后者也提供了一个开关,但必须在生成输出之前首先收集所有输入,因为必须考虑所有输入对象以进行正确排序。Sort-Object-Unique
Sort-Object -Unique在实践中比Select-Object -Unique.至于如何以更高效、流式的方式实现:可以将目前Select-Object -Unique看到的对象存储在一个实例中,以便于高效测试输入对象是否被视为等于已输出的对象;有关 PowerShell 示例,请参阅此答案。System.Collections.Generic.HashSet`1
如果 且何时 Select-Object -Unique是固定的,则权衡如下:
感兴趣的输出对象相对于所有输入对象的比例越小,您使用的效果就越好Select-Object -Unique(即使您必须随后对结果对象进行排序)。
如果您无论如何都需要输出/考虑所有输入对象,并且假设按排序顺序输出感兴趣的对象是期望/可接受的,Sort-Object那么是更好的选择。
从 PowerShell 7.2 开始,字符串输入Select-Object -Unique意外地区分大小写,尽管 PowerShell 默认情况下通常不区分大小写- 请参阅GitHub 问题 #12059。
无需检查 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
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)
如果上面给定的管道几乎立即生成其唯一的输出对象,则感兴趣的命令是流式传输;如果输出对象出现之前需要一段时间,它会首先收集所有输入。
| 归档时间: |
|
| 查看次数: |
1938 次 |
| 最近记录: |