PowerShellでフォルダとファイル名を正規表現で絞り込んで一覧を取得する

## やりたいこと
1. ファイル名一覧を取得したい。
2. 正規表現で取得対象のフォルダ・ファイル名を絞り込みたい。（bkフォルダやoldフォルダを対象外にするなど）

## 結論

### ソースコード

```powershell:find.ps1
param (
    [string]$fileNameMatch,
    [string]$path,
    [string]$fileNameNotMatch,
    [string]$dirMatch,
    [string]$dirNotMatch,
    [switch]$recurse
)

$fileList = Get-ChildItem -File -Path $path -Recurse:$recurse

if (0 -ne $fileNameMatch.Length)    { $fileList = $fileList | Where-Object { $_.Name          -match    $fileNameMatch    } }
if (0 -ne $fileNameNotMatch.Length) { $fileList = $fileList | Where-Object { $_.Name          -notmatch $fileNameNotMatch } }
if (0 -ne $dirMatch.Length)         { $fileList = $fileList | Where-Object { $_.DirectoryName -match    $dirMatch         } }
if (0 -ne $dirNotMatch.Length)      { $fileList = $fileList | Where-Object { $_.DirectoryName -notmatch $dirNotMatch      } }

$fileList | ForEach-Object { $_.FullName }
```

### 実行例

#### フォルダ構成

```
C:.
│  find.ps1
│  サンプル01.txt
│  サンプル02.txt
│  サンプル03.txt
│
├─bk
│  サンプル01.txt
│  サンプル02.txt
│
└─old
   20211225_サンプル01.txt
   20211225_サンプル02.txt
```

#### ファイル名一覧を取得する

```
> ./find.ps1
C:\Users\*****\Documents\サンプル\find.ps1
C:\Users\*****\Documents\サンプル\サンプル01.txt
C:\Users\*****\Documents\サンプル\サンプル02.txt
C:\Users\*****\Documents\サンプル\サンプル03.txt
```

`recurse`オプションを指定するとサブフォルダも取得できる。

```
> ./find.ps1 -recurse
C:\Users\*****\Documents\サンプル\find.ps1
C:\Users\*****\Documents\サンプル\サンプル01.txt
C:\Users\*****\Documents\サンプル\サンプル02.txt
C:\Users\*****\Documents\サンプル\サンプル03.txt
C:\Users\*****\Documents\サンプル\bk\サンプル01.txt
C:\Users\*****\Documents\サンプル\bk\サンプル02.txt
C:\Users\*****\Documents\サンプル\old\20211225_サンプル01.txt
C:\Users\*****\Documents\サンプル\old\20211225_サンプル02.txt
```

### ファイル名で絞り込む

```
> ./find.ps1 -recurse -fileNameMatch "01"
C:\Users\*****\Documents\サンプル\サンプル01.txt
C:\Users\*****\Documents\サンプル\bk\サンプル01.txt
C:\Users\*****\Documents\サンプル\old\20211225_サンプル01.txt
```

正規表現パターンに含まれない条件も同時に指定する場合、下記のように実行する。

```
> ./find.ps1 -recurse -fileNameMatch "01" -fileNameNotMatch "_"
C:\Users\*****\Documents\サンプル\サンプル01.txt
C:\Users\*****\Documents\サンプル\bk\サンプル01.txt
```

### フォルダで絞り込む

```
> ./find.ps1 -recurse -dirMatch "bk"
C:\Users\*****\Documents\サンプル\bk\サンプル01.txt
C:\Users\*****\Documents\サンプル\bk\サンプル02.txt
```

bkフォルダとoldフォルダを対象外にする場合、下記のように実行する。

```
> ./find.ps1 -recurse -dirNotMatch "bk|old"
C:\Users\*****\Documents\サンプル\find.ps1
C:\Users\*****\Documents\サンプル\サンプル01.txt
C:\Users\*****\Documents\サンプル\サンプル02.txt
C:\Users\*****\Documents\サンプル\サンプル03.txt

> ./find.ps1 -recurse -dirNotMatch "bk|old" -fileNameMatch "01"
C:\Users\*****\Documents\サンプル\サンプル01.txt
```

## 説明

### ファイル名一覧を取得する

`Get-ChildItem`コマンドレットでファイル・フォルダの一覧を取得できる。
これにオプションをつけることで、欲しい情報を制限・拡張できる。

```powershell
Get-ChildItem -File -Path "C:\Users\*****\Documents" -Recurse
```

#### `Get-ChildItem`コマンドレット
ファイル・フォルダの一覧を取得する。

#### `-File`オプション
ファイルのみを取得する。（フォルダは取得しない）

#### `-Path`オプション
対象のパスを指定する。
`Get-ChildItem -Path "対象のパス"`のように使用する。

#### `-Recurse`オプション
サブフォルダも取得する。

### 取得したファイル名一覧を正規表現で絞り込む

`Where-Object`コマンドレットを使用することで、
`Get-ChildItem`で取得したオブジェクトにフィルターをかけることができる。

```powershell
Get-ChildItem -File -Path "C:\Users\*****\Documents" -Recurse | Where-Object { $_.Name -match "regex" }
```

#### `|`（パイプライン演算子）
前のコマンドレットの結果を次のコマンドレットに送信する。
これによって`Get-ChildItem`の取得結果に対して`Where-Object`を適用している。

#### `Where-Object`コマンドレット
指定した条件に一致するオブジェクトのみを返却する。
`Where-Object { 条件式 }`のように使用する。

#### `$_`
パイプラインで渡されたオブジェクトを表す自動変数。
上記の場合、`Get-ChildItem`コマンドレットで取得したオブジェクトを表している。

#### `$_.Name`
`$_`オブジェクトの中にある`Name`プロパティを取得する。
上記の場合、`$_`は`Get-ChildItem`の取得結果を表しているため、
`$_.Name`ではファイル名のみを取得している。
`Get-ChildItem`が持っているプロパティは`Get-ChildItem | Get-Member -MemberType property`コマンドレットで確認できる。
今回は下記のプロパティを使用する。

- Name: ファイル名
- DirectoryName: フォルダ
- FullName: ファイルのフルパス（フォルダとファイル名を結合したもの）

#### `-match`演算子
正規表現パターンと一致するか否かを返却する。
`"対象の文字列" -match "正規表現パターン"`のように使用する。
これと類似で`-notmatch`演算子があり、こちらは正規表現パターンと一致しない場合に`true`を返却する。

### コマンドライン引数を設定する

`param`でコマンドライン引数を指定できる。

```powershell:sample.ps1
param (
    [string]$inputStr,
    [switch]$inputBool
)
if (inputBool) {
    Write-Host $inputStr
}
Write-Host $inputStr
```

`[]`で引数の型を指定できる。

#### string型
コマンドラインに入力された文字列を取得する。

#### switch型
コマンドラインにオプションとして設定されたか否かを取得する。

#### 実行例

```
> ./sample.ps1 -inputStr "hello" -inputBool
hello
hello
```

switch型はオプションとして指定しなかった場合は`false`になる。

```
> ./sample.ps1 -inputStr "hello"
hello
```

switch型は`:`を使用して明示的に`false`を指定することもできる。

```
> ./sample.ps1 -inputStr "hello" -inputBool:$false
hello
```

### 指定フォルダ配下の全ファイル一覧を正規表現で絞り込んで取得する
上記のことを踏まえて、表題の機能を実現すると以下のようになる。

```powershell:find.ps1
# 引数
param (
    # ファイル名に一致させたい正規表現パターン
    [string]$fileNameMatch,

    # 指定したいフォルダ
    [string]$path,

    # ファイル名に一致させたくない正規表現パターン
    [string]$fileNameNotMatch,
    
    # フォルダに一致させたい正規表現パターン
    [string]$dirMatch,
    
    # フォルダに一致させたい正規表現パターン
    [string]$dirNotMatch,

    # サブフォルダも含むか否か
    [switch]$recurse
)

# ファイル名一覧を取得する
$fileList = Get-ChildItem -File -Path $path -Recurse:$recurse

# ファイル名にフィルターをかける
if (0 -ne $fileNameMatch.Length) {
    $fileList = $fileList | Where-Object { $_.Name -match $fileNameMatch }
}
if (0 -ne $fileNameNotMatch.Length) {
    $fileList = $fileList | Where-Object { $_.Name -notmatch $fileNameNotMatch }
}

# フォルダにフィルターをかける
if (0 -ne $dirMatch.Length) {
    $fileList = $fileList | Where-Object { $_.DirectoryName -match $dirMatch }
}
if (0 -ne $dirNotMatch.Length) {
    $fileList = $fileList | Where-Object { $_.DirectoryName -notmatch $dirNotMatch }
}

# フルパスを出力する
$fileList | ForEach-Object { $_.FullName }
```