在PowerShell中的*nix fold等效命令

3
今天我有几百个项目(从SQL查询中的ID),需要将它们粘贴到另一个查询中,以便分析师可以读取。我需要使用*nix的fold命令。我想将这300行重新格式化为每行多个数字,用空格隔开。我会使用fold -w 100 -s
在*nix上类似的工具包括fmtpar
在Windows中,有没有一种简单的方法在PowerShell中执行此操作?我期望其中一个*-Format命令可以做到,但我找不到它。 我正在使用PowerShell v4。
请参阅https://unix.stackexchange.com/questions/25173/how-can-i-wrap-text-at-a-certain-column-size
# Input Data
# simulate a set of 300 numeric IDs from 100,000 to 150,000
100001..100330 | 
    Out-File _sql.txt -Encoding ascii

# I want output like:
# 100001,  100002,  100003,  100004,  100005, ... 100010, 100011
# 100012,  100013,  100014,  100015,  100016, ... 100021, 100021
# each line less than 100 characters.

我尝试了 -join ' ',输出确实在100个字符处换行,但是数字被分开了。我想要在空格处换行,这样数字的值就不会因为换行而改变。 - yzorg
刚刚发现了http://vgoenka.tripod.com/unixscripts/fold.awk.txt,它可能与Windows上的GIT cli附带的gawk.exe一起使用。 - yzorg
@EmperorXLII,感谢提供链接。对于未来的读者:Emporer的链接中的解决方案使用了for和本地类型,因此在数据集为10,000-10,000,000项时可能会快得多。当数据集适合RAM时,循环已知比管道快10倍。 - yzorg
5个回答

7

根据文件大小的不同,您可以将其全部读入内存,用空格连接,然后在100 *字符或下一个空格处拆分。

(Get-Content C:\Temp\test.txt) -join " " -split '(.{100,}?[ |$])' | Where-Object{$_}

该正则表达式查找100个字符,然后找到第一个空格。然后将该匹配项 -split ,但由于模式被括号包裹,因此返回匹配项而不是丢弃它。 Where 去除在匹配项之间创建的空条目。

小样本证明理论

@"
134
124
1
225
234
4
34
2
42
342
5
5
2
6
"@.split("`n") -join " "  -split '(.{10,}?[ |$])' | Where-Object{$_}

以上拆分尽可能以10个字符为单位。如果无法拆分,则数字仍然保留。示例基于我用头猛击键盘所创建的。
134 124 1 
225 234 4 
34 2 42 
342 5 5 
2 6

您可以将此转换为一个函数,以恢复您最有可能寻找的简单性。它可以变得更好,但这并不是答案的重点。
Function Get-Folded{
    Param(
        [string[]]$Strings,
        [int]$Wrap = 50
    )
    $strings  -join " " -split "(.{$wrap,}?[ |$])" | Where-Object{$_}
}

再次提供示例

PS C:\Users\mcameron> Get-Folded -Strings (Get-Content C:\temp\test.txt) -wrap 40
"Lorem ipsum dolor sit amet, consectetur 
adipiscing elit, sed do eiusmod tempor incididunt 
ut labore et dolore magna aliqua. Ut enim 
ad minim veniam, quis nostrud exercitation 
... output truncated...

你可以看到,它本来应该在40个字符处进行拆分,但第二行更长。它在40个字符后的下一个空格处进行拆分,以保留单词。

这似乎存在Unicode字符问题。这个 变成了 …。有没有办法修复它? - Kevin
你能给我提供一个更大的样本来进行测试,并告诉我你的PowerShell版本吗? - Matt
我正在运行版本5.1。另一个例子是é变成了é变成了— - Kevin
我找到了解决方案。我将其更改为Get-Content C:\Temp\test.txt -Encoding UTF8,问题得到了解决。 - Kevin
哦,我本来想说我没看出问题。但如果你正在使用我的代码示例,那么是的,你会遇到这个问题。 - Matt

1
当我看到这个问题时,第一时间想到的是滥用 Format-Table 来实现,主要是因为它知道在指定宽度时如何正确地换行。在编写了一个函数后,似乎其他提出的解决方案更短,可能更容易理解,但我还是觉得应该发布这个解决方案。
function fold {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        $InputObject,
        [Alias('w')]
        [int] $LineWidth = 100,
        [int] $ElementWidth
    )

    begin {
        $SB = New-Object System.Text.StringBuilder

        if ($ElementWidth) {
            $SBFormatter = "{0,$ElementWidth} "
        }
        else {
            $SBFormatter = "{0} "
        }
    }

    process {
        foreach ($CurrentObject in $InputObject) {
            [void] $SB.AppendFormat($SBFormatter, $CurrentObject)
        }
    }

    end {
        # Format-Table wanted some sort of an object assigned to it, so I 
        # picked the first static object that popped in my head:
        ([guid]::Empty | Format-Table -Property @{N="DoesntMatter"; E={$SB.ToString()}; Width = $LineWidth } -Wrap -HideTableHeaders |
            Out-String).Trim("`r`n")
    }
}

使用它会产生以下输出:

PS C:\> 0..99 | Get-Random -Count 100 | fold
1 73 81 47 54 41 17 87 2 55 30 91 19 50 64 70 51 29 49 46 39 20 85 69 74 43 68 82 76 22 12 35 59 92 
13 3 88 6 72 67 96 31 11 26 80 58 16 60 89 62 27 36 37 18 97 90 40 65 42 15 33 24 23 99 0 32 83 14  
21 8 94 48 10 4 84 78 52 28 63 7 34 86 75 71 53 5 45 66 44 57 77 56 38 79 25 93 9 61 98 95          

PS C:\> 0..99 | Get-Random -Count 100 | fold -ElementWidth 2
74 89 10 42 46 99 21 80 81 82  4 60 33 45 25 57 49  9 86 84 83 44  3 77 34 40 75 50  2 18  6 66 13  
64 78 51 27 71 97 48 58  0 65 36 47 19 31 79 55 56 59 15 53 69 85 26 20 73 52 68 35 93 17  5 54 95  
23 92 90 96 24 22 37 91 87  7 38 39 11 41 14 62 12 32 94 29 67 98 76 70 28 30 16  1 61 88 43  8 63  
72                                                                                                  

PS C:\> 0..99 | Get-Random -Count 100 | fold -ElementWidth 2 -w 40
21 78 64 18 42 15 40 99 29 61  4 95 66  
86  0 69 55 30 67 73  5 44 74 20 68 16  
82 58  3 46 24 54 75 14 11 71 17 22 94  
45 53 28 63  8 90 80 51 52 84 93  6 76  
79 70 31 96 60 27 26  7 19 97  1 59  2  
65 43 81  9 48 56 25 62 13 85 47 98 33  
34 12 50 49 38 57 39 37 35 77 89 88 83  
72 92 10 32 23 91 87 36 41              

1
如果每行只有一个项目,并且您想要将每100个项目连接成由空格分隔的单个行,则可以将所有输出放入文本文件中,然后执行以下操作:
gc c:\count.txt -readcount 100 | % {$_ -join " "}

啊,我错过了关于字符的部分...我的代码只是将每100个项目连接在一起。其他答案可能更准确。 - Noah Sparks

1

这就是我最终使用的。

# simulate a set of 300 SQL IDs from 100,000 to 150,000
100001..100330 | 
    %{ "$_, " } | # I'll need this decoration in the SQL script
    Out-File _sql.txt -Encoding ascii

gc .\_sql.txt -ReadCount 10 | %{ $_ -join ' ' }

感谢大家的努力和回答。我真的很惊讶在 Rohn Edward 的答案中没有使用 [guid]::Empty 的情况下,没有办法使用 Format-Table 来实现这一点。
我的 ID 比我给出的示例要一致得多,因此 Noah 使用的 gc -ReadCount 是特定数据集中最简单的解决方案,但将来我可能会使用 Matt 的答案或 Emperor 在评论中链接的答案。

Format-Wide适用于您拥有对象的情况。您可以使用Select-String(我们的grep)将每行读取为Match对象。 sls .* .\_sql.txt | Format-Wide Line -Column 10。这会产生:100001,100002,100003,100004,100005,100006,100007,100008,100009,100010,。与原始答案相同的警告:仅在所有项目大小几乎相同时才有效。 - yzorg

0
我想到了这个:
$array = 
(@'
1
2
3
10
11
100
101
'@).split("`n") |
foreach {$_.trim()}

$array = $array * 40

$SB = New-Object Text.StringBuilder(100,100)

foreach ($item in $array) {

Try { [void]$SB.Append("$item ") }

Catch {
         $SB.ToString()
         [void]$SB.Clear()
         [Void]$SB.Append("$item ")
      }
}    
#don't forget the last line
$SB.ToString()

1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 
1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 
1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 
1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 
1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 
1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 
1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 1 2 3 10 11 100 101 

也许没有您期望的那么紧凑,也可能有更好的方法来实现它,但似乎它可以工作。


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