検索した文字列の行から後ろの内容を抽出したい(PowerShell)

テキストファイルを文字列で検索して、その行から後ろの 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

参考