Select-String:PowerShell における grep 機能

7月 31, 2018 セキュリティとコンプライアンス, MOVEit

PowerShell コードを書くときに、単一の文字列またはテキストファイル全体の中でテキストを検索したい場合は、どうすればいいでしょうか?Unix/Linux を使ったことがある人は、多分よく使われる grep ユーティリティはご存知でしょう。grep ユーティリティはオプションつきでテキスト検索できますが、このユーティリティは Windows には存在しません。万事休すでしょうか?いいえ、Select-String コマンドレットがあります。

多くの従業員の名前と住所を含む大きな文字列があるとしましょう。この文字列の構造は一般的なものではないので、すべての従業員名を取り出すにはテキスト解析が必要になります。どうすればいいでしょうか?まず、ここで例として使用する文字列を見てみましょう。

||Adam Bertram|| 2122 Acme Ct, Atlantic City, NJ
--||Joe Jonesy||-- 555 Lone St, Las Vegas, NV
==|Suzie Shoemaker|== 6783 Main St, Los Angelas, CA

この文字列に $employees という変数名を割り当てます。文字列から従業員の名前を取得するために、Select-String の右に、シンタックスを並べます。まず、Pattern パラメータを使って従業員名の1つを静的に検索してみましょう。

PS> $employees | Select-String -Pattern 'Adam Bertram'

||Adam Bertram|| 2122 Acme Ct, Atlantic City, NJ
--||Joe Jonesy||-- 555 Lone St, Las Vegas, NV
==|Suzie Shoemaker|== 6783 Main St, Los Angelas, CA

Select-String は何かを返してきました。検索に失敗したら何も返さないので、何かを返したということは一致するものが見つかったことを意味します。ですが、返ってきたものは、文字列全体です。なぜでしょうか?その理由は、Select-String が文字列全体を1個の最小単位として解析したからです。そこで、この文字列を一行ずつの異なる文字列に分割する方法を考えなければなりません。各従業員のレコードはそれぞれ別の行にあるので、改行文字(`n)のところで分割することができます。

PS> $employees = $employees -split "`n"
PS> $employees | Select-String -Pattern 'Adam Bertram'

||Adam Bertram|| 2122 Acme Ct, Atlantic City, NJ

検索結果として正しい一行だけが返ってきました。ちょっと見込みが立ってきました。次に、すべての従業員の情報を獲得できるよう指定する必要があります。そのためには、それぞれの行に共通したパターンがあるかを見てみます。各従業員の名前は縦棒(|)で囲まれているようです。Select-String の Pattern パラメータでこのパターンを使用できます。また、各従業員の名と姓はスペースで区切られているので、これも考慮に入れることができます。

このパターンは、Select-String の Pattern パラメータで、問題なく使える正規表現で表示するよう指定できます。

PS> $employees | Select-String -Pattern '\|\w+ \w+\|'

||Adam Bertram|| 2122 Acme Ct, Atlantic City, NJ
--||Joe Jonesy||-- 555 Lone St, Las Vegas, NV
==|Suzie Shoemaker|== 6783 Main St, Los Angelas, CA

今度は、Select-String は正規表現を使用して各行を返すようになりました。次に、それぞれの社員名を解析する必要があります。現時点では、住所の情報は必要ありません。社員名の解析のために、Select-String が返すマッチしたオブジェクトの Matches プロパティを参照します。

PS> $employees | Select-String -Pattern '\|\w+ \w+\|' | foreach {$_.Matches}

Groups   : {0}
Success  : True
Name     : 0
Captures : {0}
Index    : 1
Length   : 14
Value    : |Adam Bertram|

Groups   : {0}
Success  : True
Name     : 0
Captures : {0}
Index    : 3
Length   : 12
Value    : |Joe Jonesy|

Groups   : {0}
Success  : True
Name     : 0
Captures : {0}
Index    : 2
Length   : 17
Value    : |Suzie Shoemaker|

もう少しです!Value プロパティに必要な従業員名が含まれていることがわかりました。ですが、パイプ文字(縦棒)に囲まれています。これは、正規表現を使った検索でパイプ文字に囲まれたものを指定したためです。検索にはパイプ文字を含める必要がありますが、検索結果としてはパイプ文字を含まない文字列が欲しいところです。どうするべきでしょうか?1つの方法は、正規表現グループを使用することです。正規表現グループは、返してほしい検索結果をカッコで囲んで表現します。この場合、従業員の名と姓を表す正規表現文字列をカッコで囲んで、もう一度やり直してみましょう。

PS> $employees | Select-String -Pattern '\|(\w+ \w+)\|' | foreach {$_.Matches}

Groups   : {0, 1}
Success  : True
Name     : 0
Captures : {0}
Index    : 1
Length   : 14
Value    : |Adam Bertram|

Groups   : {0, 1}
Success  : True
Name     : 0
Captures : {0}
Index    : 3
Length   : 12
Value    : |Joe Jonesy|

Groups   : {0, 1}
Success  : True
Name     : 0
Captures : {0}
Index    : 2
Length   : 17
Value    : |Suzie Shoemaker|

あれぇ、Value はまだパイプ文字を含んでいますね。でも、ちょっと Groups プロパティを見ていただけますか?以前の {0} ではなく、{0, 1} と表示されています。これは、Select-String がグループを認識したことを意味します。このグループの各要素を表示するには、foreach ループを使ってそれぞれに適用します。Groups プロパティは配列なので、1つの要素ごとに、カッコでくくられたパイプ文字のない部分をその Value プロパティとして表示するようにします。

PS> $employees | Select-String -Pattern '\|(\w+ \w+)\|' | foreach {$_.Matches.Groups[1].Value}
Adam Bertram
Joe Jonesy
Suzie Shoemaker

これで文字列から各従業員の名前を取り出すことができました!Select-String はもっといろいろなことが可能なので、検索してチェックしてみてください。

Adam Bertram

Adam Bertram is a 20-year veteran of IT. He’s currently an automation engineer, blogger, independent consultant, freelance writer, author, and trainer. Adam focuses on DevOps, system management, and automation technologies as well as various cloud platforms. He is a Microsoft Cloud and Datacenter Management MVP and efficiency nerd that enjoys teaching others a better way to leverage automation.

Read next PowerShell を使用した再起動時間の測定