【问题标题】:Split string on arbitrary-length substrings (Powershell)在任意长度的子字符串上拆分字符串 (Powershell)
【发布时间】:2019-10-07 16:37:51
【问题描述】:

我已经格式化了其他来源的文本文件;我无法控制这些来源或要求它们生成更符合我的目的的格式,例如 CSV。我可以查看文件的标题行以确定列宽(和名称,但这里没有问题)。完成后,我将拥有一系列宽度。我希望能够根据我从标题中确定的宽度拆分该文件中的后续行。

显然,我可以遍历宽度数组,并咬掉适当长度的初始子字符串,但我希望有一种更有效的方法 - 例如,如果我想使用固定宽度的列,我可以只使用-split "(\w{$foo})",其中$foo 是包含列宽的变量。

事实上,有没有更有效的方法来做到这一点?

示例数据:

Junction      0122 D150441-4    Ni Po De           210 Na

列宽$cols=@(14, 5, 11, 2, 16, 3, 4, 2)

注意:我不关心切碎数据中的尾随空格;我可以稍后管理它们。我现在只是想切碎数据。

应 iRon 的要求,以便能够展示他的 ConvertFrom-SourceTable,这是一个可能需要解析的完整文件

@SUB-SECTOR: sec_C   SECTOR: reft
#
# Trade routes within the subsector
#
#--------1---------2---------3---------4---------5---------6---
#PlanetName   Loc. UPP Code   B   Notes         Z  PBG Al LRX *
#----------   ---- ---------  - --------------- -  --- -- --- -
Lemente       1907 B897563-B    Ag Ni              824 Na
Zamoran       2108 B674675-A  Q Ag Ni              904 Dr

【问题讨论】:

  • 你能添加一个示例字符串+宽度数组吗?
  • @MathiasR.Jessen - 完成
  • 不清楚您是否在寻找,但完整的解决方案可能在这里:stackoverflow.com/a/50648461/1701026
  • @iRon - 该链接将非常有用,尽管我并不总是拥有如此完美的标题。谢谢!
  • ConvertFrom-SourceTable cmdlet 不需要完美的标头。

标签: powershell substring


【解决方案1】:

事实上,有没有更有效的方法来做到这一点?

如果“更高效”是指“占用更少 CPU 周期的东西”,那么是的:

$string = 'Junction      0122 D150441-4    Ni Po De           210 Na'
$cols = @(14, 5, 11, 2, 16, 3, 4, 2)
$substrings = @(
  $cols |Select -SkipLast 1 |ForEach-Object {
    $string.Remove($_)
    $string = $string.Substring($_)
  }
  $string
)

# $substrings now contain the individual column values

上面的代码将通过不断地从字符串的前一个副本中删除它们来获取第一个 n-1 子字符串。


如果“更高效”的意思是“更少的代码”,您可以连接构建的正则表达式模式并一次性抓取所有捕获组:

$string = 'Junction      0122 D150441-4    Ni Po De           210 Na'
$cols = @(14, 5, 11, 2, 16, 3, 4, 2)

# generate the regex pattern 
# in this case '(.{14})(.{5})(.{11})(.{2})(.{16})(.{3})(.{4})(.{2})'
$pattern = $cols.ForEach({"(.{$_})"})-join''

# use `-match` and $Matches to grab the individual groups
$substrings = if($string -match $pattern){
  $Matches[1..($cols.Length-1)]
}

# $substrings again holds all our substrings

【讨论】:

  • 我还没有完成对这些的分析,以完全理解为什么它们有效,但它们确实有效;谢谢你!有一些代表!
【解决方案2】:

ConvertFrom-SourceTable

$Text = @'
@SUB-SECTOR: sec_C   SECTOR: reft
#
# Trade routes within the subsector
#
#--------1---------2---------3---------4---------5---------6---
#PlanetName   Loc. UPP Code   B   Notes         Z  PBG Al LRX *
#----------   ---- ---------  - --------------- -  --- -- --- -
Lemente       1907 B897563-B    Ag Ni              824 Na
Zamoran       2108 B674675-A  Q Ag Ni              904 Dr
'@

您可以使用原始here string 作为ConvertFrom-SourceTable cmdlet 的输入,但如果使用Get-Content 从文件中检索数据,$Table 可能是一个字符串数组(行):

$Table = $Text -Split "[\r\n]+"

如果您的标题从不改变,最简单的方法是使用-Header-Ruler 参数重新定义标题行和标尺:

$Table | Select -Skip 7 | ConvertFrom-SourceTable `
    -Header 'PlanetName    Loc. UPP Code   B   Notes         Z  PBG Al LRX *' `
    -Ruler  '----------    ---- ---------  - --------------- -  --- -- --- -' `
    | Format-Table


PlanetName Loc. UPP Code  B Notes Z PBG Al LRX *
---------- ---- --------  - ----- - --- -- --- -
Lemente    1907 B897563-B   Ag Ni   824 Na
Zamoran    2108 B674675-A Q Ag Ni   904 Dr

(顺便说一句。-Ruler 参数在这里并不是真正需要的,可以在此特定表中省略)

如果每个表格的标题都不同,您可能会考虑自动重新格式化表格,并从标题和标尺行中删除 # 并将其替换为空格:

$Table | Select -Skip 5 |
    ForEach-Object {$_ -Replace '^#', ' '} |
        ConvertFrom-SourceTable | Format-Table

第一列未完全正确对齐,但随后的数据将对其进行校正。有一个例外:特别是当输入是在流中提供时[1],与标头/标尺右对齐的数据(通常是整数,如下例所示)将是 默认由ConvertFrom-SourceTable cmdlet 解释

[1] 如果输入作为管道流(而不是此处的原始字符串)提供,ConvertFrom-SourceTable cmdlet 将充当in the middle of a pipeline cmdlet,并为下一个 cmdlet 中间释放每个对象。因此,它将只能确定列连接和调整到当前行。

ConvertFrom-SourceTable '
 PlanetName Loc. UPP Code  B Notes Z PBG Al LRX *
 ---------- ---- --------  - ----- - --- -- --- -
12345789012 1907 B897563-B   Ag Ni   824 Na
        123 2108 B674675-A Q Ag Ni   904 Dr
' | Format-Table

(这里注意上面表格输入和Format-Table输出完全一样)

也就是说,如果第一行第一列的字段是一个与表头右对齐的字符串(如上例中的12个字符),如果无法解释就会产生错误(例如,如果它不是数字)。您可以使用-Literal 开关避免这种情况。

结论

我猜这个命令将使用ConvertFrom-SourceTable cmdlet 完成整个技巧:

$Table | Select -Skip 5 |
    ForEach-Object {$_ -Replace '^#', ' '} |
        ConvertFrom-SourceTable -Literal | Format-Table

更新(2019 年 5 月 3 日)

我在ConvertFrom-SourceTable 中添加了一项新功能,该功能可能会用于像这样的浮动表格:

-Floating
默认情况下,浮动表格中的介绍带有不带标尺的标尺 通过管道流式传输的内容会自动跳过。
如果为管道输入提供了-Floating 开关,则 对象的流式传输将从标尺开始(流式浮动表 不能没有尺子)。
如果浮动被显式禁用 (-Floating:$False),则标头 假定在第一行,即使表格没有流式传输。

这意味着您可以将命令简化为并且不必再将-Skip 指定到某一行:

ConvertFrom-SourceTable -Literal ($Table | ForEach-Object {$_ -Replace '^#', ' '})

如果您想从(大)输入文件流式传输数据,则需要提供-Floating 开关来告诉 cmdlet 等待标尺:

$Table | ForEach-Object {$_ -Replace '^#', ' '} |
    ConvertFrom-SourceTable -Literal | Format-Table

更新(2019 年 10 月 6 日)

我已更新 ConvertFrom-SourceTable cmdlet。
虽然-Markdown-Floating 参数已经用尽,但cmdlet 仍然支持markdown 和浮动表。这些功能可以通过显式设置
-HorizontalDash(别名-HDash)和-VerticalDash(别名-VDash)参数来实施 (见Help -Full ConvertFrom-SourceTable

对于这个特定的问题:
如果它涉及原始表(完整的文本表,行由换行符分隔):

PS C:\> ConvertFrom-SourceTable ($Text -Replace '#', ' ') | Format-Table

PlanetName Loc. UPP Code  B Notes Z PBG Al LRX *
---------- ---- --------  - ----- - --- -- --- -
Lemente    1907 B897563-B   Ag Ni   824 Na
Zamoran    2108 B674675-A Q Ag Ni   904 Dr

如果您喜欢流式传输输入(和输出),则需要通过显式设置水平破折号字符 (-Hash '-') 来定义表格的开始位置:

PS C:\> $Text -Split "[\r\n]+" | ForEach-Object {$_ -Replace '^#', ' '} | 
        ConvertFrom-SourceTable -HDash '-' | Format-Table

PlanetName Loc. UPP Code  B Notes Z PBG Al LRX *
---------- ---- --------  - ----- - --- -- --- -
Lemente    1907 B897563-B   Ag Ni   824 Na
Zamoran    2108 B674675-A Q Ag Ni   904 Dr

【讨论】:

  • 用于处理这些文件 - 它们不是唯一的,但它们是很好的代表 - 我必须观察数据以确定要跳过多少行 - 可能是任意的# Trade routes...#Planet... 之间的行数。但是,这看起来像是我的库中的一个有用的 cmdlet;谢谢!
  • 该 cmdlet 主要侧重于自动识别列,但如果提供了明确的标尺,实际上可以识别稍后在此处字符串中的特定行开始的表...这意味着您不必再将-Skip 带到表格中,请参阅我的答案中的 update。 (注意ForEach-Object {$_ -Replace '^#', ' '} 仍然是必需的。它是特定于合并到通用cmdlet 中)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-20
  • 2015-03-12
  • 1970-01-01
  • 2018-09-07
  • 2021-08-11
  • 1970-01-01
相关资源
最近更新 更多