【问题标题】:Powershell and regex to extract multiple points of data from a filePowershell 和正则表达式从文件中提取多个数据点
【发布时间】:2022-01-01 18:01:06
【问题描述】:

我正在尝试从一个相当讨厌的日志文件中添加多个数据点(First、Last、ID 号)。

我有这个:

Get-Content c:\LOG\22JAN01.log | Out-String | 
  % {[Regex]::Matches($_, "(?<=FIRST:)((.|\n)*?)(?=LAST:)")} | % {$_.Value}

这在提取名字方面做得很好 - 但我还需要从同一行获取姓氏和 ID 号并将它们一起呈现“BOB SMITH 123456”

日志文件的每一行如下所示:

名字:BOB 最后:史密斯 门:主入口 ID:123456 时间:2021 年 12 月 31 日星期五晚上 11:55:47 事件:19002304

我希望输出看起来像:

  • 鲍勃·史密斯 123456
  • 杰克·琼斯 029506
  • 卡伦·卡彭特 6890298

到目前为止,我只能设法获得所有名字,而没有其他任何东西。感谢您为我指明正确方向的任何帮助!

【问题讨论】:

  • 日志文件看起来和我们在引用文本中看到的一样吗?姓名、姓氏和门在同一行?

标签: regex powershell


【解决方案1】:

如果他们总是在同一行,我喜欢用 switch 来阅读。

switch -Regex -File c:\LOG\22JAN01.log {
    'FIRST:(\w+) LAST:(.+) DOOR.+ ID:(\d+) ' {
        [PSCustomObject]@{
            First = $matches[1]
            Last  = $matches[2]
            ID    = $matches[3]
        }
    }
}

示例日志输出

First Last      ID     
----- ----      --     
BOB   SMITH     123456 
JACK  JONES     029506 
KAREN KARPENTER 6890298

您可以将其捕获到一个变量中,然后继续使用您喜欢的对象。

$output = switch -Regex -File c:\LOG\22JAN01.log {
    'FIRST:(\w+) LAST:(.+) DOOR.+ ID:(\d+) ' {
        [PSCustomObject]@{
            First = $matches[1]
            Last  = $matches[2]
            ID    = $matches[3]
        }
    }
}

$output | Out-GridView

$output | Export-Csv -Path c:\Log\parsed_log.log -NoTypeInformation

【讨论】:

    【解决方案2】:

    您需要使用捕获组()

    假设FIRST 始终位于行首(如果不是,则删除^),并且字段名称始终存在且顺序相同,并且它们的值至少为一个字符长,你可以使用,例如:

    $result = & {
      $path = "c:\LOG\22JAN01.log";
      $pattern = "^FIRST:(.+?) LAST:(.+?) DOOR:.+? ID:(\d+)";
      Select-String -Path $path -Pattern $pattern -AllMatches |
      % {$_.Matches.Groups[1], $_.Matches.Groups[2], $_.Matches.Groups[3] -join " "}
    }
    

    .+? 表示匹配除换行符之外的任何字符中的一个或多个,在匹配模式中后面的内容之前尽可能少。如果一定会匹配所需的值,则可以使用更严格的东西,例如 [A-Z]+

    【讨论】:

      【解决方案3】:

      如果您可以假设每个字段名由(英文)仅字母组成,[1] 例如FIRST可以将-replace operatorConvertFrom-StringData cmdlet 组合在一起的通用解决方案

      # Sample array of input lines.
      $inputLines = 
        'FIRST:BOB LAST:SMITH DOOR:MAIN ENTRANCE ID:123456 TIME:Friday, December 31, 2021 11:55:47 PM INCIDENT:19002304',
        'FIRST:JACK LAST:JONES DOOR:SIDE ENTRANCE ID:123457 TIME:Friday, December 31, 2021 11:55:48 PM INCIDENT:19002305',
        'FIRST:KAREN LAST:KARPENTER DOOR:BACK ENTRANCE ID:123458 TIME:Friday, December 31, 2021 11:55:49 PM INCIDENT:19002306'
      
      $inputLines -replace '\b([a-z]+):', "`n`$1=" | 
        ConvertFrom-StringData |
          ForEach-Object { $_.FIRST, $_.LAST, $_.ID -join ' ' }
      
      • 对于每个输入行,-replace 操作将每个字段名称-值对放在自己的行上,将分隔符 : 替换为 =

      • 生成的行块由ConvertFrom-StringData 解析为hashtable,表示每个输入行的字段,允许通过名称方便地访问字段,例如.FIRST(PowerShell 允许您使用 property-access 语法 作为 index 语法的替代,例如['FIRST'])。

      输出:

      BOB SMITH 123456
      JACK JONES 123457
      KAREN KARPENTER 123458
      

      [1] 更一般地,您可以使用这种方法,只要您可以制定一个明确标识字段名称的regex

      【讨论】:

        【解决方案4】:

        使用这个可重用的功能:
        (另见:#16257 String >>>Regex>>> PSCustomObject

        function ConvertFrom-Text {
            [CmdletBinding()]Param (
                [Regex]$Pattern,
                [Parameter(Mandatory = $True, ValueFromPipeLine = $True)]$InputObject
            )
            process {
                if ($_ -match $pattern) {
                    $matches.Remove(0)
                    [PSCustomObject]$matches
                }
            }
        }
        

        $log = @(
            'FIRST:BOB LAST:SMITH DOOR:MAIN ENTRANCE ID:123456 TIME:Friday, December 31, 2021 11:55:47 PM INCIDENT:19002304'
            'FIRST:JOHN LAST:DOE DOOR:MAIN ENTRANCE ID:789101 TIME:Friday, December 31, 2021 11:55:47 PM INCIDENT:19002304'
        )
        
        $Log |ConvertFrom-Text -Pattern '\bFIRST:(?<First>\S*).*\bLAST:(?<Last>\S*).*\bID:(?<ID>\d+)'
        
        ID     Last  First
        --     ----  -----
        123456 SMITH BOB
        789101 DOE   JOHN
        

        【讨论】:

        • 一个简洁的功能,但如果您使用捕获组,则无需后视:(?&lt;=FIRST:) 会更好,就像 FIRST: 等一样。
        【解决方案5】:

        假设日志文件看起来像我们在引用文本中看到的那样,您可以像这样匹配它:

        $log = @'
        FIRST:BOB LAST:SMITH DOOR:MAIN ENTRANCE ID:123456 TIME:Friday, December 31, 2021 11:55:47 PM INCIDENT:19002304
        FIRST:JOHN LAST:DOE DOOR:MAIN ENTRANCE ID:789101 TIME:Friday, December 31, 2021 11:55:47 PM INCIDENT:19002304
        '@
        
        $re = [regex]'(?si)FIRST:(?<first>.*?)\s*LAST:(?<last>.*?)\s*DOOR.*?ID:(?<id>\d+)'
        
        foreach($match in $re.Matches($log))
        {
            '{0} {1} {2}' -f
                $match.Groups['first'].Value,
                $match.Groups['last'].Value,
                $match.Groups['id'].Value
        }
        
        # Results in:
        BOB SMITH 123456
        JOHN DOE 789101
        

        这个正则表达式应该适用于多行字符串,因此您可以将-Raw 用于Get-Content

        $re = [regex]'(?si)FIRST:(?<first>.*?)\s*LAST:(?<last>.*?)\s*DOOR.*?ID:(?<id>\d+)'
        
        $result = foreach($match in $re.Matches((Get-Content ./test.log -Raw)))
        {
            [pscustomobject]@{
                First = $match.Groups['first'].Value
                Last  = $match.Groups['last'].Value
                ID    = $match.Groups['id'].Value
            }
        }
        
        $result | Export-Csv path/to/newlog.csv -NoTypeInformation
        

        请参阅https://regex101.com/r/WluWpD/1 了解 regex 解释。

        【讨论】:

        • 示例中DOOR 的值为MAIN ENTRANCE,因此ENTRANCE 不应出现在您的正则表达式中,因为其他门可能不包含它。此外,最后的.*? 毫无意义。否则看起来不错。
        • @MikeM 我很笨,谢谢!对于这两件事(.*? 也是)。学习正则表达式很难呵呵
        • 到目前为止这似乎有效!至少它显示了我需要的东西。现在只是想弄清楚如何将它写入文件,我想我会准备好的!
        • @DerekB 看我上次的编辑,你只需要将foreach 循环的结果收集到一个变量中($result),然后Out-File 的结果。
        • 啊,是的,我错过了 $result = 行 - 现在它导出到文件 - 谢谢!
        猜你喜欢
        • 1970-01-01
        • 2018-02-19
        • 2012-03-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多