ログの整形やファイル名の一括変換など、運用スクリプトでは正規表現を使う場面がよくあります。PowerShellの文字列処理 で基本的な置換を扱いましたが、実際に書いているとエラーは出ないのに結果だけが違うという詰まり方をすることがあります。
この記事では、PowerShell の正規表現でハマりやすい4点をまとめます。いずれも手元の PowerShell 7.4 で実際に動かして確認した挙動です。

1. 置換文字列はシングルクォートで囲む
これが一番よく踏む落とし穴です。-replace で後方参照の $1 を使うとき、囲む引用符の種類で結果が変わります。
# 正しい:シングルクォートで囲む
'John Smith' -replace '(\w+)\s+(\w+)', '$2, $1'
# Smith, John
# 間違い:ダブルクォートで囲む
'John Smith' -replace '(\w+)\s+(\w+)', "$2, $1"
# , と半角スペースだけが残る
原因は、PowerShell がダブルクォート内の $2 $1 を「自分の変数」として先に展開してしまうためです。$1 という変数は定義されていないので空文字になり、正規表現エンジンには ", " という文字列だけが渡ります。
$1 は正規表現側の後方参照であって PowerShell の変数ではありません。エラーにならず、空文字が入った結果が返るだけなので、テストデータが短いと見落とします。
置換文字列に変数を混ぜたい場合は、先に組み立ててから渡します。
$prefix = '社内'
# -f 演算子で文字列を作ってから -replace に渡す
$replacement = '{0}$2, $1' -f $prefix
'John Smith' -replace '(\w+)\s+(\w+)', $replacement
# 社内Smith, John
2. 既定では大文字小文字を区別しない
多くの言語と違い、PowerShell の比較演算子は既定で大文字小文字を区別しません。
# -replace は既定で区別しない(ABC も abc も置換される)
'ABC abc' -replace 'abc', 'X'
# X X
# -creplace なら区別する(c は case-sensitive の c)
'ABC abc' -creplace 'abc', 'X'
# ABC X
同じ規則が -match / -cmatch、-split / -csplit にも当てはまります。Linux の grep や sed の感覚で書くと、意図しない箇所まで置換されます。
環境変数名やホスト名のように大文字小文字を区別すべき対象を扱うときは、-creplace を明示的に使うのが安全です。逆に、利用者が入力した文字列を検索するようなケースでは既定の動作が便利に働きます。
3. パスや記号はエスケープする
正規表現ではバックスラッシュやドットに意味があります。Windows のパスをそのまま渡すと2種類の壊れ方をします。
1つ目はエラーになるケースです。パスの末尾のバックスラッシュが、次の文字を打ち消す記号として解釈されてしまいます。
# 末尾の \ が中途半端なエスケープとして扱われる
'C:\temp\a.log' -replace 'C:\temp\', ''
# InvalidOperation: The regular expression pattern C:\temp\ is not valid.
2つ目が厄介で、エラーにならずに余計なものまで一致するケースです。ドットは「任意の1文字」を意味します。
$names = 'a.log', 'aXlog'
# ドットをそのまま書くと、aXlog にも一致してしまう
$names -match 'a.log'
# a.log
# aXlog
# エスケープすれば意図どおり
$names -match 'a\.log'
# a.log
[regex]::Escape() を通すと、メタ文字をすべて打ち消してくれます。
# 文字列をそのまま「文字として」扱わせる
'C:\temp\a.log' -replace [regex]::Escape('C:\temp\'), ''
# a.log
変数に入ったパスやユーザー入力を正規表現に渡すときは、必ず [regex]::Escape() を通すのが定石です。単純な文字列の置換で済むなら、そもそも正規表現を使わない .Replace() メソッドのほうが安全です。
# .Replace() は正規表現を解釈しないので、パスの置換にはこちらが向く
'C:\temp\a.log'.Replace('C:\temp\', '')
# a.log
4. 配列に対しては全要素へ適用される
-replace を配列に対して使うと、要素ごとに処理されて配列が返ります。ループを書く必要はありません。
# 配列の全要素に対して置換が効く
@('a-1', 'b-2', 'c-3') -replace '-', '='
# a=1
# b=2
# c=3
ファイル名の一括変換などはこれで完結します。
# 拡張子を除いた名前だけを取り出す
(Get-ChildItem *.log).Name -replace '\.log$', ''
一方で -match は挙動が変わります。配列に対して -match を使うと、真偽値ではなく「一致した要素」が返ります。
# 単一の文字列なら True/False が返る
'web01' -match '^web'
# True
# 配列なら一致した要素そのものが返る
@('web01', 'db01', 'web02') -match '^web'
# web01
# web02
フィルタとして使えて便利ですが、if の条件に書くつもりで配列を渡すと意図が変わるので注意が必要です。
5. 名前付きキャプチャで読みやすくする
$1 $2 は数が増えると何番が何なのか分からなくなります。名前を付けられます。
# (?<名前>...) で名前を付け、${名前} で参照する
$log = 'ERROR 2026-08-29 disk full'
$log -replace '(?<level>\w+)\s(?<date>[\d-]+)\s(?<msg>.+)', '${date} [${level}] ${msg}'
# 2026-08-29 [ERROR] disk full
-match と組み合わせると、$matches から名前で取り出せます。
if ('host01.example.local' -match '^(?<host>[^.]+)\.(?<domain>.+)$') {
$matches['host'] # host01
$matches['domain'] # example.local
}
ログをパースして CSV にまとめるようなスクリプトでは、この書き方にしておくと後から読み返したときに意味が分かります。
6. まとめ
PowerShell の正規表現でハマりやすい4点をまとめました。
- 置換文字列はシングルクォート。ダブルクォートだと
$1が空文字になり、エラーも出ない - 既定では大文字小文字を区別しない。区別したいなら
-creplace/-cmatch - パスやユーザー入力は
[regex]::Escape()を通す。単純な置換なら.Replace()を使う - 配列に対して
-replaceは全要素へ、-matchは一致した要素を返す
どれも例外が飛ばず、黙って違う結果を返すのが厄介なところです。短いテストデータで一度動かして確認してから本番のデータに流す、という手順を挟むだけで事故はかなり減らせます。
[[PowerShell]文字列処理の方法(除去/置き換え/特定文字取得)](https://syachiku.net/ps-string-replace/)
PowerShellの学習におすすめの書籍
PowerShell を体系的に学ぶなら、PowerShell Core 以降のクロスプラットフォーム対応をふまえた解説書が分かりやすいです。Windows の自動化にとどまらず、macOS や Linux でも動く現在の PowerShell を、基礎から実務での使い方まで通して押さえられます。
PowerShell実践ガイドブック クロスプラットフォーム対応の次世代シェルを徹底解説



コメント