Windowsを操作していて、こんな経験がありませんか?
「あのファイルが必要なのに、どこにあるか分からない!」「ひとつひとつファイルを開いて確認するのは実質無理だ」
この記事では、こうしたファイル検索に関する問題を解決するために、「現在のディレクトリ以下を再帰的に検索し、指定した文字列を含むファイルの一覧を出力する方法」を解説します。
特定の文字列を含んだファイルを一覧表示する方法
下記のスクリプトをコピペしてください。ファイル名はfindfiles.ps1
とします。
# findfiles.ps1
Param(
$arg # 引数
)
try {
$ErrorActionPreference = "Stop"
$count = 0
$files = Get-ChildItem -Recurse -File
$totals = ($files | Measure-Object).Count
$files | ForEach-Object {
$count += 1
$per = ($count/$totals)*100
$rtime = $totals - $count
Write-Progress -Activity "Progress: " -Status "$per% Complete" -PercentComplete $per -secondsRemaining $rtime
if (Get-Content $_.FullName | Select-String -Pattern "$arg" -Quiet) {
Write-Host "ファイル名: $($_.FullName)"
}
}
} catch {
Write-Host $_ -ForegroundColor "Red" # catch内の例外情報は、「$_」変数でアクセスできる。
} finally {}
上記のスクリプトを、PATHが通っているフォルダに保存します。
「PATH通し」に関して詳細を知りたい方は、以下の記事をご参照ください。
使い方
Powershellを立ち上げ、findfiles "文字列"
とします。すると、現在ディレクトリ以下全てを検索し、指定した文字列が記述されているファイル全てを一覧表示します。
PS C:\Users\ユーザー名> findfiles "文字列"
使用例
試しに、現在「テスト」フォルダにいるとします。
テストフォルダの中には、2つのフォルダがあり、さらにそれぞれのフォルダの中に3つのフォルダがあります。それら全てのフォルダに「test.txt
」が存在し、その内2つのファイルには「当たり」と書かれているとします。
以下のような構造です。
PS C:\Users\ユーザー名\テスト> tree
.
├── test.txt
├── test01
│ ├── 01
│ │ └── test.txt
│ ├── 02
│ │ └── test.txt
│ ├── 03
│ │ └── test.txt
│ └── test.txt
└── test02
├── 01
│ └── test.txt
├── 02
│ └── test.txt
├── 03
│ └── test.txt
└── test.txt
このようにサブフォルダがいくつも存在する構造では、ファイルの中身を一つ一つ調べるのも一苦労します。
そこで、ターミナル上でfindfiles "当たり"
と入力しましょう。
すると、当たりが書かれたファイルパスが一覧表示されました。
PS C:\Users\ユーザー名\テスト> findfiles "当たり"
ファイル名: C:\Users\ユーザー名\テスト\test01\test.txt
ファイル名: C:\Users\ユーザー名\テスト\test01\03\test.txt
Powershellスクリプトの構造解説
このスクリプトがどんな構造になっているのか解説します。
1.引数
まずParam()
とは、Powershellで引数を設定するコードです。
ここに引数を設定すると、./hoge.ps1 引数
のように引数をスクリプトに渡せるようになります。
Param(
$arg # 引数
)
2.例外処理
try{} catch{} finally{}
は例外処理のコードです。$ErrorActionPreference = "Stop"
は、エラーが発生した際に、強制的に処理を止めるための記述です。
try {
$ErrorActionPreference = "Stop"
処理
} catch {
例外処理
} finally {}
3.処理
以下が、「現在ディレクトリ以下を全て検索し、指定した文字列を含んだファイル一覧を出力する方法」のコードです。
$files = Get-ChildItem -Recurse -File
$totals = ($files | Measure-Object).Count
$files | ForEach-Object {
if (Get-Content $_.FullName | Select-String -Pattern "$arg" -Quiet) {
Write-Host "ファイル名: $($_.FullName)"
}
}
1つずつ解説します。
$files = Get-ChildItem -Recurse -File
- この行では、
Get-ChildItem
コマンドレットを使用して、指定されたディレクトリ内のファイルの一覧を取得しています。-Recurse
フラグは、サブディレクトリ内のファイルも含めて再帰的に検索することを指示します。-File
フラグは、ファイルのみを取得することを示しています。取得したファイルは$files
という変数に格納されます。
- この行では、
$totals = ($files | Measure-Object).Count
- この行では、取得したファイルの数を数えます。
Measure-Object
コマンドレットは、渡されたオブジェクトのプロパティを測定し、統計情報を提供します。.Count
は測定されたオブジェクトの数を返します。
- この行では、取得したファイルの数を数えます。
$files | ForEach-Object { ... }
- この行は
$files
に含まれる各ファイルに対して処理を行います。ForEach-Object
コマンドレットは、配列内の各オブジェクトに対して指定された処理を実行します。各ファイルに対して実行される処理は、波括弧{}
内に記述されています。
- この行は
if (Get-Content $_.FullName | Select-String -Pattern "$arg" -Quiet) { ... }
- この行では、ファイル内のコンテンツを取得し、特定のパターンに一致するかどうかを確認しています。
Get-Content
コマンドレットは、ファイルの内容を取得します。$_
は現在のファイルオブジェクトを表し、FullName
プロパティはファイルのフルパスを返します。Select-String
コマンドレットは、指定されたパターンに一致する行を選択します。-Pattern
フラグは検索するパターンを指定し、-Quiet
フラグは一致が見つかった場合に真を返します。if
文の条件式が真の場合は、波括弧{}
内のコードが実行されます。
- この行では、ファイル内のコンテンツを取得し、特定のパターンに一致するかどうかを確認しています。
Write-Host "ファイル名: $($_.FullName)"
- この行は、一致したファイルのフルパスを出力します。
Write-Host
コマンドレットは、指定した文字列をコンソールに表示します。$($_.FullName)
は、現在のファイルのフルパスを表します。
- この行は、一致したファイルのフルパスを出力します。
一番のポイントは、「Get-Content
コマンドレット」と「Select-String
コマンドレット」です。
この2つを組み合わせることで、ファイルの中身に特定の文字列があるかどうかを真偽で判定することができます。
4.進捗バー
残りのコードは「処理の進捗」を表示するためのものです。先程の「テスト」フォルダではファイル数が少ないため、実行しても進捗バーは一瞬で消えますが、数千から数万のファイルを抱えるフォルダで使用する場合は、非常に役立つと思います。
Write-Progress -Activity "Progress: " -Status "$per% Complete" -PercentComplete $per -secondsRemaining $rtime
処理の進行状況を表示するためにWrite-Progress
コマンドレットが使用されています。-Activity
フラグは進行状況ウィンドウに表示されるアクティビティ名を指定し、-Status
フラグは進行率のステータスを示します。-PercentComplete
フラグは進行率をパーセンテージで指定し、-secondsRemaining
フラグは残りの秒数を指定します。
まとめ
この記事では、現在のディレクトリ以下を再帰的に検索し、指定した文字列を含むファイルの一覧を出力する方法を解説しました。
findfiles.ps1
を使用すれば、findfiles "文字列"
という簡単なコマンドで、指定した文字列が含まれるファイルのパス一覧を取得できます。
特に、数千から数万のファイルを持つプロジェクトでの使用に役立つかと思われます。