外部アプリケーションなしのWindowsでgrepみたいなことをする

【追記】こちらの記事で触れたPowerShellSelect-String については別記事でもう少し詳しく触れています。こちらもご覧ください。 tech.sanwasystem.com

こんにちは、PowerShellが好きなwakです。ログファイルの検索を行うときなど、Windowsでもgrepのような文字列検索を行いたい状況があります(昨日たくさんやりました)。そのための方法をメモしておきます。

インデックス

  1. findコマンドを使う(お手軽・低機能・UTF-8には非対応)
  2. findstrコマンドを使う(多少は複雑なことができる・UTF-8には非対応)
  3. PowerShellのSelect-Stringコマンドレットを使う(正規表現利用可・UTF-8対応)
    1. 簡単に結果をファイル出力する(簡単・制限あり)
    2. 頑張って結果をファイル出力する(面倒・書式制御可)

前もって書いておくと、PowerShellでテキスト検索を行い、その結果をUTF-8で出力する方法は次の通りです。

$utf8 = New-Object System.Text.UTF8Encoding($False) #BOMなしUTF-8
$result = Select-String "FATAL|ERROR" *.log -Encoding default # 検索結果を取得
[System.IO.File]::WriteAllLines("result4.txt", ($result | % { $_.get_line() }), $utf8) # 出力

なお、検索対象は以下の内容のファイル2件とします。文字コードは最初はShift-JISで用意してください。

cat20150421.log
2015-04-21 19:30:00 ERROR カリカリがないです
2015-04-21 20:00:00 DEBUG 寝顔がかわいい
2015-04-21 20:30:00 INFO 猫が起きました
2015-04-21 21:30:00 FATAL 猫がPCの上で粗相をしました
cat20150422.log
2015-04-22 12:00:00 INFO 猫が寝ました
2015-04-22 12:30:00 DEBUG 寝顔がかわいい
2015-04-22 13:30:00 INFO 猫が起きました
2015-04-22 14:00:00 FATAL 猫がお皿をひっくり返しました

f:id:nurenezumi:20150422113021j:plain

今回は本文とちょっとだけ関係のある可愛い猫

1. findコマンドを使う

MS-DOS時代からある由緒正しいコマンドです。ただし、 Shift-JISとUTF-16(BOMあり)のファイルにしか対応していません。

C:\blog>find "FATAL" cat20150422.log

---------- CAT20150422.LOG
2015-04-22 14:00:00 FATAL 猫がお皿をひっくり返しました

複数ファイルも受け付けます。

C:\blog>find "FATAL" *.log

---------- CAT20150421.LOG
2015-04-21 21:30:00 FATAL 猫がPCの上で粗相をしました

---------- CAT20150422.LOG
2015-04-22 14:00:00 FATAL 猫がお皿をひっくり返しました

デイリーでローテーションしているログファイルの場合など、ファイル名は不要なケースもあるでしょう。上記の結果からさらに"FATAL"を検索すれば検索結果だけが絞り込めます。

C:\blog>find "FATAL" *.log | find "FATAL"
2015-04-21 21:30:00 FATAL 猫がPCの上で粗相をしました
2015-04-22 14:00:00 FATAL 猫がお皿をひっくり返しました

100行ぐらい出てくるからクリップボードにコピーしたいな……という場合、コマンドの末尾に| clipを付け加えましょう。上記の結果がそのままクリップボードにコピーされます。

C:\blog>find "FATAL" *.log | find "FATAL" | clip

結果をファイル出力する場合はリダイレクトしてください。

C:\blog>find "FATAL" *.log | find "FATAL" > result.txt

2. findstrコマンドを使う

findコマンドの後継がfindstrコマンドで、こちらは正規表現(のごくごく一部)が使えます。@ITの記事が網羅的なので読むと話が早いです。こちらもShift-JISにしか対応していません。

C:\blog>findstr "FATAL" *.log
cat20150421.log:2015-04-21 21:30:00 FATAL 猫がPCの上で粗相をしました
cat20150422.log:2015-04-22 14:00:00 FATAL 猫がお皿をひっくり返しました
C:\blog>findstr /R "した\>" *.log
cat20150421.log:2015-04-21 20:30:00 INFO 猫が起きました
cat20150421.log:2015-04-21 21:30:00 FATAL 猫がPCの上で粗相をしました
cat20150422.log:2015-04-22 12:00:00 INFO 猫が寝ました
cat20150422.log:2015-04-22 13:30:00 INFO 猫が起きました
cat20150422.log:2015-04-22 14:00:00 FATAL 猫がお皿をひっくり返しました

3. PowerShellのSelect-Stringコマンドレットを使う

説明するより見た方が早いでしょう。

PS C:\blog> Select-String "FATAL|ERROR" *.log -Encoding default

cat20150421.log:1:2015-04-21 19:30:00 ERROR カリカリがないです
cat20150421.log:4:2015-04-21 21:30:00 FATAL 猫がPCの上で粗相をしました
cat20150422.log:4:2015-04-22 14:00:00 FATAL 猫がお皿をひっくり返しました

PowerShellの標準の文字エンコーディングUTF-16(BOMあり)です。UTF-8は自動認識してくれるようですが、失敗する場合は文字コード-Encodingで明示的に指定してあげる必要があります。

文字コード引数
Shift-JISdefault
UTF-8utf8
UTF-16(BOMあり)unicode

3.1 簡単にファイル出力する

リダイレクトで出力するのが一番手っ取り早いのですが、文字エンコーディングUTF-16(BOMあり)になります。

PS C:\blog> Select-String "FATAL|ERROR" *.log -Encoding default > result.txt

変更したい場合はOut-Fileを使いましょう。UTF-8にしてみます。

PS C:\blog> Select-String "FATAL|ERROR" *.log -Encoding default | Out-File -FilePath result.txt -Encoding utf8 -Width 10000

最後の-Widthは1行あたりの最大長を指定するもので、これを超えた場合は強制的に改行が入ります。結果の書式はどちらもこのようになります。順にファイル名、行数、内容です。

cat20150421.log:1:2015-04-21 19:30:00 ERROR カリカリがないです
cat20150421.log:4:2015-04-21 21:30:00 FATAL 猫がPCの上で粗相をしました

この方法は簡単ですが、難点がいくつかあります。

  • 書式が選べない(上のようになる)
  • 1行が一定の長さを超える場合は強制的に改行が入る(-Widthで指定可能)
  • UTF-8で出力しても強制的にBOMが入る

3.2 頑張って綺麗にファイル出力する

Select-StringコマンドレットはMicrosoft.PowerShell.Commands.MatchInfoオブジェクトの配列を返します。画面やファイルにテキスト表示する際はこのMatchInfoクラスのToString()メソッドが暗黙的に呼ばれるのですが、これを自分でテキスト変換すれば上述した難点の1つめは解決します。(ついでに書式を自由に決めることもできます)

さらにOut-Fileコマンドレットに任せず、自前でファイル出力を行うことで難点の2つめ・3つめも解決しましょう。.NETのSystem.IO.FileクラスのWriteAllLinesメソッドを直接呼び出します。

これをまとめると冒頭のコードになります。頑張ればワンライナーになりますが……

$utf8 = New-Object System.Text.UTF8Encoding($False) #BOMなしUTF-8
$result = Select-String "FATAL|ERROR" *.log -Encoding default # 検索結果を取得
[System.IO.File]::WriteAllLines("result.txt", ($result | % { $_.get_line() }), $utf8) # 出力

PowerShellを使ってみよう

今回はテキスト検索をネタにしてみました。他環境と比べると記述量は若干増えるので、取っつきづらい面は確かにあります。しかし上でちょっと触れてもらったように、コマンドレットとコマンドレットの間は.NETのオブジェクトでやり取りされるため、柔軟性や自由度は比較になりません。

さして量が多くなければメモ帳で開いて検索することもできますが、それを繰り返すのは時間の無駄ですし、ミスが紛れ込む危険も生じます。PowerShellの利点はWindowsありさえすれば利用できることがほぼ間違いなく保証されていることで、今後もWindowsでお仕事をするあなたの頼もしい相棒になるでしょう。