在任意长度的子字符串上分割字符串(Powershell)

时间:2019-04-17 15:32:34

标签: powershell substring

我已经格式化了其他来源的文本文件;我无法控制这些来源,也无法要求他们生成更适合我用途的格式,例如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

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

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

更新

我为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