如何使用powershell转换文件内容

How to convert filecontent using powershell

我有一个格式奇怪的日志文件,我想将其转换为 table。格式是每行包含多个键值对(每行相同的对)。我想转换这些行,以便每个 属性 成为 table 中的一列,其中包含该行的值。

请注意,原始日志文件每行包含 39 个属性,日志文件约为 80MB。

示例行:

date=2019-12-02 srcip=8.8.8.8 destip=8.8.4.4 srcintf="port2"
date=2019-12-01 srcip=8.8.8.8 destip=8.8.4.4 srcintf="xyz abc"
date=2019-12-03 srcip=8.8.8.8 destip=8.8.4.4 srcintf="port2"
date=2019-12-05 srcip=8.8.8.8 destip=8.8.4.4 srcintf="port2"
date=2019-12-07 srcip=8.8.8.8 destip=8.8.4.4 srcintf="port2"

我试过:

Get-Content .\testfile.log | select -First 10 | ConvertFrom-String | select p1, p2, p3 | ft | Format-Wide

但这不会将 属性 名称分解为列名称。所以在这个例子中我希望 P1 是日期,p2 srcip 和 p3 destip 并且每个值的第一部分被删除。

任何人有任何提示或创意如何将其转换为 table?

ConvertFrom-String 提供基于分隔符的解析以及基于包含示例值的模板的启发式解析。基于分隔符的解析应用了您无法控制的自动类型转换,并且模板语言的文档很少,确切的行为很难预测 - 最好完全避免使用此 cmdlet。另请注意,它在 PowerShell [Core] v6+.

中不可用

相反,我建议使用一种基于 switch statement[1] and the -split operator 的方法来创建表示日志行的自定义对象 ([pscustomobject]) 的集合:

# Use $objects = switch ... to capture the generated objects in a variable.
switch -File .\testfile.log { # Loop over all file lines
  default {
    $oht = [ordered] @{ } # Define an aux. ordered hashtable
    foreach ($keyValue in -split $_) { # Loop over key-value pairs
      $key, $value = $keyValue -split '=', 2 # Split pair into key and value
      $oht[$key] = $value -replace '^"|"$' # Add to hashtable with "..." removed
    }
    [pscustomobject] $oht  # Convert to custom object and output.
  }
}

注:

  • 以上假设您的值没有嵌入空格;如果他们这样做,则需要做更多的工作 - 请参阅下一节。

  • 要在 变量 中捕获生成的自定义对象,只需使用 $objects = switch ...

    • 多了两行日志,$objects 变成了 [object[]][pscustomobject] 实例数组。如果你想确保 $objects 也成为一个数组,即使恰好只有 one 日志行,使用 [array] $objects = switch ...[array] 是有效的与 [object[]]).
    • 相同
  • 要通过 管道 直接将输出对象发送到其他 cmdlet,请将 switch 语句包含在 & { ... }[=50 中=]

根据您的示例输入,这会产生:

date       srcip   destip  srcintf
----       -----   ------  -------
2019-12-02 8.8.8.8 8.8.4.4 port2
2019-12-01 8.8.8.8 8.8.4.4 port2
2019-12-03 8.8.8.8 8.8.4.4 port2
2019-12-05 8.8.8.8 8.8.4.4 port2
2019-12-07 8.8.8.8 8.8.4.4 port2

变体,支持 带有嵌入空格的值 "..." 中(例如,srcintf="port 2"):

switch -file .\testfile.log {
  default {
    $oht = [ordered] @{ }
    foreach ($keyValue in $_ -split '(\w+=(?:[^"][^ ]*|"[^"]*"))' -notmatch '^\s*$') {
      $key, $value = $keyValue -split '=', 2
      $oht[$key] = $value -replace '^"|"$'
    }
    [pscustomobject] $oht
  }
}

请注意,不支持 嵌入式转义 " 实例(例如,srcintf="port \"2\"" 将不起作用)。

解释:

  • $_ -split '(\w+=(?:[^"][^ ]*|"[^"]*"))' 由匹配 key=valueWithoutSpaceskey="value that may have spaces" 标记的 regex 拆分,并且由于将表达式括在 [=34= 中](创建捕获组),-split 输出的标记中包含 这些 "separators"(默认情况下,不包括分隔符)。

  • -notmatch '^\s*$' 然后从结果中清除空的和全空格的标记("data tokens",这在我们的例子中不感兴趣),实际上只留下键值对。

  • $key, $value = $keyValue -split '=', 2将给定的key-value token按=分割成最多2个token,并使用解构赋值将key和value赋值给分离的变量。

  • $oht[$key] = $value -replace '^"|"$' 添加一个条目到辅助。具有手头键和值的哈希表,其中 -replace '^"|"$' 使用 从值的开头和结尾删除 "(如果存在)。


[1] switch -File 是使用 Get-ContentForEach-Object 的组合逐行处理文件的灵活且更快的替代方法。

所以你可以做的是将每一行切割成键值对的哈希表,而不是将它们传递给ConvertFrom-StringData。这种方法有几个注意事项。为了简单起见,您的源数据是 space 分隔的。如果您的真实数据包含 spaces(可以减轻),这将中断。其他明显的警告是您不能保证 属性 顺序。

Get-Content c:\temp\so.txt | ForEach-Object{
     [PSCustomObject](($_ -split " ") -join "`r`n" | ConvertFrom-StringData)
} | Select-Object date, srcip, destip, srcintf

输出:

date       srcip   destip  srcintf
----       -----   ------  -------
2019-12-02 8.8.8.8 8.8.4.4 "port2"
2019-12-01 8.8.8.8 8.8.4.4 "port2"
2019-12-03 8.8.8.8 8.8.4.4 "port2"
2019-12-05 8.8.8.8 8.8.4.4 "port2"
2019-12-07 8.8.8.8 8.8.4.4 "port2"

好的,为了讨论的目的,我将假设如下:

  1. 数据在文件中PSDATA.TXT
  2. 除了分隔名称-值对的空格外,数据中没有其他空格。
  3. 接受table生成的表格数据将所有值都视为字符串。

鉴于...

Get-Content -Path PSDATA.TXT |
ForEach-Object {$_ -replace ' ','";' -replace '=','="' -replace '""','"'} |
ForEach-Object {New-Object PSObject -Property (Invoke-Expression ("[Ordered]@{{{0}}}" -f $_))}

... 将生成一个 table,其中文件中的每一行都变成一个 PSObject,其字段的名称取自每个名称-值对中的名称,关联的值是该字段的值, 作为一个字符串。如果您不使用 PowerShell v4 或更高版本(我不确定 3),您可以省略 [Ordered],但 PSObject 中字段顺序的副作用不一定是相同的顺序如文件中所示。

如果您想要这些 PSObject 的数组以供进一步处理,您可以将上面的整行包装在一个变量赋值中,例如 $A=(«that whole thing above, on one line»),如果您想将它发送到 CSV 文件,您可以在末尾添加 | Export-CSV -path NewCSVFile.CSV

我更喜欢数据表,这样您就可以轻松地对日志文件进行排序、过滤、合并等操作:

$logFilePath  = 'C:\test\test.log'

$dt = New-Object system.Data.DataTable
[void]$dt.Columns.Add('P1',[string]::empty.GetType() )
[void]$dt.Columns.Add('P2',[string]::empty.GetType() )
[void]$dt.Columns.Add('P3',[string]::empty.GetType() )

foreach( $line in [System.IO.File]::ReadLines($logFilePath) )
{
    $tokenArray = $line -split '[= ]'

    $row    = $dt.NewRow()
    $row.P1 = $tokenArray[1]
    $row.P2 = $tokenArray[3]
    $row.P3 = $tokenArray[5]
    [void]$dt.Rows.Add( $row )

}

$dt