用 PowerShell 处理纯文本 - 4

命题

QQ 群里的史瑞克朋友提出的一个命题:

$txt="192.168.1
192.168.2
192.168.3
172.19.3
192.16.1
192.16.2
192.16.11
192.16.3
10.0.4
192.16.29
192.16.9
192.16.99
192.16.100"

要求输出:

10.0.4
172.19.3
192.16.1-192.16.3
192.16.9
192.16.11
192.16.29
192.16.99-192.16.100
192.168.1-192.168.3

问题解析

* 将各个 IP 段补充为三位的格式
* 按字符串排序
* 遍历每一行,按照以下规则处理:
    * 如果和上一行连续,则上一段可能没有结束,更新 `$endIP`
    * 如果和上一行不连续
        * 若 `$startIP` 和 `$endIP` 相同,说明是单个 IP,将单个 IP 加入 $result
        * 若 `$startIP` 和 `$endIP` 不同,说明是一段 IP,将一段 IP 加入 $result
        * 更新 `$startIP` 和 `$endIP`
* 最后一行需要特殊处理

PowerShell 实现

$DebugPreference = "Continue"

$txt="192.168.1
192.168.2
192.168.3
172.19.3
192.16.1
192.16.2
192.16.11
192.16.3
10.0.4
192.16.29
192.16.9
192.16.99
192.16.100"
$txt += "`n999.999.999"

$startIP = @(0, 0, 0)
$endIP = @(0, 0, 0)
$result = @()

-split $txt | % {
    $fullSegments = ($_ -split "\." | % {
        "{0:D3}" -f [int]$_
    })
    $fullSegments -join "."
} | sort | % {
    Write-Debug "Processing $_"
    $segments = @($_ -split "\." | % {
        [int]$_
    })

    if ($endIP[0] -eq $segments[0] -and
        $endIP[1] -eq $segments[1] -and
        $endIP[2] + 1 -eq $segments[2]) {
        Write-Debug '和上一个IP连续'
        $endIP = $segments
    } else {
        Write-Debug '和上一个IP不连续'
        if (($startIP -join ".") -eq ($endIP -join ".")) {
            Write-Debug '单个IP'
            $result += $startIP -join "."
        } else {
            Write-Debug '一段IP'
            $result += ($startIP -join ".") + "-" + ($endIP -join ".")
        }

        $startIP = $segments
        $endIP = $segments
    }
}

$result | select -Skip 1

源代码请在这里下载

用 PowerShell 处理纯文本 - 3

命题:

怎样把字符 “ABC-EFGH-XYZ” 替换为 “012-3456-789”

为了解决这个 case,先归纳它的规律:

  • 注意字符集和数字之间的对应关系,我们可以自定义一个字符集来表示。
  • 保留 - 号。
  • 不区分大小写,提高适应性。

根据以上规律编写 PowerShell 代码:

$charSet = 'ABCEFGHXYZ'.ToCharArray()

$text = 'ABC-EFGH-XYZ'

$array = ($text.ToUpper().ToCharArray() | % {
    if ($_ -eq '-') {
        '-'
    } else {
        [string]([System.Array]::IndexOf($charSet, $_))
    }
})

$array -join ''

结果是:

012-3456-789

用 PowerShell 处理纯文本 - 2

应朋友要求,帮忙解决一例 PowerShell 问题:

有一个 CSV 文件,其中有一个 Photo 字段存的是 BASE64 编码的字符串,这个字符串包含换行符。在 Import-Csv 的时候,Photo 字段不会作为一个整体值,而是会变成每行一个。文件的内容是这样的:

StaffNum,LogonName,ObjName,Title,Office,Department,Photo
03138,wangjunhao,王俊豪,流程优化主管,宜山路,运营平台中心/项目部,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a
HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy
...
PyoooGSr0qRCdtFFMBy9qkFFFJCYo60w85oopgNzjoBTto9KKKQj/9k=

为了解决这个 case,先归纳它的规律:

  1. BASE64 字符串的字符集为 0..9、a..z、A..Z,以及/和=。
  2. BASE64 字符串每一行不超过 76 个字符。
  3. 如果某一行从第一个字符到最后一个字符,都符合上述 2 条规律,说明前一行并没有结束。应当把当前行拼接到前一行中。

根据以上规律编写 PowerShell 代码:

$fileName = 'AllUsers.csv'

$currentLine = ''
gc $fileName | % -process {
        if ($_ -cmatch '^[a-zA-Z0-9/+=]{1,76}$') {
            # 如果符合 BASE64 特征,说明上一行未结束。
            $currentLine += $_
        } else {
            # 如果不符合 BASE64 特征,说明上一行是完整的。
            Write-Output $currentLine
            $currentLine = $_
        }
    } -end {
        $currentLine
    } |
ConvertFrom-Csv

完整的 .CSV 及 .PS1 文件请在这里下载

用 PowerShell 处理纯文本 - 1

原始文本:”data1”:111,”data2”:22,”data3”:3,”data4”:4444444,”data5”:589
要求:转换成对象

方法一,采用字符串运算及 ConvertFrom-StringData 命令:

$rawTxt='"data1":111,"data2":22,"data3":3,"data4":4444444'
$rawTxt -split ',' | ForEach-Object {
   $temp= $_ -split ':'
   "{0}={1}" -f $temp[0].Substring(1,$temp[0].Length-2),$temp[1]
} | ConvertFrom-StringData

方法二,采用正则表达式,使用.NET的方法:

$rawTxt = '"data1":111,"data2":22,"data3":3,"data4":4444444,"data5":589'
$regex = [regex] '"(?<name>\w*)":(?<value>\d*),?'
$match = $regex.Match($rawTxt)
while ($match.Success) {
    [PSCustomObject]@{
        Name = $match.Groups['name'].Value
        Value = $match.Groups['value'].Value
    }
    $match = $match.NextMatch()
}

方法三,采用正则表达式,使用 Select-String Cmdlet:

Select-String -InputObject $rawTxt -Pattern $regex -AllMatches | % {
    $_.Matches
} | % {
   [PSCustomObject]@{
        Name = $_.Groups['name'].Value
        Value = $_.Groups['value'].Value
    }
}

三者的执行结果都是这样:

Name          Value
----          -----
data1         111
data2         22
data3         3
data4         4444444
data5         589

原命题参见:[PowerShell 文本处理实例(三)] 1