PowerShellの基本的なことがわかってなかったシリーズ第5弾です。
PowerShell版のGrepともいえるSelect-Stringコマンドレットですが、ヘルプには、
文字列とファイル内のテキストを検索します。
としか記載されていません。
PowerShellでは基本オブジェクトを扱うのにもかかわらず"文字列"とだけ言われても何を指しているのかさっぱりわかりません。
そこで本エントリではこの"文字列"が具体的に何を指すのかを調べてみました。
調査した環境はWindows 8.1、PowerShell 4.0になります。
PowerShellのバージョンによっては結果が違うかもしれませんが多分同じか大差ないと思います。(未検証です。すいません。)
Select-Stringコマンドレットの実体
Select-String
コマンドレットの実体はMicrosoft.PowerShell.Commands.Utility.dll
にあるSelectStringCommand
クラスになります。
ILSpy等でこのクラスの中身を見て具体的に何を検索しているのか調べることができるので、以降はその結果について書いていきます。
ファイルを検索する場合
Select-String
コマンドレットは-Path
および-LiteralPath
パラメータを指定してファイルを検索する場合と-InputObject
を指定してオブジェクトを検索する場合にパターン分けされます。
はじめにファイルを検索する場合について調べてみます。
この場合はわかりやすく、-Path
および-LiteralPath
で指定されたファイル内部のテキストが検索対象になります。
-Encoding
パラメーターで指定されたエンコーディング(デフォルトはUTF8)でファイルが開かれ中のテキストが1行ずつ検索されていきます。
ちなみに-Path
と-LiteralPath
の違いはファイル名にワイルドカードを許可するかしないかになります。
オブジェクトを検索する場合
-InputObject
を指定した場合はオブジェクトが検索対象になります。
この場合は指定されるオブジェクトによって内部動作が変わってきます。
1. System.IO.FileInfoの場合
-InputObject
にSystem.IO.FileInfo
が来た場合は前項同様にファイルの中身を検索します。
FileInfo
を特別扱いしている理由はあまりよくわかりません...
例として、あるディレクトリにHello.txt
というファイルがあり、その中身が、
Hello.txt
Hello World.
な場合を想定します。
Get-ChildItem(ls)
コマンドレットを実行した結果が
PS C:\temp\HelloDir> ls ディレクトリ: C:\temp\HelloDir Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2015/08/20 21:36 12 Hello.txt
な場合、戻り値はHello.txt
に対するFileInfo
クラスになります。
PS C:\temp\HelloDir> (ls).GetType().FullName System.IO.FileInfo PS C:\temp\HelloDir> (ls).FullName C:\temp\HelloDir\Hello.txt
ここでこの結果に対してSelect-String
を実行すると、
PS C:\temp\HelloDir> ls | Select-String "hello" Hello.txt:1:Hello World.
となりHello.txt
の中身を検索し1行目の"Hello"がヒットしていることがわかります。
ちなみに、ファイルの中身でなくHello.txt
のファイル名を検索したい場合は、
PS C:\temp\HelloDir> ls | Out-String -Stream | Select-String "hello" ディレクトリ: C:\temp\HelloDir -a--- 2015/08/20 21:36 12 Hello.txt
の様に一度Out-String -Stream
を通してやると良いです。
尚、System.IO.DirectoryInfo
はディレクトリ名が検索対象になります。
PS C:\temp\HelloDir> (ls ..\ -Directory).GetType().FullName System.IO.DirectoryInfo PS C:\temp\HelloDir> ls ..\ -Directory | Select-String "hello" HelloDir
2. Microsoft.PowerShell.Commands.MatchInfoの場合
Microsoft.PowerShell.Commands.MatchInfo
はSelect-String
の戻り値の型です。
検索されてヒットされた結果を格納するオブジェクトになります。
このクラスを特別扱いしているのはSelect-String
された結果をさらにパイプラインでつないだ場合を想定しているのだと思われます。
-InputObject
にMatchInfo
が来た場合はMatchInfo.Line
プロパティ(String型)の内容が検索対象になります。
先ほどの例を使うと、
PS C:\temp\HelloDir> ls | Select-String "hello" Hello.txt:1:Hello World. PS C:\temp\HelloDir> (ls | Select-String "hello").GetType().FullName Microsoft.PowerShell.Commands.MatchInfo PS C:\temp\HelloDir> (ls | Select-String "hello").Line Hello World.
となり、MatchInfo.Line
プロパティは"Hello World."であることがわかります。
これに対して、
PS C:\temp\HelloDir> ls | Select-String "hello" | Select-String "txt" PS C:\temp\HelloDir>
とSelect-String "txt"
でパイプラインを繋げた場合、コンソールには"Hello.txt"が表示されいるもののMatchInfo.Line
には"txt"は含まれていないため何もヒットしません。
Select-String "world"
であればヒットします。
PS C:\temp\HelloDir> ls | Select-String "hello" | Select-String "world" Hello.txt:1:Hello World.
ちなみに先ほどと同様にOut-String -Stream
を通した場合はSelect-String "txt"
で検索がヒットします。
PS C:\temp\HelloDir> ls | Select-String "hello" | Out-String -Stream | Select-String "txt" Hello.txt:1:Hello World.
3. その他の型の場合
-InputObject
にその他の型のオブジェクトが来た場合はその型をSystem.Management.Automation.LanguagePrimitives.ConvertTo()
メソッドでString型に変換した結果が検索対象になります。
名前から察するにこのメソッドはPowerShellの型変換機構そのものだと思われます。なので-InputObject
にその他の型のオブジェクトが来た場合はPowerShellの型変換機構によってString型に変換された結果が検索対象になるといっても良いと思います。
流石にこのSystem.Management.Automation.LanguagePrimitives.ConvertTo()
メソッドの詳細を読み解くことはできませんでした...
ただ、いろいろなクラスで挙動を見た限り、おおむねToString()
メソッドの結果と近いのかなぁと思いました。
これまでの例でGet-ChildItem(ls)
した結果をFormat-List(fl)
した場合、以下の様な結果になります。
PS C:\temp\HelloDir> ls | fl ディレクトリ: C:\temp\HelloDir Name : Hello.txt Length : 12 CreationTime : 2015/08/20 21:36:47 LastWriteTime : 2015/08/20 21:36:59 LastAccessTime : 2015/08/20 21:36:47 VersionInfo : File: C:\temp\HelloDir\Hello.txt InternalName: OriginalFilename: FileVersion: FileDescription: Product: ProductVersion: Debug: False Patched: False PreRelease: False PrivateBuild: False SpecialBuild: False Language:
ls | fl
した結果、コンソールにはフォーマットされた内容が表示されますが、戻り値はMicrosoft.PowerShell.Commands.Internal.Format.ほげほげ
なクラスの配列になります。
PS C:\temp\HelloDir> ls | fl | select { $_.GetType().FullName } $_.GetType().FullName ----------------------- Microsoft.PowerShell.Commands.Internal.Format.FormatStartData Microsoft.PowerShell.Commands.Internal.Format.GroupStartData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.GroupEndData Microsoft.PowerShell.Commands.Internal.Format.FormatEndData
このため単純にSelect-String
を実行しても何もヒットしません。
PS C:\temp\HelloDir> ls | fl | Select-String "hello" PS C:\temp\HelloDir>
ここでなんでもありありな検索条件.*
を渡してやると
PS C:\temp\HelloDir> ls | fl | Select-String ".*" Microsoft.PowerShell.Commands.Internal.Format.FormatStartData Microsoft.PowerShell.Commands.Internal.Format.GroupStartData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.GroupEndData Microsoft.PowerShell.Commands.Internal.Format.FormatEndData
となり、文字列型に変換(≒ToString())された後の文字列が検索対象となっていることがわかります。
PS C:\temp\HelloDir> ls | fl | select { $_.ToString() } $_.ToString() --------------- Microsoft.PowerShell.Commands.Internal.Format.FormatStartData Microsoft.PowerShell.Commands.Internal.Format.GroupStartData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.GroupEndData Microsoft.PowerShell.Commands.Internal.Format.FormatEndData
また、これまでと同様にOut-String -Stream
を通してやるといい感じになってくれます。
PS C:\temp\HelloDir> ls | fl | Out-String -Stream | Select-String "hello" ディレクトリ: C:\temp\HelloDir Name : Hello.txt VersionInfo : File: C:\temp\HelloDir\Hello.txt
追記
2015/08/25追記
以下の補足記事を書きましたのでこちらも併せてご覧ください。