如何使用 Powershell 将哈希表与另一个哈希表进行比较?

How can I compare a hashtable with another one using Powershell?

我刚开始使用 powershell,但我现在的知识非常匮乏。 我有这个 .log 文件,如下所示:

18.7.2017 12:59:15  Starting thread: KEYWORD1
18.7.2017 12:59:33  Thread finished; ... KEYWORD1
18.7.2017 13:32:19  Starting thread: KEYWORD2
18.7.2017 13:34:8  Thread finished;... KEYWORD2

我现在想知道,启动的每个线程是否也已完成。 如果有未完成的线程我想将时间戳与当前时间进行比较。

我认为哈希表可以解决问题,这就是我想出的:

foreach($line in Get-Content $sourceDirectory)
{
    if($line -like "*Starting thread*")
    {
        $arrStart = $line -split ' '
        $startThreads=$arrStart[$arrStart.Length-1]
        $hashmap1 = @{$arrEnd[$arrEnd.Length-1] = $arrEnd[1]}
    }

    if($line -like "*Thread finished*")
    {
        $arrEnd = $line -split ' '
        $hashmap2 = @{$arrEnd[$arrEnd.Length-1] = $arrEnd[1]}
        $endThreads=($arrEnd[1]+" "+$arrEnd[$arrEnd.Length-1])
    }
}

现在如何比较这两个哈希图?

您似乎正在尝试制作两个哈希表,一个用于开始,一个用于完成。重要信息是关键字。与其制作散列表,因为你真的只需要一条信息,数组会是更好的数据类型。

# Find Lines with `Starting thread` and drop everything before the final space to get the array of KEYWORDS that started
$Start = (Select-String $sourceDirectory 'Starting thread') -replace '^.*Starting thread.*\s+'
# Find Lines with `Thread finished` and drop everything before the final space to get the array of KEYWORDS that finished
$Finish = (Select-String $sourceDirectory 'Thread finished') -replace '^.*Thread finished.*\s+'
# Find everything that started but hasn't finished.
$Start.where({$_ -notin $Finish})

注意:where 方法和 -notin 需要 PS4+。还假设线程不会多次启动和停止。

一种方法是使用 RegEx 将每一行分开,然后从细节创建一个新对象。例如:

Get-Content .\data.txt |
    ForEach-Object {
        if ($_ -match "^(?<time>(\d+\.){2}\d+ (\d{2}:){2}\d{2}).*(?<state>Starting|finished).*\b(?<keyword>\w+)$")
        {
            [PsCustomObject]@{
                Keyword = $matches.keyword
                Action = $(if($matches.state -eq "Starting"){"Start"}else{"Finish"})
                Time = (Get-Date $matches.time)
            }
        }
    }

假设您有一个包含以下内容的日志文件 (data.txt):

18.7.2017 12:59:15  Starting thread: KEYWORD1
18.7.2017 13:32:19  Starting thread: KEYWORD2
18.7.2017 12:59:15  Starting thread: KEYWORD3
18.7.2017 13:34:18  Thread finished;... KEYWORD2
18.7.2017 12:59:15  Starting thread: KEYWORD4
18.7.2017 13:34:18  Thread finished;... KEYWORD3
18.7.2017 12:59:15  Starting thread: KEYWORD5
18.7.2017 13:34:18  Thread finished;... KEYWORD5

运行 上面的代码针对它,给出输出:

Keyword  Action Time               
-------  ------ ----               
KEYWORD1 Start  18/07/2017 12:59:15
KEYWORD2 Start  18/07/2017 13:32:19
KEYWORD3 Start  18/07/2017 12:59:15
KEYWORD2 Finish 18/07/2017 13:34:18
KEYWORD4 Start  18/07/2017 12:59:15
KEYWORD3 Finish 18/07/2017 13:34:18
KEYWORD5 Start  18/07/2017 12:59:15
KEYWORD5 Finish 18/07/2017 13:34:18

这与原始文件相比没有太大改进,但现在您有了一些对象,可以更轻松地处理它们。例如,您可以通过在最后一个括号后附加以下内容来查看哪些没有匹配 start/finish:

| Group-Object Keyword -NoElement | Sort-Object Count -Descending

这给出了这样的输出:

Count Name                     
----- ----                     
    2 KEYWORD2                 
    2 KEYWORD3                 
    2 KEYWORD5                 
    1 KEYWORD1                 
    1 KEYWORD4  

现在可以更轻松地查看哪些有 start/finish 对(例如,每组有 2 个项目)

这对于您的场景来说可能有点矫枉过正,但正如您所说的您是 PowerShell 的新手,我想我会提到它,因为将文本转换为这样的对象以进行处理通常非常有用。

JPBlanc recommends grouping the records in a comment on the question, and the Group-Object cmdlet 确实提供了一个概念上优雅的解决方案:

注意:假设如果给定的关键字只有 一个 条目,则它始终是 starting 条目。

Select-String 'Starting thread:|Thread finished;' file.log | 
  Group-Object { (-split $_)[-1] } | Where-Object { $_.Count % 2 -eq 1 }
  • Select-String调用只提取感兴趣的行(一个线程开始,一个线程结束),使用正则表达式(正则表达式)

  • Group-Object 调用在每行 ($_),即关键字。

  • Where-Object 然后 returns 只有那些条目数为奇数的结果,即那些没有 配对的条目 , 表示已开始但未完成的线程。

这会产生如下内容:

Count Name          Group
----- ----          -----
    1 KEYWORD3      {/Users/jdoe/file.log:5:28.8.2018 08:59:16  Starting thread: KEYWORD3}

这可能不是您想要的格式,但考虑到输出是 objects,这在 PowerShell 中很典型,您可以根据自己的喜好以编程方式轻松处理它们。

从技术上讲,上述命令输出 [Microsoft.PowerShell.Commands.GroupInfo] instances whose .Group property in this case contains [Microsoft.PowerShell.Commands.MatchInfo] 个实例,如 Select-String 的输出。


以下代码扩展了上面的代码以生成自定义输出,报告自每个未完成的线程启动以来已经过去了多少时间:

$now = Get-Date
Select-String 'Starting thread:|Thread finished;' file.log  | 
  Group-Object { (-split $_)[-1] } | Where-Object { $_.Count % 2 -eq 1 } | ForEach-Object {
    foreach ($matchInfo in $_.Group) { # loop over started-only lines
      $tokens = -split $matchInfo.Line # split into tokens by whitespace
      $date, $time = $tokens[0..1]     # extract date and time (first 2 tokens)
      $keyword = $tokens[-1]           # extract keyword (last token)
      # Parse date+time into a [datetime] instance.
      # Note: Depending on the current culture, [datetime]::Parse("$date $time") may do.
      $start = [datetime]::ParseExact("$date $time", 'd\.M\.yyyy HH:mm:ss', [cultureinfo]::InvariantCulture)
      # Custom output string containing how long ago the thread was started:
      "Thread $keyword hasn't finished yet; time elapsed since it started: " +
        ($now - $start).ToString('g')
    }
  }

这会产生如下内容:

Thread KEYWORD3 hasn't finished yet; time elapsed since it started: 2:03:35.347563

2:03:35.347563(2 小时,3 分钟,...)是 [TimeSpan] instance that is the result of subtracting two points in time ([datetime] 个实例的字符串表示形式。