grepコマンドとPowerShellのsls (Select-String)の比較

こんにちは、PowerShellが好きなwakです。以前書いたこちらの記事をリライトします。

tech.sanwasystem.com

はじめに

Windowsにはgrepコマンドがないとはよく言われることですが、Windowsに標準で備わっているPowerShellにはgrepよりも高性能な検索コマンドレット、Select-Stringが実装されています。この記事はgrepSelect-Stringとを比較し、慣れない人でもすぐに使えるようにすることを目的としています。また、「そもそもこのSelect-Stringコマンドをどうやって実行するの?」といったPowerShellの(超)基本的な使い方は本稿の末尾に記載してあります。

grepとの比較表

grep Select-String
大文字・小文字の区別 -i -CaseSensitive *1
正規表現 -E -SimpleMatch *2
マッチしない行を検索 -v -NotMatch
前後表示 -A 《行数》
-B 《行数》
-Context 《行数》
文字コード指定 不可*3 -Encoding 《文字エンコーディング名》
ディレクトリを
再帰的に検索
-r (dir -recurse 《ファイル名パターン》)
ファイル名のみ出力 -l
-L
(後述)

以下、順に解説していきます。

1件のファイルから検索をする場合

grepで1件のファイルから指定したフレーズを検索する場合はこのように実行しますが、

grep pattern filename.txt

PowerShellでこちらに相当する処理はこうなります。

Select-String "pattern" filename.txt

Select-Stringにはslsという省略形が用意されています。エイリアスですのでどちらを使ってもまったく同じ結果が得られます。

sls "pattern" filename.txt

本稿では以降もSelect-Stringと表記しますが、もちろん実際に使うときにはslsで構いません。

-i : 大文字・小文字を区別したい/区別したくない

デフォルトでは大文字・小文字は区別されません。区別したい場合は-CaseSensitiveオプションを追加します。

# "Neko"にはマッチするが"neko"にはマッチしない
Select-String "Neko" filename.txt -CaseSensitive

-e : 正規表現を使いたい/使いたくない

パターンはデフォルトで正規表現とみなされます。正規表現として扱ってほしくない場合は-SimpleMatchオプションを追加します。

# "SELECT * FROM"にマッチ。-CaseSensitiveも指定しているので小文字ならマッチしない
Select-String "SELECT * FROM" filename.txt -SimpleMatch -CaseSensitive

-v : パターンにマッチしない行を探したい

-NotMatchオプションを使います。

# 「A」「a」「B」「b」「C」「c」のいずれも含まない行にマッチ
Select-String "[A-C]" filename.txt -NotMatch

-A / -B : パターンにマッチした前後の行も出力したい

-Contextオプションで行数を指定します。残念ながら前後別々に行数を指定することはできません。

# "function", "Function"などがある行と、その上下3行ずつ(計7行)を検索
Select-String "function" HogeClass.cs -Context 3

文字エンコーディングを指定したい

検索対象のファイルがShift-JISだと文字エンコーディングの自動判定ができないので、明示的に指定してあげなければいけません。これは-Encodingオプションを使います。

Select-String "猫" filename.txt -Encoding oem

-Encodinggの後ろにスペース1個)と入力した後でTABキーを押せば指定可能な文字エンコーディングが順に表示されます。oemがShift-JISだと覚えておけば事足りるでしょう(UTF-8がデフォルトです)。

複数のファイルから検索する場合

ここまでは1件のファイルから検索を行うものでした。次は複数件のファイルを対象として検索を行う方法です。

ワイルドカードを使いたい

まず普通にワイルドカードが使えます。

Select-String "pattern" *.txt

特定の複数のファイルを指定して検索したい

ワイルドカードではなく、ファイル名を書き並べたいならこうなります。カッコとダブルクオートが少々見づらいのですが我慢してください。

Select-String "pattern" ("z:\log\filename1.txt", "c:\data\text\filename2.txt")

ディレクトリを再帰的にたどって検索したい

次のどちらでも好きなものを選んでください。c:\data以下にある*.cs全てを検索対象としています。

Select-String "pattern" (dir -recurse c:\data\*.cs)
dir -recurse c:\data\*.cs | Select-String "pattern"

Dir -recurse c:\data\*.csは、c:\data以下から*.csを全部探すという意味になります(1回このコマンドだけを実行してみるといいです)。ディレクトリ名を省略したらカレントディレクトリになります。

特定の拡張子のファイルは除外したい

-Excludeで除外できます。

# カレントディレクトリ以下の全てのファイルから検索、ただし*.exeと*.binは除く
Select-String "pattern" (dir -recurse *.* -Exclude *.exe, *.bin)
dir -recurse *.* -Exclude *.exe, *.bin | Select-String "pattern"

検索対象のファイルを別のテキストファイルから与えたい

1行に1件ファイル名(フルパス)が書いてあるテキストファイルfilelist.txtがあったとして、そこから検索をする場合です。

Get-Content filelist.txt -Encoding UTF8 | % { Select-String "pattern" $_ }

なお、カレントディレクトリ以下の全てのファイル名(フルパス)をファイルに書き出すにはこのように実行します。ワイルドカードの部分は必要に応じて*.txtなどと書き換えてください。

dir -Recurse -File *.* | % { $_.FullName } | Out-File filelist.txt -Encoding UTF8

出力の書式や出力先を変える

標準の出力の書式では、ファイル名・行数・行の内容(行全体)が出力されます。なぜかというと、この結果1行1行はそれぞれMicrosoft.PowerShell.Commands.MatchInfoクラスのインスタンスであり、このToString()メソッドがそのような書式の文字列を返すようになっているからです。

https://msdn.microsoft.com/ja-jp/library/microsoft.powershell.commands.matchinfo(v=vs.85).aspx

このオブジェクトはFileName, Lineといったプロパティを持っていますから、欲しいものを好きなように並べて結果を出力することができます。

結果だけを画面に出力したい

ファイル名はいらない場合です。

# 末尾に「| Select Line」と書き加える
Select-String "pattern" filename | Select Line
Select-String "pattern" (dir -recurse *.* -Exclude *.exe, *.bin) | Select Line

以下では例は1行ずつしか示しませんが、どのような検索を行ったかにかかわらず、検索を行ったコマンドの末尾に「| ~」と書き加える形でOKです。

結果だけを画面に出力したい

ファイル名はいらない場合です。

Select-String "pattern" filename | Select Line

少し書式は面倒ですが、こちらの方を使うと色々と融通がききます。

Select-String "pattern" filename | % { $_.Line }

マッチした部分だけを出力したい

Select-String "pattern" filename | % { $_.Matches.Value }

このMatchesは.NETのSystem.Text.RegularExpressions.Matchクラスのインスタンスです。パターンに()を使っていればGroupsプロパティでさらに部分文字列を取り出せたりします。*4

Select-String "^(\d{4})-(\d{2})-(\d{2})" (dir -Recurse *.*) | % { $g = $_.Matches.Groups; $g.Groups[1].Value }

ファイル名(フルパス)、行数、行の内容をタブ区切りで出力したい

Select-String "pattern" filename | % { [string]::Format("{0}`t{1}`t{2}", $_.Path, $_.LineNumber, $_.Line) }

.NETを使っている人にはお馴染みのSystem.String.Format()を使ってみました。`tは他言語の\tと同じタブ文字を意味します。PowerShellらしく書くなら次のどちらかになりそうです。

# 末尾に「|」以降を書き加える
Select-String "pattern" filename | % { "{0}`t{1}`t{2}" -f $_.Path, $_.LineNumber, $_.Line }
Select-String "pattern" filename | % { ( $_.Path, $_.LineNumber, $_.Line) -join "`t" }

色々と試行錯誤したい

検索結果をまず先に変数に格納しておけば毎回検索する手間が省けるので話が早くなります。

$searchresult = Select-String "pattern" filename

これでまず検索結果が変数に入ります(画面には何も出ません)。

$searchresult

とだけ入力して実行すれば検索結果が出力されますし、

$searchresult | Select Line
$searchresult | % { "{0}`t{1}`t{2}" -f $_.Path, $_.LineNumber, $_.Line }

のように色々と試すこともできます。

結果をファイルに書き出す

検索結果は画面に出力するだけではなくファイルに書き出すこともできます。これについてはgrepと同じようにリダイレクトで吐き出すのが一番簡単でしょう。

Select-String "pattern" filename | % { ... } > result.txt
$searchresult | Select Line > result.txt

文字エンコーディングUTF-16になります。これが気に食わない人は、末尾に> ファイル名と書き加えるのではなく、さらにパイプをつなげてOut-Fileコマンドレットに渡します。

Select-String "pattern" filename | % { ... } | Out-File result.txt -Encoding UTF8
$searchresult | Select Line | Out-File result.txt -Encoding UTF8

ここで出力されるファイルはBOMありUTF-8という微妙なフォーマットになるのですが、これは諦めてください(少しコードを書けば解決するのですが)。

PowerShellの基本

起動

まず、何はなくともPowerShellを立ち上げましょう。

なんでもいいです。

補完

dir, Select-Stringといったコマンドレットによって入力可能なオプションはあらかじめ決まっています。-まで入力してTABキーを連打すると、指定可能なオプションが列挙されます。たとえば-CaseSensitiveであれば、-cまで入力してTABキーを押せば一発で補完される、といった具合です。

また、ファイル名・ディレクトリ名も同じように補完が効きます。

大文字・小文字

コマンドレット・オプションの大文字・小文字は区別されません。Select-StringSELECT-STRINGselect-stringと書いても問題ありません。

\エスケープっていらないの?

PowerShellでは\文字は特別な意味を持ちません。AB.と同じ普通の文字です。したがって\エスケープは必要ありません。特に正規表現を書くときにはこれが楽です。

そのかわり、たとえば改行文字はn`、タブ文字は\t`と書くことになっています。バッククオートそのものを検索したいときはバッククオートを2つ連ねて書いてください。

*1:デフォルトで区別なし

*2:デフォルトで正規表現

*3:grep単体では不可能。nkfとか使う

*4:"?"の書式でのグループ名には対応していないようでした