While循环不会生成管道输出。

3

看起来While循环无法生成可在管道中继续的输出。我需要处理一个大型(几个GiB)的文件。在这个简单的例子中,我想提取第二个字段,按其排序,然后只获取唯一值。我对While循环和将事物推送到管道中有什么不理解吗?

在*NIX世界中,这将是一个简单的操作:

cut -d "," -f 2 rf.txt | sort | uniq

在PowerShell中,这并不是那么简单。 源数据。
PS C:\src\powershell> Get-Content .\rf.txt
these,1,there
lines,3,paragraphs
are,2,were

脚本。
PS C:\src\powershell> Get-Content .\rf.ps1
$sr = New-Object System.IO.StreamReader("$(Get-Location)\rf.txt")

while ($line = $sr.ReadLine()) {
    Write-Verbose $line
    $v = $line.split(',')[1]
    Write-Output $v
} | sort

$sr.Close()

输出内容。

PS C:\src\powershell> .\rf.ps1
At C:\src\powershell\rf.ps1:7 char:3
+ } | sort
+   ~
An empty pipe element is not allowed.
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : EmptyPipeElement
3个回答

2

有时候我们会把事情搞得比必要的还要复杂。你手头有一个没有表头的CSV文件。下面的方法可以帮助你解决问题:

Import-Csv .\rf.txt -Header f1,f2,f3 | Select-Object -ExpandProperty f2 -Unique | Sort-Object

Import-Csv会像Get-Content一样尝试将整个文件读入内存吗?如果是这样,那就无法处理大型文件。 - lit
@Liturgist 我的理解是,Get-Content(在未使用 -Raw 开关的情况下调用时)不会将整个文件读入内存。如果您看到了这种行为,那很可能是因为您将其导入了 Sort-Objectsort 是后者的别名);请参阅我的答案以了解原因。 - briantist
抱歉回复迟了。Import-Csv 命令会逐行读取文件,您不需要担心要单独使用 Get-Content 命令。详见 https://technet.microsoft.com/library/2a767ced-0fc9-4896-a8f0-2c5bdee49910(v=wps.630).aspx。 - Nasir
@Nasir 只要他仍然将管道传输到 Sort-Object,他就会遇到相同的“问题”。 - briantist

2
Nasir的解决方法似乎是在这里走的路线。
如果你想知道你的代码出了什么问题,答案是while循环(以及do/while/until循环)不像PowerShell中的其他语句一样一致地向管道返回值(实际上确实如此,我将保留那些例子,但向下滚动查看它对你不起作用的真正原因)。
ForEach-Object是一个cmdlet而不是内置的语言特性/语句;确实将对象返回到管道。
1..3 | % { $_ }

foreach语句;返回值。

foreach ($i in 1..3) { $i }

if/else -- 语句; 返回结果。

if ($true) { 1..3 }

for -- 语句;执行并返回结果。

for ( $i = 0 ; $i -le 3 ; $i++ ) { $i }

switch语句:有返回值。

switch (2)
{
    1 { 'one' }
    2 { 'two' }
    3 { 'three' }
}

但出于某些原因,这些其他循环似乎表现得不可预测。

无限循环,返回 $i0;没有增量)。

$i = 0; while ($i -le 3) { $i }

不返回任何内容,但$i会被自增:
$i = 0; while ($i -le 3) { $i++ }

如果您在括号中包裹表达式,似乎它确实会被返回:

$i = 0; while ($i -le 3) { ($i++) }

但事实证明(我在学习时发现),while 的奇怪返回值语义与您的错误无关;无论其返回值如何,都不能将语句管道传输到函数/命令。
foreach ($i in 1..3) { $i } | measure

你会收到同样的错误。

你可以通过使用$()将整个语句作为子表达式来“绕过”此问题:

$( foreach ($i in 1..3) { $i } ) | measure

在这种情况下,这对你很有用。或者在您的while循环中,您可以将项目添加到数组中,而不是使用Write-Output,然后进行排序:

$arr = @()

while ($line = $sr.ReadLine()) {
    Write-Verbose $line
    $v = $line.split(',')[1]
    $arr += $v
} 

$arr | sort

我知道你正在处理一个大文件,也许你认为通过逐行管道传输到sort可以避免大内存占用。在大多数情况下,PowerShell确实可以通过管道实现这种方式,但排序的问题在于你需要整个集合才能进行排序,因此Sort-Object命令将“收集”传递给它的每个项目,然后最终进行实际的排序;我不确定你是否可以完全避免这种情况。诚然,让Sort-Object来完成而不是自己构建数组可能更有效,这取决于它的实现方式,但我认为你在RAM上并不能节省太多。


0

其他解决方案

Get-Content -Path C:\temp\rf.txt | select @{Name="Mycolumn";Expression={($_ -split "," )[1]}} | select Mycolumn -Unique | sort

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