PowerShell 使用 -Tail 和 -Wait 在两个字符串之间提取文本

PowerShell Extract text between two strings with -Tail and -Wait

我有一个包含大量日志消息的文本文件。 我想提取两个字符串模式之间的消息。我希望提取的消息显示在文本文件中。

我尝试了以下方法。它有效,但不支持 Get-Content 的 -Wait 和 -Tail 选项。此外,提取的结果显示在一行中,但与文本文件不同。欢迎输入:-)

示例代码

function GetTextBetweenTwoStrings($startPattern, $endPattern, $filePath){

    # Get content from the input file
    $fileContent = Get-Content $filePath

    # Regular expression (Regex) of the given start and end patterns
    $pattern = "$startPattern(.*?)$endPattern"

    # Perform the Regex opperation
    $result = [regex]::Match($fileContent,$pattern).Value

    # Finally return the result to the caller
    return $result
}

# Clear the screen
Clear-Host

$input = "THE-LOG-FILE.log"
$startPattern = 'START-OF-PATTERN'
$endPattern = 'END-OF-PATTERN'

# Call the function
GetTextBetweenTwoStrings -startPattern $startPattern -endPattern $endPattern -filePath $input

根据 Theo 的回答改进了脚本。 以下几点需要改进:

  1. 尽管我在脚本中调整了缓冲区大小,但输出的开头和结尾以某种方式被修剪。
  2. 如何将每个匹配结果包装成 START 和 END 字符串?
  3. 我仍然不知道如何使用 -Wait-Tail 选项

更新脚本

# Clear the screen
Clear-Host

# Adjust the buffer size of the window
$bw = 10000
$bh = 300000
if ($host.name -eq 'ConsoleHost') # or -notmatch 'ISE'
{
  [console]::bufferwidth = $bw
  [console]::bufferheight = $bh
}
else
{
    $pshost = get-host
    $pswindow = $pshost.ui.rawui
    $newsize = $pswindow.buffersize
    $newsize.height = $bh
    $newsize.width = $bw
    $pswindow.buffersize = $newsize
}


function Get-TextBetweenTwoStrings ([string]$startPattern, [string]$endPattern, [string]$filePath){
    # Get content from the input file
    $fileContent = Get-Content -Path $filePath -Raw
    # Regular expression (Regex) of the given start and end patterns
    $pattern = '(?is){0}(.*?){1}' -f [regex]::Escape($startPattern), [regex]::Escape($endPattern)
    # Perform the Regex operation and output
    [regex]::Match($fileContent,$pattern).Groups[1].Value
}

# Input file path
 $inputFile = "THE-LOG-FILE.log"

# The patterns
$startPattern = 'START-OF-PATTERN'
$endPattern = 'END-OF-PATTERN'


Get-TextBetweenTwoStrings -startPattern $startPattern -endPattern $endPattern -filePath $inputFile

首先,你不应该使用 $input 作为 self-defined 变量名,因为这是一个 Automatic variable.

然后,您将文件作为字符串 array 读取,而您更愿意将文件作为单个多行字符串读取。对于该附加开关 -Raw 到 Get-Content 调用。

您正在创建的正则表达式不允许 fgor 正则表达式在您提供的开始和结束模式中使用特殊字符,因此我建议在创建正则表达式字符串时对这些模式使用 [regex]::Escape()

虽然您的正则表达式确实在括号内使用了组捕获序列,但在获取您寻求的值时并没有使用它。

最后,我建议使用 PowerShell 命名约定 (Verb-Noun) 作为函数名称

尝试

function Get-TextBetweenTwoStrings ([string]$startPattern, [string]$endPattern, [string]$filePath){
    # Get content from the input file
    $fileContent = Get-Content -Path $filePath -Raw
    # Regular expression (Regex) of the given start and end patterns
    $pattern = '(?is){0}(.*?){1}' -f [regex]::Escape($startPattern), [regex]::Escape($endPattern)
    # Perform the Regex operation and output
    [regex]::Match($fileContent,$pattern).Groups[1].Value
}

$inputFile    = "D:\Test\THE-LOG-FILE.log"
$startPattern = 'START-OF-PATTERN'
$endPattern   = 'END-OF-PATTERN'

Get-TextBetweenTwoStrings -startPattern $startPattern -endPattern $endPattern -filePath $inputFile

会产生如下结果:

blahblah
more lines here

(?is) 使正则表达式 case-insensitive 也有点匹配换行符


很高兴看到您正在使用我的 Get-TextBetweenTwoStrings 函数版本,但是我相信您误将控制台中的输出误认为是专用文本编辑器中的输出。在控制台中,太长的行将被截断,而在记事本等文本编辑器中,您可以选择换行或水平滚动条。

如果你只是追加

| Set-Content -Path 'X:\wherever\theoutput.txt'

对于 Get-TextBetweenTwoStrings .. 调用,您会发现当您在 Word 或记事本中打开它时,这些行 NOT 被截断了。

事实上,您可以在该行后面加上

notepad 'X:\wherever\theoutput.txt'

让记事本立即打开该文件。

  • 您需要对您的 Get-Content call, in a pipeline, such as with ForEach-Object 执行 流式传输 处理,如果您想要处理正在处理的行 阅读.

    • 如果您使用 Get-Content -Wait,这是必须的,因为这样的调用不会自行终止(它会无限期地等待新行添加到文件中),但在管道它的输出可以在接收时被处理,甚至在命令终止之前。
  • 您正在尝试跨多行匹配 ,只有当您使用 -Raw 时,Get-Content 输出才有效开关 - 默认情况下,Get-Content 逐行读取其输入文件

    • 但是,-Raw-Wait 不兼容。
    • 因此,您必须坚持line-by-line处理,这需要您分别匹配开始和结束模式,并跟踪您何时处理这两个模式之间的行。

这是一个概念证明,但请注意以下几点:

  • -Tail 100 是 hard-coded - 根据需要调整或将其设为另一个参数。

  • -Wait 的使用意味着函数将 运行 无限期地等待新行被添加到 $filePath - 所以你需要使用 Ctrl-C 来阻止它。

    • 虽然您可以在管道中使用 Get-TextBetweenTwoStrings 调用自身进行 object-by-object 处理,但 将其结果分配给变量($result = ... ) 在以 Ctrl-C 终止时将不起作用 ,因为这种终止方法也会中止赋值操作。

    • 为了解决这个限制,下面的函数被定义为一个 advanced function, which automatically enables support for the common -OutVariable parameter,即使在使用 Ctrl-C;您的示例调用将如下所示(如 Theo 所述,不要将自动 $input 变量用作自定义变量):

      # Look for blocks of interest in the input file, indefinitely,
      # and output them as they're being found.
      # After termination with Ctrl-C, $result will also contain the blocks
      # found, if any.
      Get-TextBetweenTwoStrings -OutVariable result -startPattern $startPattern -endPattern $endPattern -filePath $inputFile
      
  • 根据您的反馈,您希望行块包含开始和结束模式匹配的整行,因此下面的正则表达式包含在.*

  • 您的 $startPattern$endPattern 参数中的 pattern 这个词有点模棱两可,因为它表明它们本身是 regexes 因此可以 as-is 或嵌入 as-is 在 -match 运算符的 RHS 上更大的正则表达式中。
    但是,在下面的解决方案中,我假设它们被视为 文字 字符串,这就是为什么它们用 [regex]::Escape(); 转义的原因;如果这些参数本身确实是正则表达式,则简单地省略这些调用;即:

    $startRegex = '.*' + $startPattern + '.*'
    $endRegex = '.*' + $endPattern + '.*'
    
  • 该解决方案假定块之间没有重叠,并且在给定的块中,开始和结束模式位于不同的行上。

  • 找到的每个块都输出为单个 multi-line 字符串,使用 LF ("`n") 作为换行符;如果您想要 CRLF 换行符序列,请使用 "`r`n";对于 platform-native 换行符格式(Windows 上的 CRLF,Unix-like 平台上的 LF),使用 [Environment]::NewLine.

# Note the use of "-" after "Get", to adhere to PowerShell's
# "<Verb>-<Noun>" naming convention.
function Get-TextBetweenTwoStrings {

  # Make the function an advanced one, so that it supports the 
  # -OutVariable common parameter.
  [CmdletBinding()]
  param(
    $startPattern, 
    $endPattern, 
    $filePath
  )

  # Note: If $startPattern and $endPattern are themselves
  #       regexes, omit the [regex]::Escape() calls.
  $startRegex = '.*' + [regex]::Escape($startPattern) + '.*'
  $endRegex = '.*' + [regex]::Escape($endPattern) + '.*'

  $inBlock = $false
  $block = [System.Collections.Generic.List[string]]::new()

  Get-Content -Tail 100 -Wait $filePath | ForEach-Object {
    if ($inBlock) {
      if ($_ -match $endRegex) {
        $block.Add($Matches[0])
        # Output the block of lines as a single, multi-line string
        $block -join "`n"
        $inBlock = $false; $block.Clear()       
      }
      else {
        $block.Add($_)
      }
    }
    elseif ($_ -match $startRegex) {
      $inBlock = $true
      $block.Add($Matches[0])
    }
  }

}