忍者ブログ

テキストファイルの頻出行を調べるPowerShell

2024年10月03日
前にsyslogが肥大化する原因を調査した記事を書いた。

http://aki.p-kin.net/etc/ログが肥大化する原因を調査した話

原因わかれば終わりってんなら良いけど、分割した全ファイルを確認とか、定期的に作業となるとめんどくさくてやってらんない。

ということで今回は、ドラッグドロップするだけでで解析を自動的に行うスクリプトを作った話。


前回記事のおさらい

始める前に原因調査の手順をおさらい。以下の3ステップで調査をした。
  1. Power Shellで分割
  2. ファイルをエクセルに貼り付けて数値を削除
  3. ファイル毎に頻出する行をカウント

言語選定

分割処理はPowerShellのコマンドが楽チンなので、その他の処理もPowerShellで作成することにした。バッチからPowerShellとVBSを呼び出す方法もなくはないけど、シンプルな方が良いし。

PowerShellのコーディング

いつものようにCopilotに仕様を提示して、得られたソースコードを対話の中でブラッシュアップしながら開発。以下の項目は改良点。

ステータス表示を追加

実際に動かしてみると結構時間がかかるので、現在状態を表示する機能を追加することにした。
Copilotにソースコードを解説させて勉強しながら、ステータス表示を改良して完成。こう言う細かい部分は、自分で作った方が早い。

Power Shellで分割

変数lineLimit行毎に分割ファイルを作成する。1万行毎に別ファイルにするのがちょうど良いかな。

ファイルをエクセルに貼り付けて数値を削除

ワイルドカードを使って置換。VBSはベタに置換した方が速かったけど、PowerShellは未検証のためひとまずこのまま。

2024/10/7追記 動作確認の結果、べたに置換した方がファイル解析が速かった。
●$cleanLine = $line -replace '\d', ''だと30秒
●$cleanLine = $line.Replace("0","").Replace("1","") ・・・.Replace("9","") だと15秒

ファイル毎に頻出する行をカウント

辞書型配列を用意して、分割ファイル毎に集計。メモリ節約のため、出現数がcountBorder未満の行は無視。

元のファイル全体で頻出行をカウント

集計用の辞書型配列を別途用意して、分割ファイル毎の出現数を加算。

注意点

PowerShellをドラッグ&ドロップで実行するためには、おまじないが必要なので注意!

おまじない

ps1ファイルのショートカットを作成し、ショートカットのリンク先の先頭に以下を追加。よく使うので、辞書登録しておくと便利。

powershell -NoProfile -ExecutionPolicy RemoteSigned -File


というわけで、以下ソース。


テキストファイルの頻出文字列を集計.ps1


# ドラッグ&ドロップされたファイルを取得
param (
   [string]$filePath
)
# 解析行数制限と行カウント閾値を設定
$lineLimit = 10000
$countBorder = 100


 


# ファイルを読み込み
Write-Output "Get-Content : $filePath ..." 
Write-Output "" 
$content = Get-Content $filePath
$filePathWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($filePath)


 


# Splitフォルダを作成
$splitFolder = "Split"
if (-not (Test-Path $splitFolder)) {
    New-Item -ItemType Directory -Path $splitFolder
  Write-Output "" 
}


 


# ファイルを分割して保存
Write-Output "Splitting the file into smaller chunks..."
$splitFiles = @()
for ($i = 0; $i -lt $content.Length; $i += $lineLimit) {
   $splitFile = "$splitFolder\$filePathWithoutExtension" + "_split_$($i / $lineLimit).txt"
   $splitFiles += $splitFile
   $content[$i..[math]::Min($i + $lineLimit - 1, $content.Length - 1)] | Set-Content $splitFile
}


 


# 結果を保存するファイル
#$resultFile = "Result_frequentLines.txt"
#if (Test-Path $resultFile) {
#   Remove-Item $resultFile
#}
# 結果を保存するファイル
$resultFile = "$filePathWithoutExtension" + "_frqLines.txt"
if (Test-Path $resultFile) {
   Remove-Item $resultFile
}


 


# 全体の頻出行をカウントする辞書型配列
$globalLineCounts = @{}


 


# 各ファイルを処理
Write-Output "" 
$iMax = $splitFiles.Count
$i = 0
foreach ($splitFile in $splitFiles) {
   $i = $i + 1
   Write-Output "Results have been saved to $resultFile : $i / $iMax"
   $lineCounts = @{}
   $frequentLines = @{}
   $lines = Get-Content $splitFile
   foreach ($line in $lines) {
       # 数値を削除
       $cleanLine = $line -replace '\d', ''
       # 出現回数をカウント
       if ($lineCounts.ContainsKey($cleanLine)) {
           $lineCounts[$cleanLine]++
       } else {
           $lineCounts[$cleanLine] = 1
       }
   }
   # $countBorder 設定した回数以上出現する行を別の辞書型配列に格納
   foreach ($key in $lineCounts.Keys) {
       if ($lineCounts[$key] -ge $countBorder) {
           $frequentLines[$key] = $lineCounts[$key]


 


           # トータル出現回数をカウント
           if ($globalLineCounts.ContainsKey($key)) {
               $globalLineCounts[$key] += $lineCounts[$key]
           } else {
               $globalLineCounts[$key] = $lineCounts[$key]
           }
       }
   }
}


 


# 結果をファイルに出力(出現回数の多い順にソート)
$sortedGlobalLineCounts = $globalLineCounts.GetEnumerator() | Sort-Object Value -Descending
foreach ($entry in $sortedGlobalLineCounts) {
    Add-Content $resultFile "$($entry.Value) | $($entry.Key)"
}

PR
Comment
  Vodafone絵文字 i-mode絵文字 Ezweb絵文字