PowerShell ワイルドカードと正規表現とエスケープ

この記事では以下について説明します。

1. **PowerShellが**解釈する**シングルクォート**、**ダブルクォート**、そのエスケープ
1. **コマンド等が**解釈する**ワイルドカード**とそのエスケープ
1. **コマンド等が**解釈する**正規表現**とそのエスケープ

2021/07/26 更新：記事を全体的に書き直しました。

# PowerShellによる文字列の解釈

PowerShellでは、**シングルクォートとダブルクォート**のどちらで囲うかによって処理が違います。

参考：[about_Quoting_Rules - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_quoting_rules)

## シングルクォート

シングルクォートで囲った文字列は、**そのままの文字列**として扱われます。

唯一の例外はシングルクォートで、**シングルクォートはシングルクォート自身でエスケープ**します。


```powershell
'$i is 5' # Output: $i is 5
'don''t' # Output: don't
```

## ダブルクォート

ダブルクォートで囲った文字列は、**変数、式、後述するエスケープシーケンスが置換**されます。変数はドル記号 `$` を前に付けます。式はドル記号と丸括弧 `$( )` 内に記述します。

```powershell
$i = 5
"$i is 5" # Output: 5 is 5
"${i}th" # Output: 5th
"$(2+3) is 5" # Output: 5 is 5
```

### 特殊文字のエスケープ

特殊文字をエスケープするには、**バッククォート `` ` `` を前に付けます**。

ダブルクォートはダブルクォート自身でエスケープすることもできます。

```powershell
$i = 5
"`$i is $i" # Output: $i is 5
"`$(2+3) is $(2+3)" # Output: $(2+3) is 5
"``" # Output: `

"`"" # Output: "
"""" # Output: "
```

### エスケープシーケンス

ダブルクォートで囲った文字列では、以下の**エスケープシーケンス**などが使用できます。

シーケンス | 説明
- | -
\`n | 改行
\`t | 水平タブ
\`u{x} | ユニコード文字（※バージョン5では利用不可）

```powershell
"Column1`t`tColumn2`t`tColumn3" # Output: Column1         Column2         Column3
"line`nbreak"
<# Output:
line
break
#>
```

PowerShellで使用できるすべてのエスケープシーケンスとその使い方については、以下の参考リンクをご参照ください。

参考：[about_Special_Characters - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_special_characters?view=powershell-7.1)

なお、エスケープシーケンスにならない普通の文字の前にバッククォートを付けた場合は、単にバッククォートが消えます。

```powershell
"he`llo`,` world`!" # Output: hello, world!
```

### オブジェクトのプロパティの展開

変数（オブジェクト）の**プロパティ**を展開するには、式 `$( )` の記法が使えます。

```powershell
$obj = [PSCustomObject]@{property="hello"}
"property is $($obj.property)" # Output: property is hello
```

# コマンド等による文字列の解釈

一部のコマンドや演算子などでは、ワイルドカードや正規表現がサポートされています。それらの記法を説明します。

## ワイルドカード

ワイルドカードは、**一部のコマンドおよび `-like` 演算子でサポート**されています。どのコマンドがワイルドカードをサポートしているかは一概に言えませんが、`-Path` や `-Name` の引数であれば基本的にワイルドカードが使えるようです。

ワイルドカードの説明は以下のとおりです。

ワイルドカード | 説明 | 記述例 | 一致例
- | - | - | -
* | 0文字以上の任意の文字 | `a*` | A, ag, Apple
? | 任意の1文字 | `?n` | An, in
[ ] | 範囲内の1文字 | `[a-c]` | a, b, c
[ ] | いずれか1文字 | `[bcl]ook` | book, cook, look

参考：

- [about_Wildcards - PowerShell | Microsoft Docs](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards)
- [Supporting Wildcard Characters in Cmdlet Parameters - PowerShell | Microsoft Docs](https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/supporting-wildcard-characters-in-cmdlet-parameters)

### ワイルドカードのエスケープ

ワイルドカードをエスケープするには、**バッククォート `` ` `` を前に付けます**。このとき、バッククォートがPowerShellに解釈されないように、**シングルクォートで文字列を囲ったほうがよい**です。

```powershell
'[test] hello' -like '[test]*' # Output: False （※右側の [ ] はワイルドカード扱い）
'[test] hello' -like '`[test`]*' # Output: True

'`test hello' -like '`test*' # Output: False （※右の `t は t に変換されて評価される模様）
'`test hello' -like '``test*' # Output: True
```

ダブルクォートで文字列を囲うと、最初にPowerShellで解釈されるので、以下のようにバッククォートをエスケープしておく必要が出てきます。

```powershell
'[test] hello' -like "[test]*" # Output: False
'[test] hello' -like "`[test`]*" # Output: False
'[test] hello' -like "``[test``]*" # Output: True

'`test hello' -like "`test*" # Output: False
'`test hello' -like "``test*" # Output: False
'`test hello' -like "```test*" # Output: False
'`test hello' -like "````test*" # Output: True
'`test hello' -like "`````test*" # Output: False
'`test hello' -like "``````test*" # Output: True
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096584/eb3389bd-6939-5f20-9da6-777e46545a81.png)
※下段の ```` ```test ```` は前から順番に ``` `` ``` `` `t `` でそれぞれ解釈されます。`` `t `` は、`-like` 演算子にとってはエスケープシーケンスでも何でもないため、単に `t` になります。

**`-Path` の引数でワイルドカードが必要ない場合**は、**代わりに `-LiteralPath` が使えます**。

```powershell
Get-ChildItem -Path '[test].txt' # t.txt, e.txt, s.txt がヒット
Get-ChildItem -LiteralPath '[test].txt' # [test].txt がヒット
```

**`-Path` の引数でエスケープする必要がある場合**は、シングルクォートで囲ったとしても**バッククォートが2個 ``` `` ``` 必要**となるようです（※`Get-ChildItem` コマンドで確認。他のコマンドでは不明。おそらく内部的に2回エスケープ処理されているのだと思われます）。

```powershell
# 名前が[test]から始まるファイルを見つける
Get-ChildItem -Path '[test]*' # NG
Get-ChildItem -Path '`[test`]*' # NG
Get-ChildItem -Path '``[test``]*' # OK
```

## 正規表現

正規表現は**以下のコマンド、文、演算子で利用できます**。

- `Select-String`
- `switch` 文
- `-match` `-replace` `-split` 演算子

これらはデフォルトでは**大文字と小文字を区別しません**。区別する場合は、それぞれ以下のようにします。

- `Select-String -CaseSensitive`
- `switch -CaseSensitive`
- `-cmatch` `-creplace` `-csplit`

ここで、正規表現の記法を一部紹介します。他の記法や具体例は、以下の参考リンクをご参照ください。

記号 | 説明
- | -
. | 任意の一文字（ワイルドカード）
* | 直前の文字の0回以上の繰り返し
+ | 直前の文字の1回以上の繰り返し
? | 直前の文字が0回か1回
{n} | 直前の文字のn回の繰り返し
{n,} | 直前の文字のn回以上の繰り返し
{n,m} | 直前の文字のn回からm回の繰り返し
^ | 文字列の先頭、行頭
$ | 文字列の末尾、行末
[a-z] | 範囲内の1文字
[abc] | いずれか1文字
\w | 英数字とアンダースコアの1文字
\d | 数字の1文字
\s | スペース
\t | タブ
\r | キャリッジリターン
\n | 改行

参考：

- [about_Regular_Expressions - PowerShell | Microsoft Docs](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_regular_expressions)
- [Regular Expression Language - Quick Reference | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference)

### 正規表現のエスケープ

**正規表現内でスケープするにはバックスラッシュ `\` を使用します**。例えば、ピリオドを表すには `\.` とします。

### `-replace` 演算子でのキャプチャと置換

**`<入力> -replace <正規表現>,<置換文字列>`** では、**「正規表現」内の `( )` で囲った部分をキャプチャし、「置換文字列」内で順番に `$1` `$2` … と記述して変数のように使うことができます**（PowerShellの変数ではないことに注意）。

```powershell
'Japan Tokyo' -replace '(\w+) (\w+)','$2, $1' # Output: Tokyo, Japan
```

「置換文字列」内でドル記号をエスケープするには `$$` とします（※「正規表現」内では `\$` でエスケープします）。

```powershell
'500 USD' -replace '(\d+) USD','$$$1' # Output: $500
```

ここでも、ダブルクォートで囲うとPowerShellの変数や式などが展開されることに注意します。

```powershell
$i = 5
'<h> hours' -replace '<h>',"$i" # Output: 5 hours
```

「置換文字列」内でタブなどの特殊文字を使うにはダブルクォートで囲って `` `t `` などを使います（※「正規表現」側では `\t` などを使います）。

```powershell
'a,b' -replace ',',"`t" # Output: a       b
```

# あとがき

最近PowerShellの勉強を始めた筆者です。

`Get-Alias ?` を実行したらよく分からない実行結果が出てきたので、それをきっかけにいろいろ調べた結果、この記事ができました。

以下をご覧ください。

```powershell
Get-Alias ? # 「1文字のエイリアス」がヒット
Get-Alias '?' # 「1文字のエイリアス」がヒット
Get-Alias '`?' # 「?」がヒット
Get-Alias "?" # 「1文字のエイリアス」がヒット
Get-Alias "`?" # 「1文字のエイリアス」がヒット
Get-Alias "``?" # 「?」がヒット
```

この記事を最後まで読んでくださった皆さんは、上の実行結果がなぜこのようになるのか、ある程度理解できるようになったことと思います。私もいろいろ調べたおかげで理解できたのでよかったです。

`Get-Alias ?` の事情について詳しく知りたい場合は、こちらの記事（[PowerShell Deep Dive: Understanding Get-Alias, wildcards, escape characters, quoting rules, literal vs. non-literal paths, and the timing of string evaluation – Poshoholic](https://poshoholic.com/2009/02/18/powershell-deep-dive-understanding-get-alias-wildcards-escape-characters-quoting-rules-literal-vs-non-literal-paths-and-the-timing-of-string-evaluation/)）をご参照ください。