テキストファイルを文字列で検索して、その行から後ろの 10 行分抽出する、みたいなことができないか、調べてみました。
例えば、シカゴ IMM 通貨先物ポジション。
これの、ユーロのポジションが見たいので、 EURO FX - CHICAGO MERCANTILE EXCHANGE
の文字列がある行から 20 行分出力する、ということをしたいわけです。
行数を指定してテキストファイルを読み込む
まず、文字列を検索することは置いといて、行数を指定してテキストファイルの内容を読み込むには Get-Content
コマンドレットを使えばよさそうです。
PS C:\> (Get-Content Cmdlets.txt)[0..9]
Get-Content
コマンドレットでテキストファイルを読み込むと、内容が配列のように扱えるようです。
[0..9]
はテキストファイルの 1 行目から 10 行目を表しています。
テキストファイルから文字列を検索する
次に、テキストファイルから文字列を検索します。
Select-String
コマンドレットを使えばよさそうです。
PS C:\> Select-String -Path "*.xml" -Pattern "the the"
結果は次のように表示されます。
test1.xml:11:the the
test2.xml:21:the the
今回は、行の番号だけ必要になりますので、 Get-Member
コマンドレットでオブジェクトの内容を見てみます。
PS C:\> Select-String -Path "*.xml" -Pattern "the the" | Get-Member
TypeName: Microsoft.PowerShell.Commands.MatchInfo
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
RelativePath Method string RelativePath(string directory)
ToString Method string ToString(), string ToString(string directory)
Context Property Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename Property string Filename {get;}
IgnoreCase Property bool IgnoreCase {get;set;}
Line Property string Line {get;set;}
LineNumber Property int LineNumber {get;set;}
Matches Property System.Text.RegularExpressions.Match[] Matches {get;set;}
Path Property string Path {get;set;}
Pattern Property string Pattern {get;set;}
LineNumber
というプロパティがそれでしょうか。
PS C:\> Select-String -Path "*.xml" -Pattern "the the" | ForEach-Object { $_.LineNumber }
11
21
行の番号が取れたようです。
文字列を指定してテキストファイルを読み込む
上で試したことを組み合わせてみます。
PS C:\> (Get-Content financial_lof.htm)[((Select-String -Path "financial_lof.htm" -Pattern "EURO FX - CHICAGO MERCANTILE EXCHANGE" | ForEach-Object { $_.LineNumber }) - 1)..((Select-String -Path "financial_lof.htm" -Pattern "EURO FX - CHICAGO MERCANTILE EXCHANGE" | ForEach-Object { $_.LineNumber }) + 18)]
EURO FX - CHICAGO MERCANTILE EXCHANGE (CONTRACTS OF EUR 125,000)
CFTC Code #099741 Open Interest is 652,721
Positions
…略…
ファイルに出力したいので、 Set-Content
コマンドレットにパイプで渡してみます。
PS C:\> (Get-Content financial_lof.htm)[((Select-String -Path "financial_lof.htm" -Pattern "EURO FX - CHICAGO MERCANTILE EXCHANGE" | ForEach-Object { $_.LineNumber }) - 1)..((Select-String -Path "financial_lof.htm" -Pattern "EURO FX - CHICAGO MERCANTILE EXCHANGE" | ForEach-Object { $_.LineNumber }) + 18)] | Set-Content -Path output.htm
検索した文字列の行から後ろの内容を抽出できたようです。
文字列を検索するときに前後の行を抽出できた…
Select-String
コマンドレットの -Context
オプションで前後の行を抽出できたようです。
PS C:\> Select-String -Path "financial_lof.htm" -Pattern "EURO FX - CHICAGO MERCANTILE EXCHANGE" -Context 0, 19
> financial_lof.htm:105:EURO FX - CHICAGO MERCANTILE EXCHANGE (CONTRACTS OF EUR 125,000)
financial_lof.htm:106:CFTC Code #099741 Open Interest is 652,721
financial_lof.htm:107:Positions
…略…
前の行は 0 で、後ろの行は 19 としています。 この数字に、検索する文字列の行は含めていません。
このままだと Microsoft.PowerShell.Commands.MatchInfo
型で返ってきてしまって見づらいので、必要な内容だけを出力します。
PS C:\> Select-String -Path "financial_lof.htm" -Pattern "EURO FX - CHICAGO MERCANTILE EXCHANGE" -Context 0, 19 | ForEach-Object { $_.Line + "`r`n" + ($_.Context.PostContext -Join "`r`n") }
EURO FX - CHICAGO MERCANTILE EXCHANGE (CONTRACTS OF EUR 125,000)
CFTC Code #099741 Open Interest is 652,721
Positions
…略…
マッチした行は Line
プロパティ、後ろの行は Context
プロパティの PostContext
プロパティにありました。
前の行は Context
プロパティの PreContext
プロパティにありました。
それぞれの文字列を改行で区切って出力したかったので、 `r`n
も出力しています。
PowerShell ではバックスラッシュでエスケープするのではなくて、バッククォートでエスケープするようです。
終わり
過去に少しだけ PowerShell を使ったことがありましたが、パイプでオブジェクトが渡せることは便利なことだと気づきました。 Bash は文字列としてパイプするだけだったように思いますので、大きな違いに感じました。
でも、 PowerShell ってあまり普及していない印象です。 Windows の標準の設定で、 PowerShell のスクリプトファイルが実行できないあたりは、PowerShell を採用しづらくさせているような気がします。 セキュリティ的には仕方ないのかな。
PS C:\> Get-ExecutionPolicy
Restricted