シェルスクリプトの正規表現の詳細解説（令和最新版）〜 基本正規表現(BRE)と拡張正規表現(ERE)

## はじめに

この記事は UNIX コマンド（POSIX コマンド）で使える正規表現（基本正規表現 BRE と拡張正規表現 ERE）を正しく理解したい人のための記事です。正規表現とはなにか？みたいな基本的な話はしません。他のプログラミング言語で使ってるから正規表現自体は知ってるつもりだけど、シェルスクリプトだといつもの正規表現が使えず苦手だという人のために、シェルスクリプトにおける正規表現を深く理解できるような内容にしています。

基本的に POSIX に準拠した内容を中心に解説しており、どの環境にも対応した内容にしています。さらに Linux (GNU) や BSD や macOS の環境固有の拡張された正規表現、歴史的な UNIX コマンドの話や各コマンド毎の細かい違いなど、実際に使う上で必要な知識も解説しています。

**注意** bash 等のシェルの正規表現についてはこの記事では詳しく扱っていません。シェルの正規表現については以下のリンクを参照してください。

- **[シェルスクリプト (bash, ksh, zsh) で正規表現を使う方法のまとめ](https://qiita.com/ko1nksm/items/3d1fd784611620b1bea5)**
- [Bashで正規表現マッチした文字列を取り出す](https://qiita.com/rockhopper/items/bee538ab4c6aabcd6b0f)
- [シェルスクリプトで正規表現を使い、ファイルから文字列を抽出](https://qiita.com/taro373/items/262164e66d03160d9354)

## シェルスクリプトの正規表現の基礎知識

### 各コマンドに潜む正規表現の細かい差異

一般的にシェルスクリプトはいくつかのコマンドを使って何かを実現しますが、それらのコマンドの正規表現の対応には細かい差異があります。もともと**正規表現には正しい仕様というものがあったわけではありません**。歴史的にテキストエディタの便利な機能として開発され、いろんなコマンドで採用され、徐々に拡張されてきたものです。POSIX で基本正規表現 (BRE) や拡張正規表現 (ERE) として標準化されたのはその後です。一応の標準化はされましたが細かい差異は残っています。

同じコマンドでも Linux (GNU) 版、BSD 版、macOS 版で動作が違うこともあります。このような事情により、シェルスクリプトで正規表現を使って何かをする場合、単に正規表現を覚えるだけではなくコマンドごとの細かい違いを知る必要があります。そこでこの記事では前半でシェルスクリプトにおける正規表現の基本を解説し後半で各コマンド毎の注意点を解説しています。

また UNIX コマンド（POSIX コマンド）はおよそ 30 年前に作られたものであり、多くのプログラミング言語の高度な正規表現と比べると基本的な機能しか備えていません。もし高度な正規表現が必要な場合、（該当部分を）他の言語で実装することをおすすめします。

ちなみに Perl などの更に高度な正規表現は POSIX で標準化された正規表現を無視して拡張したわけではなく、最初から拡張され続けていた正規表現の流れの未来にすぎません。逆に POSIX の正規表現というのが、正規表現の歴史の中の UNIX コマンドの時点で採用されていたその瞬間を切り取ったものにすぎません。すなわち POSIX の正規表現が、正当な正規表現だと考えるのは間違いです。POSIX はソフトウェア業界で「これからは、この標準に従っていきましょう」と決まったルールではなく、単に POSIX という標準化団体が勝手に決めたもので、実装者が「悪くないね。メリットもあるから採用しよう」と考えて採用しているだけです。それじゃだめだろうと考えた実装者は、POSIX に従いつつもだめだと思ったところは POSIX に従わないのです。

### 基本正規表現(BRE)と拡張正規表現(ERE)

基本的な UNIX コマンド（POSIX コマンド）は POSIX が定義した基本正規表現 (BRE) と拡張正規表現 (ERE) のどちらか、または両方を採用しています。コマンド毎に採用している正規表現が違うのは POSIX で正規表現が標準化されるよりも前にコマンドが作られているという歴史的な理由からです。名前から**基本の BRE があってそれを拡張したのが ERE だと思われがちですがそうではありません**。

- **基本正規表現 (BRE)** ・・・ 特殊文字に一貫性がなく分かりづらい
  - 「最初の正規表現」を簡略化して、その後に拡張されたものだから
- **拡張正規表現 (ERE)** ・・・ 特殊文字に一貫性があり分かりやすい
  - 「最初の正規表現」を素直に拡張したものだから

実は BRE と ERE の両方の元となった「最初の正規表現」があります。「最初の正規表現」はまず簡略化され、それが UNIX コマンドに導入され普及しました。簡略化しただけなら良かったのですが、普及するにつれて機能が足りないことがわかり、互換性を保ちながら後付で拡張したため一貫性がなく分かりづらい文法になってしまいました。これが BRE です。その一方で「最初の正規表現」を素直に拡張したものが登場します。こちらは純粋に文法を拡張したため一貫性がある分かりやすい文法になっています。これが ERE です。

このように BRE と ERE は異なる経緯で登場、発展してきたものであり、文法の基本構造に違いがあるため完全な互換性がありません。これら正規表現の歴史についての話は「[正規表現が UNIX コマンドに導入され POSIX で標準化するまでの歴史を紐解いてみた](https://qiita.com/ko1nksm/items/17b8105f793953dc83c7)」で詳しくまとめています。

**補足** POSIX では [SRE（単純正規表現）](https://pubs.opengroup.org/onlinepubs/007908775/xsh/regexp.html)というのも策定していたようですが廃止されたものなので省略します。ただ SRE と BRE は私にはほとんど同じに見え大きな違いが分かりません。

### 拡張正規表現だけを覚えれば十分❗

2 種類の正規表現があるため面倒に思うかもしれませんが、基本的に **ERE（拡張正規表現）だけを覚えれば十分**です。なぜかというとよく使われているコマンドは POSIX 準拠で ERE をサポートしているからです。ただしコマンド毎に細かい違いがあり実際には ERE ± α となっているので注意は必要です（プラスだけではなくマイナスもあります）。

#### 正規表現の基本は拡張正規表現 (ERE)

正規表現はほぼ全てのプログラミング言語で直接またはライブラリとして間接的に採用されておりプログラマであれば必修といえる知識ですが、**ERE はそれらの言語の正規表現のサブセット相当です**（エスケープシーケンスや`(?...)` 構文等がない）。逆に言えば ERE は、どの正規表現でも共通して使える文法で、ERE から高度な正規表現へと混乱することなく知識を拡張していくことができます。名前に反して**正規表現の基本の文法は ERE の方**なのです。

#### 各コマンドが対応している正規表現

|           | BRE | ERE |     |          | BRE | ERE |     |          | BRE | ERE |
| --------- | --- | --- | --- | -------- | --- | --- | --- | -------- | --- | ----|
| **[[ ]]** |     | yes |     | **ex**   | yes |     |     | **lex**  |     | yes |
| **awk**   |     | yes |     | **expr** | yes |     |     | **more** | yes |     |
| **ed**    | yes |     |     | **grep** | yes | yes |     | **sed**  | yes | yes |
| **egrep** |     | yes |     | **less** |     | yes |     | **vim**  | yes | yes |

上記は正規表現を使う UNIX (POSIX) コマンドですが、シェルスクリプトで使うなら ERE だけを覚えれば十分だとわかると思います。例外は `expr` ぐらいですが、あまり使わないと思います。人によっては対話型シェルから `less` や `vim` を使っていると思いますが、これらも全て ERE をサポートしています。ちなみに `egrep` は POSIX 準拠ではなくなったコマンドです。

#### sed -E (ERE) は POSIX Issue 8 で標準化される（予定）

もともと `sed` コマンドは BRE にしか対応しておらず、`sed -E` または `sed -r` は BSD または GNU の拡張機能なので厳密に POSIX に準拠したい場合は使用してはならないと言われていたこともありました。しかしそれはもう昔の話です。`sed -E` は POSIX で標準化されます。もはや最新のよく使われている環境で使えないかもと気にする必要はありません。

詳細は「[sedで拡張正規表現 (ERE) を使う時は -r ではなく -E オプション（POSIX Issue 8 準拠）を使いましょう](https://qiita.com/ko1nksm/items/6c0d8e6226a29ea41f07)」を参照してください。

#### 基本正規表現 (BRE) が必要になる場合はほとんどない

BRE を使わなければいけないのは次のような場合だけです。

- `expr` で正規表現を使いたい（純粋な POSIX シェルでは `[[ ]]` が使えない）
- `sed` の `-E` オプションを使うのはまだ時期尚早だと思っている
  - POSIX で標準化されるのは 2022 年後期の Issue 8
  - 商用 Unix（macOS を除く）ではまだサポートされていない
  - ERE では後方参照が使えない ← 事実だが[置換文字列での参照は後方参照ではないので使える](#後方参照と置換文字列での参照の違い)
- `less` や `vim` ではなく POSIX 準拠の `more` や最小機能の `vi` で頑張っている

このような場合に当てはまる人は BRE も覚える必要がありますが、ほとんどの人は ERE だけで十分だと思います。とは言え BRE と ERE の違いはそんなに大きくはありません。違いを把握すれば簡単に覚えることが出来るでしょう。

### BRE と ERE の文法の違い

BRE と ERE の文法の基本的な違いは**バックスラッシュをつけたときの意味が一部逆になっているだけ**です。BRE と ERE は正確には POSIX が定義しているものを指しますが Linux (GNU) と macOS はそれらを拡張しているため異なる部分があります。なお比較表のタイトルの **A** - **H** の意味は次項の「特殊文字」を参照してください。

#### POSIX 版 BRE & ERE

|         |    A    |   B   |   C   |      D      |    E    |    F    |          G          |  H  |  後方参照    |
| ------- | :-----: | :---: | :---: | :---------: | :-----: | :-----: | :-----------------: | :-: | :-----------: |
| **BRE** | `^` `$` |  `.`  | `[ ]` |     `*`     | `\{ \}` | `\( \)` |                     | `\` | `\1` ... `\9` |
| **ERE** | `^` `$` |  `.`  | `[ ]` | `*` `+` `?` |  `{ }`  |  `( )`  | <code>&#124;</code> | `\` |               |

BRE では使えない `+ ?` と `|` が ERE では使えるようになっています。実の所 `+ ?` は `{ }` を使って代替可能なので BRE で使えないのは `|` のみです。逆に後方参照が使えるのは BRE だけです。（**注意** 後方参照とは `sed` などの置換文字列で使う参照のことではありません。置換文字列での参照は ERE でも対応しています。）

ERE ではここにあげた特殊文字を無効にする時にエスケープ文字（バックスラッシュ）でエスケープします。BRE でも大部分は同じなのですが `{ }` と `( )` に限っては特殊な意味をもたせるためにエスケープ文字を使うため一貫性がありません。

#### Linux (GNU) 版 BRE & ERE

|         |    A    |   B   |   C   |       D       |    E    |    F    |           G           |  H  |   後方参照    |
| ------- | :-----: | :---: | :---: | :-----------: | :-----: | :-----: | :-------------------: | :-: | :-----------: |
| **BRE** | `^` `$` |  `.`  | `[ ]` | `*` `\+` `\?` | `\{ \}` | `\( \)` | <code>\\&#124;</code> | `\` | `\1` ... `\9` |
| **ERE** | `^` `$` |  `.`  | `[ ]` |  `*` `+` `?`  |  `{ }`  |  `( )`  |  <code>&#124;</code>  | `\` | `\1` ... `\9` |

Linux (GNU) 版では BRE、ERE の両方で欠けている機能を補っているため、BRE と ERE に機能的な違いはありません。違う所は `{ }` と `( )` と `+ ? |` のエスケープの意味が反対になっている所だけです。BRE はエスケープ文字の使い方に一貫性がないということがはっきりわかります。例えば `*` `+` `?` は繰り返しの数が違うだけの似たような機能なのに `*` はエスケープ文字が必要なく `+ ?` はエスケープ文字が必要になっています。こんな状態なので BRE を（最初に）覚えようとすると混乱してしまうことでしょう。もし必要なら BRE は ERE を覚えた後に差分として覚えることをお勧めします。

補足ですが Linux (GNU) 版の BRE & ERE は「[C 言語スタイルのエスケープシーケンス](#c-言語スタイル)」を使用することが出来ます。

#### macOS 版 BRE & ERE

macOS では基本的に BSD 版のコマンドが使われているのですが、拡張された正規表現である enhanced BRE と enhanced ERE が使える場合があります。

|         |    A    |   B   |   C   |       D       |    E    |    F    |           G           |  H  |   後方参照    |
| ------- | :-----: | :---: | :---: | :-----------: | :-----: | :-----: | :-------------------: | :-: | :-----------: |
| **BRE** | `^` `$` |  `.`  | `[ ]` | `*` `\+` `\?` | `\{ \}` | `\( \)` | <code>\\&#124;</code> | `\` | `\1` ... `\9` |
| **ERE** | `^` `$` |  `.`  | `[ ]` |  `*` `+` `?`  |  `{ }`  |  `( )`  |  <code>&#124;</code>  | `\` | `\1` ... `\9` |

比較表は特殊文字だけを書いているため Linux (GNU) 版 と同じですが、文法は更に拡張されており、**最短一致 `.*?`、キャプチャなしの部分式 `(?:...)`、インラインオプション `(?i)`、インラインリテラルモード `\Q...\E`、インラインコメント `(?#...)` 等の高度な正規表現に対応しています**。詳細は `man re_format` の ENHANCED FEATURES を参照してください。ウェブでは [re_format(7) [osx man page]](https://www.unix.com/man-page/osx/7/re_format/) で見ることが出来ます。

ただし全てのコマンドで ENHANCED FEATURES が有効になっているわけではないようで、macOS Big Sur 11.6 では `grep` では使えましたが `sed` では使えませんでした。

```sh
$ echo "foobarbaz" | grep 'foo\(bar\|baz\)\?'
foobarbaz

$ echo "foobarbaz" | sed -n '/foo\(bar\|baz\)\?/p'
```

補足ですが macOS 版の enhanced BRE & enhanced ERE は「[C 言語スタイルのエスケープシーケンス](#c-言語スタイル)」を使用することが出来ます。

#### BRE は `{ }` `( )` と `+` `*` `|` をエスケープ

BRE を使う時の注意点は `{ }` `( )` と `+` `*` `|` をエスケープするということです。ただし POSIX 版の BRE では `{ }` `( )` にしか対応していません。Linux (GNU) 版 や macOS 版の拡張された BRE を使うぐらいなら ERE を使えばいいと思いますが、もし拡張された BRE を使う必要がある場合は `+` `?` `|` もエスケープして使うことが出来ます。

## UNIX (POSIX) コマンドの正規表現

### 特殊文字（メタ文字）`^$ .[] *+? {} () | \`

特殊文字 (Special Characters) は特殊な意味を持つ文字のことです。メタ文字とも言われますが POSIX ではブラケット表現の中でのみメタ文字という用語を使っているようなので、この記事では特殊文字と呼ぶことにします。なお"文字列"ではなく"文字"といった場合 1 文字を意味します。BRE の場合 `+ ? { } ( ) |` はエスケープ文字（バックスラッシュ `/`）で始まるエスケープシーケンスとして実装されており特殊"文字"に含めないのが正しいのですが説明の都合上（BRE と ERE を分けて書きたくないので）この記事では特殊文字の記述方法の違いとして扱います。

|     |      特殊文字       | 意味                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| --- | :-----------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A   |       `^` `$`       | **アンカー (anchor)** 文字列の行頭（`^`） または 行末（`$`） にマッチします。                                                                                                                                                                                                                                                                                                                                                            |
| B   |         `.`         | **ピリオド** 任意の一文字にマッチします。                                                                                                                                                                                                                                                                                                                                                                                                |
| C   |        `[ ]`        | **ブラケット表現 (bracket expression)** カッコ内のいずれかの文字にマッチします。詳細は「ブラケット表現」を参照。                                                                                                                                                                                                                                                                                                                         |
| D   |     `*` `+` `?`      | **量指定子 (quantifier operator)** 前の文字またはグループの「0 個以上の繰り返し `*`」「1 個以上の繰り返し `+`」「0 個または 1 個 `?`」にマッチします。下記の量指定子の省略型とみなすことができます。なお **BRE、ERE には最長一致しかありません**（macOS 版の enhanced ERE を除く）。                                                                                                                                                              |
| E   |        `{ }`        | 量指定子の**インターバル表現 (interval expression)** `{m,n}` 前の文字またはグループの m 個以上 n 個以下の繰り返しにマッチします。「m 個 `{m}`」「m 個以上 `{m,}`」「n 個以下 `{,n}`（GNU 版のみ）」という指定の仕方もできます。                                                                                                                                                                                                          |
| F   |        `( )`        | **BRE: 部分式 (sub expression)** 後方参照で参照する部分式 / **ERE: グループ (grouping)** 選択子のためのグループ。POSIX の定義では BRE と ERE で機能が異なり名前も違います。POSIX 以外の定義では 2 つの機能をあわせ持っています。**なお肯定先読み等の `(?...)` のようなものはありません**。置換文字列で参照する範囲の指定にも使います。                                                                                                   |
| G   | <code>&#124;</code> | **選択子 (alternation operator)** <code>(exp1&#124;exp2)</code> という指定で exp1 または exp2 という意味になります。トップレベルの `()` は省略可能です。POSIX 準拠では **BRE では使えません。**                                                                                                                                                                                                                                                   |
| H   |         `\`         | 特殊文字の特殊な意味を無効にするための**エスケープ文字**です。特殊文字以外の前にバックスラッシュをおいた場合の挙動は（後方参照を除いて）POSIX では未定義です。GNU などの拡張ではエスケープシーケンスとして特別な意味を持ちます。<br><br>**後方参照 (back reference)** `\1` ... `\9` で部分式にマッチした文字列にマッチさせることができます。後方参照（置換文字列での参照のことではありません）は POSIX 準拠では **BRE でのみ使えます。** |

### 部分式・グループ `( )` と後方参照 `\1` ... `\9`

#### 部分式とグループの違い

BRE の部分式と ERE のグループは厳密には異なる機能です。例えば選択子を使って「どちらかにマッチ」を書いたらキャプチャしたときの番号がずれて困ったことはないでしょうか？それは異なる機能を合わせ持っているからこのような問題が起きるわけです。他のプログラミング言語ではキャプチャなしのグループがありますが、シェルスクリプトの場合、キャプチャなしグループが使えるのは macOS の enhanced ERE だけです。

#### 空の正規表現`()`や`(exp1|)`は使用しない

部分式・グループでの空の正規表現 `()` や `(exp1|)` の結果は [POSIX では未定義](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/basedefs/V1_chap09.html#tag_09_04_03)です。実際にエラーになる場合があるので注意してください。GNU の `grep` のドキュメントには「移植性が必要な場合は使用してはいけません」と書かれています（[参考](https://www.gnu.org/software/grep/manual/html_node/Basic-vs-Extended.html)）。

>  Outside a bracket expression, a \<left-parenthesis\> immediately followed by a \<right-parenthesis\> produces undefined results. 

訳 ブラケット表現の外で `(` の直後に `)` を置くと未定義の結果になる

> A \<vertical-line\> appearing first or last in an ERE, or immediately following a \<vertical-line\> or a \<left-parenthesis\>, or immediately preceding a \<right-parenthesis\>, produces undefined results.

訳 ERE の最初または最後に `|` がある場合、または `(|` や `|)` は未定義の結果になる

```sh
# (exp1|) は macOS (BSD) 版 grep でエラーになる
$ echo foo10bar | grep -E 'foo(10|20|30|)bar'
grep: empty (sub)expression

# 代わりに ? を使えば良い
$ printf '%s\n' foobar foo10bar | grep -E 'foo(10|20|30)?bar'
foobar
foo10bar
```

#### 置換文字列からの参照 ≠ 後方参照

勘違いされやすいですがキャプチャした部分の「置換文字列からの参照」は厳密には「後方参照」ではありません。本来の後方参照とは「`()` でマッチした文字列を**正規表現の中で**参照する正規表現の機能」のことです。つまり `sed 's/正規表現/置換文字列/g'` の「正規表現」の部分で使うものが後方参照です。どちらも同じ `\1` で `()` でキャプチャした文字列を参照しますが置換文字列は明らかに正規表現ではありませんよね？

POSIX で後方参照が規定されているのは BRE だけなので、ERE では（GNU 拡張を除き）使えませんが、これは置換文字列から参照できないという意味ではありません。別の言い方をすると、`sed -E` で正規表現に ERE を使った場合に、後方参照の `\1` が使えなくなっても、置換文字列の `\1` は使えるということです。（下記のコード参照）

```sh
# （下記の検証は macOS (BSD) 版 sed にて）

# BRE は後方参照が使える
$ echo "abcabc" | sed 's/\(abc\)\1/<\1>/g'
<abc>

# ERE では後方参照は使えない （GNU 版は拡張により使える）
$ echo "abcabc" | sed -E 's/(abc)\1/<\1>/g'
abcabc

# ただし、置換文字列での参照はできる
$ echo "abc" | sed -E 's/(abc)/<\1>/g'
<abc>
```

### Unicode 対応とロケール

文字列処理全般に当てはまる話ですが、**正規表現はロケールに依存します**。何を一文字とするかはロケールによって異なります。例えば日本語環境（文字コードは UTF-8）のデフォルトのロケール (`ja_JP.UTF-8`) であれば日本語の文字を一文字として扱いますが、 `C` (`POSIX`) ロケールの場合、1 バイトを一文字として扱ってしまいます。（ちなみにロケールの `C` と `POSIX` は全く同じ意味です。）

```sh
$ echo "あいうえお" | sed -E 's/^(.)/<\1>/g'
<あ>いうえお

# 「あ」の UTF-8 表現 `e3 81 82` の 1 バイト目だけを置換するので壊れる
$ echo "あいうえお" | LC_ALL=C sed -E 's/^(.)/<\1>/g'
<�>��いうえお
```

現在一般に使われている文字コードは UTF-8 です。`en_US.UTF-8` や `ja_JP.UTF-8` といった同じ UTF-8 を使うロケールであれば、一文字の解釈は同じですが文字の並び順や種類が異なります。次項のブラケット表現は文字の並び順や種類がとても重要になってきます。

ただし**各 OS（環境）のコマンドによって Unicode に対応しているかどうかは、まちまちなので注意が必要**です。ロケールを正しく設定していたとしても、Unicode として解釈される場合とされない場合があります。

### ブラケット表現 `[...]` `[^...]`

正規表現（BRE と ERE の両方）で使うことができる `[ ]` の中の文字リストの任意の一文字にマッチさせるためのものです。例えば `[abc]` の場合 `a` `b` `c` のいずれかにマッチします。POSIX の正規表現は「ブラケット表現」という名前ですが、その他の正規表現の話では `[ ]` 自体を「文字クラス」と呼んでいるので注意が必要です。

| メタ文字 | 意味                                                                                                                                                                                              |
| :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   `^`    | `[^...]` と書くことで否定リスト（`...` 以外の文字）の意味になります。`^` 自体をリストに入れたい場合は、文字リストの 2 文字目以降に記述します。（例 `[abc^]`）                                     |
|   `-`    | **範囲** `[c-c]` と ２ つの文字を `-` で繋ぐことにより、**現在のロケールにおいて**その文字の範囲にマッチします。`^` 自体をリストに入れたい場合は、`[ ]` の先頭か末尾に記述します。（例 `[abc-]`） |
|   `]`    | `[` を閉じます。`]` 自体をリストに入れたい場合は、`[ ]` の先頭に記述します。（例 `[]abc]`）                                                                                                       |

#### ブラケット表現は正しく動かない？

もちろんそんな事はありません。ブラケット表現は正しく動きます。記事の途中でも詳しく書いていますが、ブラケット表現が正しく動かないと勘違いしてる原因は以下の２つです。

- POSIX に準拠してない古いコマンドを使っている
  - 解決方法 [シェルスクリプト実行環境をPOSIX準拠モードに変更して互換性を上げる方法](https://qiita.com/ko1nksm/items/6ebdcba7050da2f3c585)
- ロケールが正しく設定されてない
  - 解決方法 [シェルスクリプトで正規表現の照合シンボル[.string.]と等価クラス[=char=]と文字クラス[:classname:]を正しく使う方法](https://qiita.com/ko1nksm/items/b425c1978102e5a1b109)

#### 範囲 `a-z`

「範囲」はロケールに依存することに注意してください。例えば `[a-z]` は現在のロケールが `C` (`POSIX`) の時には `è` にマッチしませんが `en_US.UTF-8` の時には**おそらくマッチしますがマッチしないかもしれません**。なぜ断定できないのかというと、**ロケールが `POSIX` 以外の場合の範囲の動作は未定義 (unspecified)** だからです。ロケールが POSIX の場合は範囲は文字の並び順（照合順序）に従って判定されます。しかしロケールが POSIX 以外の場合、単純に照合順序で判定するとは限りません。

なぜこのような仕様になっているかというと照合順序で判定するのは問題が有るからです、例えば POSIX ロケールではアルファベットは `ABCabc` という順番で並びますが `en_US.UTF-8` の場合は `aAbBcC` という順番です。この時 `[A-B]` を単純に解釈してしまうと `a` は含まれないのに `b` は含まれるという結果になってしまいます。[エストニアのアルファベット](https://en.wikipedia.org/wiki/Estonian_orthography)はアルファベットの順番が異なり `z` が最後のアルファベット文字ではありません。また `è` は `e` の前にあるのか後にあるのか、つまり `[a-e]` と `[e-g]` のどちらにマッチするのでしょうか？これらは単純な方法で解決できる問題ではありません。

POSIX では当初 (POSIX.2-1992) は照合順序で範囲を判定するという仕様でした。のちに上記のような問題があると明らかになり POSIX ロケール以外では「未定義」と仕様が変更になりました。この仕様変更により各実装が考える自然と思われるアルゴリズムで範囲を判定することが POSIX で許可されました。そのため POSIX ロケール以外でも問題は無くなったような動作をするはずですが、どのように「範囲」が判定されるかは実装依存であるため、ロケールに注意する必要があることには変わりありません。これは必ずしも POSIX ロケールを使わなければいけないという意味ではありません。例えば検索ツールのようなものならロケールと実装依存で問題ない場合もあります。ロケールをどのように扱うかは要件次第であり、このような問題を踏まえて適切な方法を選択する必要があります。

この話についての詳細は以下のページで詳しく解説されています。

- [GNU - Regexp Ranges and Locales: A Long Sad Story](https://www.gnu.org/software/gawk/manual/html_node/Ranges-and-Locales.html)
- [POSIX - Rationale: Base Definitions - Regular Expressions](https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap09.html)

#### ブラケットシンボル

ブラケット表現の中では以下の特殊な表記で特殊な文字を表現することができます。これらを POSIX では「Collation-related bracket symbols」と呼んでいるようですが、長いのでここではブラケットシンボルと呼ぶことにします。（ただ厳密に言えば BSD 拡張の `[:<:]` と `[:>:]` は Collation じゃないと思うし、なんなら `[: :]` 自体が違う気もしますが）

**もしこれらブラケットシンボルが全く使えなかったり正しく動いていないと思う場合、それは POSIX に準拠したコマンドではなく互換性のために用意されている歴史的なコマンドを使っているだけです**。今どき POSIX に準拠してないコマンドを使う理由はないので「[シェルスクリプト実行環境をPOSIX準拠モードに変更して互換性を上げる方法](https://qiita.com/ko1nksm/items/6ebdcba7050da2f3c585)」を参考にして POSIX に準拠したコマンドを使用してください。

| ブラケットシンボル | POSIX |  BSD  |  GNU  | macOS | 意味                      |
| :----------------: | :---: | :---: | :---: | :---: | ------------------------- |
|      `[:<:]`       |       |  yes  |       |  yes  | 単語の先頭にマッチ        |
|      `[:>:]`       |       |  yes  |       |  yes  | 単語の末尾にマッチ        |
|    `[:`name`:]`    |  yes  |  yes  |  yes  |  yes  | **文字クラス** 下記参照   |
|   `[.`string`.]`   |  yes  |  yes  |  yes  |  yes  | **照合シンボル** 下記参照 |
|    `[=`char`=]`    |  yes  |  yes  |  yes  |  yes  | **等価クラス** 下記参照   |
 
ブラケットシンボルは `[[:alpha:]]` のように書いて使うことが多いですが、ブラケット文字 `[` を 2 重にして指定するものと勘違いしないようにしてください。これは `[ ]` の中に `[:alpha:]` が含まれているだけなので `[0[:alpha:]9]` や `[[:lower:][:digit:]]` のように書くこともできます。またブラケット `[]` を使わずに直接ブラケットシンボルを書いてしまうのも間違いです。

```sh
# これが正しい
$ echo abc | grep '[[:alpha:]]'
abc

# これは間違い
$ echo abc | grep '[:alpha:]'
abc
```

これは一見ブラケット一つで動いてるように見えますが、単に `[]` に囲まれた  `:` `a` `l` `p` `h` `a` `:` という文字のいずれかにマッチしているだけです。勘違いしやすいので注意してください。

**文字クラス、照合シンボル、等価クラスもロケールに依存する**ことに注意してください。ロケールが正しくないと意図したとおりに動きません。これを知らずにブラケットシンボルが正しく動かないと勘違いしている人が多いです。（参考 「[シェルスクリプトで正規表現の照合シンボル[.string.]と等価クラス[=char=]と文字クラス[:classname:]を正しく使う方法](https://qiita.com/ko1nksm/items/b425c1978102e5a1b109)」）

##### 文字クラス `[:name:]`

「文字クラス」は「POSIX 文字クラス」呼ばれることがあります。これは「ブラケット表現」を「文字クラス」と呼んでいる場合に紛らわしいからです。文字クラスを使うと指定した文字クラス名に定義されている文字にマッチさせることが出来ます。

マッチする文字は現在のロケールに依存します。例えば現在のロケール（環境変数 `LC_CTYPE`）が `C` の場合には `[:alpha:]` は `è` にマッチしませんが、`en_US.UTF-8` の場合にはマッチします。ちなみに**日本語も `[:alpha:]` にマッチします**。同様に **`[[:upper:]]` は全角アルファベットにもマッチ**します。（`en_US.UTF-8` でも `ja_JP.UTF-8` でもマッチします）。

POSIX では以下の文字クラスが定義されています。詳しい意味は検索すればすぐに分かることなので省略します。

`[:alnum:]` `[:cntrl:]` `[:lower:]` `[:space:]` `[:alpha:]` `[:digit:]`
`[:print:]` `[:upper:]` `[:blank:]` `[:graph:]` `[:punct:]` `[:xdigit:]`

また `[:`name`:]` という書式でロケール依存の name が使用できるようなのですが、これに対応している実装はまだ見たことがありません。（本気で探したわけではありませんが）

ちなみに `[:blank:]` が GNU 拡張と言われることがあるようですが、おそらく SRE（単純正規表現） の話と混同されているのだと思います。SRE は BRE とほぼ同じ仕様に見えますが `[:blank:]` は含まれていません。

##### 照合シンボル `[.string.]`

現在のロケールにおいて、指定した照合文字を 1 文字とみなしてマッチします。例えば現在のロケール（環境変数 `LC_COLLATE`）が `cy_GB.utf8` の場合に `[.ch.]` は `ch` を 1 文字とみなしてマッチします。

`[.ch.]` の `ch` とマッチする文字の `ch` が同じなので勘違いしやすいですが、この照合文字はロケールの定義に書かれている名前で、自由な文字を指定できるものではないので注意してください。つまり `[.abcde.]` のように書いても `abcde` にマッチすることはなく、無効な照合文字としてエラーになります。

##### 等価クラス `[=char=]`

現在のロケールにおいて、指定した文字と等価な文字と定義されている文字にマッチします。例えば現在のロケール（環境変数 `LC_COLLATE`）が `en_US.UTF-8` の場合に `[=e=]` は `e` `è` `é` `ê` `ë` にマッチします。

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

冗長なブラケット表現の略記法です。多くのプログラミング言語の正規表現で必ずと言っていいほど使っているものですが、**残念ながら POSIX では定義されておらず POSIX に準拠したシェルスクリプトを書く場合には基本的に使えません。**またブラケット表現と同様に**ロケールに依存する**ので注意してください。

「エスケープシーケンス」の代わりに「メタ文字列 (meta string)」（"文字"ではなく"文字列"）という用語を（メタ文字列と区別せずに使っていると明記して）使っている有名な例（[詳説 正規表現](https://www.oreilly.co.jp/books/9784873113593/)）も見かけますが、どちらかと言えば一般的な用語ではないと思います。なお原著の [Mastering Regular Expressions](https://www.amazon.co.jp/dp/0596528124) ではメタ文字列は meta sequence となっています。

BRE の場合は `{}` `()` `+` `?` `|` をエスケープシーケンスで表現するためここに含めるのが正確だとは思いますが、この記事では説明の都合上これらを特殊文字の例外的な表記として扱う事としここには含めていません。同様の理由で後方参照も省略します。

**注意** 以下のエスケープシーケンスは POSIX では標準化されていません。
<sup>（2023-10-17 追記 以下の一覧は調査不足のようで間違っている可能性があります）</sup>

|      | 旧 UNIX | GNU  | BSD・macOS | マッチする文字   | 同等のブラケット表現 |
| ---- | :-----: | :---: | :---: | ---------------- | -------------------- |
| `\<` |   yes   | yes  |  yes  | 単語の先頭       | `[[:<:]]`<br>（BSD・macOS のみ） |
| `\>` |   yes   | yes  |  yes  | 単語の末尾       | `[[:>:]]`<br>（BSD・macOS のみ） |
| `\b` |         | yes  |  yes  | 単語の区切り     |                      |
| `\B` |         | yes  |  yes  | 単語の区切り以外 |                      |
| `\d` |         |      |  yes  | 数字             | `[[:digit:]]`        |
| `\D` |         |      |  yes  | 数字以外         | `[^[:digit:]]`       |
| `\s` |         | yes  |  yes  | 空白文字         | `[[:space:]]`        |
| `\S` |         | yes  |  yes  | 空白以外の文字   | `[^[:space:]]`       |
| `\w` |         | yes  |  yes  | 単語の文字       | `[_[:alnum:]]`       |
| `\W` |         | yes  |  yes  | 単語の文字以外   | `[^_[:alnum:]]`      |

ここでいう「旧 UNIX」とは POSIX に準拠してない歴史的な UNIX で使われていた正規表現ことです。具体的には Solaris で確認しています。`\<` と `\>` は GNU 拡張と説明されていることが多いですが、実際には歴史的な UNIX 時代から存在していたものです。POSIX で標準化されなかったのは BSD で実装されていなかった、もしくは `[:<:]` `[:>:]` として実装されたために、移植性がなかったからでしょう。

GNU 拡張は、BRE と ERE の両方で使用可能です。`\s` と `\S` は 2000 年過ぎぐらいに導入された比較的新しいエスケープシーケンスです。すべてのコマンドに一気に導入されたわけではなくコマンド毎に徐々に対応していったようです。

macOS 拡張も BRE と ERE の両方で使用可能ですが、ENHANCED FEATURES が有効になっているコマンドのみです。`grep` は対応していそうですが、`sed` は対応してなさそうです。

#### C 言語スタイル

これらは（POSIX 準拠の） awk や GNU 版 や macOS 版のコマンドで使える可能性がある C 言語スタイルのエスケープシーケンスです。他のプログラミング言語の正規表現でも使えることが多いですが、元々は正規表現のエスケープシーケンスとは別のものです。

C 言語スタイルのエスケープシーケンスを避けて正規表現のエスケープシーケンスが定義された・・・と思いきや実は `\b` （単語の区切り）がかぶっています。どちらが採用されるかはコマンドや正規表現次第ですが、awk (gawk) ではバックスペースとして扱われ、他の言語の正規表現だと `[\b]` のように文字クラス（ブラケット表現）の中でのみバックスペースと解釈されるようになっていたりします。

**注意** 以下は C 言語スタイルのエスケープシーケンスのリストであり、コマンドで使えるエスケープシーケンスのリストではありません。実際に使えるエスケープシーケンスはコマンドにって異なります。awk に関しては POSIX で規定されているものを明記しています。

| エスケープシーケンス | 文字コード | 意味                     | awk |
| :------------------: | :--------: | ------------------------ | --- |
|         `\a`         |    0x07    | ベル (BEL)               | yes |
|         `\b`         |    0x08    | バックスペース (BS)      | yes |
|         `\f`         |    0x0C    | 改ページ (FF)            | yes |
|         `\n`         |    0x0A    | 改行  (LF)               | yes |
|         `\r`         |    0x0D    | キャリッジリターン (CR)  | yes |
|         `\t`         |    0x09    | 水平タブ (HT)            | yes |
|         `\v`         |    0x0B    | 垂直タブ (VT)            | yes |
|         `\'`         |    0x5C    | シングルクォート         |     |
|         `\"`         |    0x27    | ダブルクォート           | yes |
|         `\\`         |    0x22    | バックスラッシュ         | yes |
|         `\?`         |    0x3F    | 疑問符                   |     |
|        `\ooo`        |            | 8 進表記の ASCII 文字    | yes |
|        `\xhh`        |            | 16 進表記の ASCII 文字   |     |
|       `\uhhhh`       |            | 16 進表記の Unicode 文字 |     |
|     `\Uhhhhhhhh`     |            | 16 進表記の Unicode 文字 |     |

## 各コマンドと正規表現の注意点

よく使われていると思われるコマンドのみに絞って解説します。**特に明記していない場合 POSIX で標準化されている範囲を解説しています**。いずれのコマンドも BRE もしくは ERE を採用していることになっていますが、コマンドによってわずかに仕様が異なります。その理由はこれらのコマンドは元々 POSIX 以前に作成されたコマンドであり、バラバラに実装・拡張されてきたからです。POSIX という標準規格の登場で基本的に共通化されましたが全てが同じ仕様になったわけではありません。

補足ですが POSIX では、これらのコマンドでパス名を処理する時はロケールによっては無効な文字シーケンスが生成される可能性があるということで [`LC_ALL=C` にすることを推奨](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html#tag_20_116_16)しています。（個人的にはもう全部 UTF-8 系になったし UTF-8 以外は非対応とし、そんな変な名前をつける方が悪いということで気にしていません）

### `[[ ]]` コマンド（シェルビルトイン）

これは POSIX コマンドではなくシェルの文法です。bash、ksh、zsh で実装されています。これらのシェルで共通で対応しているのは ERE です。また zsh では Perl の正規表現にも対応しています。使い方は `[[ "文字列" =~ 正規表現 ]]` です。正規表現の部分にはダブルクォートを書いてはいけません。シェルの正規表現についての詳細は「[シェルスクリプト (bash, ksh, zsh) で正規表現を使う方法のまとめ](https://qiita.com/ko1nksm/items/3d1fd784611620b1bea5)」で詳しくまとめています。

ちなみに私は POSIX 準拠かつパフォーマンスのために外部コマンドをなるべく使用しないようにしてるので、`if`、`case`、パラメータ展開を組み合わせて文字列判定を行う関数（例えば `is_number` 関数のようなもの）を作ってます。正規表現の方が楽ですが関数作るのは面倒くさいという程度の話です。使いまわしできますしね。

### `awk` コマンド

#### 固有の正規表現の拡張機能

awk は ERE を採用しています。**POSIX の標準規格として** awk では正規表現の中（ブラケット表現の外と中の両方）で「[C 言語スタイルのエスケープシーケンス](#c-言語スタイル)」が使えるように拡張されています。使える C 言語スタイルのエスケープシーケンスは `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v`, `\"`, `\\`, `\ooo` （C 言語スタイルのエスケープシーケンスのすべてではない）で、加えて正規表現を囲うための `\/` です。これ以外の文字に `\` をつけた場合の意味は未定義となっています。

#### 空の正規表現の意味

おそらく全ての awk の実装で、空の正規表現指定すると以下のように文字の境界にマッチしているようなのですが、これが POSIX で規定されているかどうかは不明です。（今の所書いてある場所を見つけていない。）

```sh
$ echo "abc" | gawk  '{ gsub(//, "@"); print}'
@a@b@c@
```

#### 文字列リテラルの正規表現の解釈

awk は正規表現リテラルがサポートされていますが文字列で正規表現を指定することもできます。文字列で指定した場合、awk 言語としてのエスケープシーケンスの解釈と正規表現のエスケープシーケンスの解釈の、二回のエスケープシーケンスの解釈が行われるので注意してください。下記の例は正規表現の `\s` （空白文字 = スペースやタブ等）を置換する例です。

```sh
# これは正規表現リテラルで、正規表現のエスケープシーケンスの解釈だけが行われる
$ printf 'a \t\nb' | awk  '{ gsub(/\s/, "@"); print}'
a@@
b

# これは文字列リテラルで、awk 言語と正規表現のエスケープシーケンスの二回の
# 解釈が行われるため二重にエスケープが必要
$ printf 'a \t\nb' | awk  '{ gsub("\\s", "@"); print}' 
a@@
b

# gawk の場合、警告してくれるのでありがたい
$ printf 'a \t\nb' | gawk  '{ gsub("\s", "@"); print}'
awk: コマンドライン:1: 警告: エスケープシーケンス `\s' は `s' と同等に扱われます
a
b
```

この文字列リテラルの二回のエスケープシーケンスの解釈は、エスケープシーケンスによっては一回で正しく動く例が存在します。

```sh
$ printf 'a\tb\n' | awk  '{ gsub(/\t/, "@"); print}'
a@b

# 先程の例とは違い警告されず、正しく動く
$ printf 'a\tb\n' | gawk  '{ gsub("\t", "@"); print}'
a@b
```

なぜこうなるのかと言うと、文字列リテラルで `\t` を指定し、それが awk 言語のエスケープシーケンスとしてタブ文字に解釈されますが、正規表現の一部としてタブ文字を使うことは有効な正規表現だからです。最初の例では正規表現の `\s` を置換したかったため `gsub` 関数に `\s` を渡す必要がありましたが、`\t` の場合は `gsub` 関数にタブ文字を渡しても `\t` を渡しても同じ意味になるのでエスケープは一回でも良いのです。

この違いを理解してないと `gsub` 関数などに渡す正規表現をエスケープする場合に、バックスラッシュの数は一つで (`\`) でいいのか二つ (`\\`) 必要なのかと混乱してしまうことがあります。

#### 置換文字列での参照と特殊記法

置換文字列は `sub` 関数や `gsub` 関数で正規表現にマッチした部分を置換する文字列です。置換文字列自体は awk の文字列であるため awk のエスケープシーケンスが使えます。置換文字列自体は正規表現ではありませんが、置換文字列の中で `&` を使うことでマッチした全体を参照することができます。`&` そのものに置換したい場合は `\\&` と記述します（awk の文字列としてエスケープシーケンスが解釈され、`\&` は `&` と解釈されてしまうので `\` は二重に記述する必要がある）

```sh
$ echo "foo bar baz" | awk '{ sub(/(bar)/, "<&>\n\\& \\\\ \\\\\\&"); print }'
foo <bar>
& \ \& baz
```

なお awk が採用している ERE には正規表現の後方参照はありません。ただし gawk の拡張機能の `gensub` を使用すると置換文字列の中でキャプチャした文字列を参照することができます。

```sh
$ echo "a b" | gawk '{ print gensub(/(.) (.)/, "\\2 \\1", "g")}'
b a
```

#### `\\` への置換の互換性問題

正規表現と直接関係はないのですが関連情報として書いておきます。

二重バックスラッシュに置換する場合の書き方は、POSIX 準拠の方法はこのように書きます。

```sh
$ echo "@" | gawk --posix '{ sub(/@/, "A\\\\\\\\Z"); print }'
A\\Z
````

1. awk の文字列として `"A\\\\\\\\Z"` は `"A\\\\Z"` と解釈される
2. 置換文字列の特殊記法として `"A\\\\Z"` は `A\\Z` と解釈される

しかし、nawk では 2. の解釈が行われないようです。

```sh
# macOS の /usr/bin/awk は nawk。また Debian では original-awk が nawk。
$ echo "@" | /usr/bin/awk '{ sub(/@/, "A\\\\\\\\Z"); print }'
A\\\\Z

# POSIX 非準拠
$ echo "@" | /usr/bin/awk '{ sub(/@/, "A\\\\Z"); print }'
A\\Z

# gawk のデフォルトは nawk との互換性が考慮されている？
$ echo "@" | gawk '{ sub(/@/, "A\\\\Z"); print }'
A\\Z
$ echo "@" | gawk '{ sub(/@/, "A\\\\\\\\Z"); print }'
A\\Z
```

回避策はありますが、もっとシンプルにしたい所・・・。

```sh
$ echo "@" | /usr/bin/awk '
  BEGIN { DB="\\"; gsub(/\\/, "\\\\", DB); if (DB != "\\\\") DB = "\\\\\\\\" }
  { sub(/@/, "A" DB "Z"); print }
'
A\\Z
```

バックスラッシュを二重バックスラッシュに置換する場合は簡単な書き方があります。

```sh
$ echo 'A\Z' | /usr/bin/awk '{ gsub(/\\/, "&&"); print }'
A\\Z
```

#### `{m,n}` 未実装による互換性問題

awk は歴史的に ERE に近い正規表現を実装していましたが `{m,n}` は実装されていませんでした。POSIX.2-1992 で awk も ERE を使うものとされ `{m,n}` の対応が POSIX で要求されました。POSIX に準拠してない awk の実装は~~まだ残っています~~ 現在はありません（2023-10-17 修正）が、LTS 版の Ubuntu のサポート期間を考えると 2027 年頃までは注意が必要です。

- nawk: **20190305 から対応**
  - macOS 版: おそらく POSIX 準拠となった 10.5.0 (2007) から対応している
  - FreeBSD 版: 12.3 (2021-12), 13.0 (2021-04) から対応
  - NetBSD 版: 9.0 (2020-02) から対応
  - OpenBSD 版: 6.8 (2020-10) から対応（？） （2020-06 頃マージされている）
- mawk: **1.3.4 20200717 から対応**
  - Ubuntu 23.04 の標準 の mawk のバージョンは 1.3.4 20200120（非対応）
  - Ubuntu 23.10 の標準 の mawk のバージョンは 1.3.4 20230730（対応済み）
- gawk: 3.0 (2010) より `--posix` または `--re-interval` オプションで対応
  - 4.0 (2011) よりデフォルトで有効
- Busybox awk: 1.1.3 時点で対応してるのを確認。いつから対応しているかは不明
- Solaris: Solaris 10 の `/usr/xpg4/bin/awk` で対応してるのを確認

もし `{m,n}` に対応していない awk を使っている場合は、OS のバージョンアップで対応した時に問題が出ないように今から書き方に気をつけておく必要があります。具体的には `{` や `}` という文字とマッチさせたい時はエスケープするかブラケット表現の中で使うようにする必要があります。

```sh
# 現在の mawk は {} はただの文字として扱われる
$ echo "a{3}" | mawk '/a{3}/{print "ok"}'
ok

# 他の awk の実装ではマッチしない（POSIX に準拠した動作）
$ echo "a{3}" | gawk '/a{3}/{print "ok"}'

# 以下の書き方なら、どちらも文字として扱われるし将来 mawk が {m,n} に対応しても問題ない
$ echo "a{3}" | mawk '/a\{3\}/{print "ok"}'
$ echo "a{3}" | gawk '/a\{3\}/{print "ok"}'
```

#### mawk の `[:name:]` 非対応問題

古い mawk は POSIX 文字クラス（`[:name:]`）に対応していません。どうやら mawk 1.3.3 20090710（または 20090727）から対応したようです。対応していない環境は例えば Ubuntu 19.10 です。Ubuntu 18.04 LTS の無料サポートは 2023年5月で終了していますが、有償サポートの [Ubuntu Pro では 2028 年4月までサポートが継続される](https://jp.ubuntu.com/blog/18-04-end-of-standard-support-jp)ので注意が必要です。[@akinomyoga さんのコメントより](https://qiita.com/ko1nksm/items/53abc144558b9bb5629f#comment-5da926480811af20367d)

```console:Ubuntu 19.10は非対応
$ mawk -W version
mawk 1.3.3 Nov 1996, Copyright (C) Michael D. Brennan

compiled limits:
max NF             32767
sprintf buffer      2040

$ echo 123 | mawk '/[[:digit:]]/' # なにも出力されない
```

```console:Ubuntu 20.04は対応済み
$ mawk -W version
mawk 1.3.4 20200120
Copyright 2008-2019,2020, Thomas E. Dickey
Copyright 1991-1996,2014, Michael D. Brennan

random-funcs:       srandom/random
regex-funcs:        internal
compiled limits:
sprintf buffer      8192
maximum-integer     2147483647

$ echo 123 | mawk '/[[:digit:]]/'
123
```

昔の mawk ビルトインの正規表現ライブラリは POSIX 文字クラスに対応しておらず、まず 20090709 に加えられたパッチで外部の正規表現ライブラリが使えるようになり、20090727 の修正で mawk ビルトインの正規表現ライブラリが対応したようです（通常はデフォルトであるビルトインの正規表現ライブラリが使われるはず）。[CHANGES](https://invisible-island.net/mawk/CHANGES) より

```
20090709
	+ add support for external regexp libraries (patch by Aleksey Cheusov)
	  (originally comp.lang.awk June 15 2005, also cited in Debian #355442
	  March 6, 2006).

20090727
        + add check/fix to prevent gsub from recurring to modify on a substring
          of the current line when the regular expression is anchored to the
          beginning of the line; fixes gawk's anchgsub testcase.

        + add check for implicit concatenation mistaken for exponent; fixes
          gawk's hex testcase.

        + add character-classes to built-in regular expressions.

        + add 8-bit locale support
```

どうも CHAGES の参照先が間違っているようで正しくは以下です。

- [#65617: mawk: please implement POSIX-style character classes](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=65617)
- [#314323: mawk: please implement POSIX-style character classes](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=314323)
- [#355442 mawk: missing Posix ERE curly braces](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=355442) （これじゃないはず）
- [#485898: mawk: seems to have trouble with some RE expressions](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=485898)

#### [GNU] `\b` 重複問題

awk では POSIX の仕様として正規表現で C 言語スタイルのエスケープシーケンスが使えます。さらに gawk では拡張された正規表現のエスケープシーケンスも使えます。この中で唯一 `\b` は両方で定義されています。当然 awk としての標準仕様の方が優先ですので `\b` と書けば C 言語スタイルのエスケープシーケンスとしてバックスペースと解釈されます。では正規表現の `\b`（単語の区切り）が使いたい場合はどうするか？ `\b` の代わりに `\y` を使います。

```sh
$ printf "foo\nfoobar\nbar\n"
foo
foobar
baz

$ printf "foo\nfoobar\nbar\n" | sed -n '/\bbar/p'
bar

printf "foo\nfoobar\nbar\n" | awk '/\ybar/{print}'
bar
```


### `expr` コマンド

#### 固有の正規表現の拡張機能

`expr` は BRE を採用しています。`expr STRING : REGEXP` という表現で正規表現のマッチングができます。正規表現は `^` で始めなくても文字列の頭からマッチするという意味になるので注意してください。正規表現の最初の文字を `^` にした場合に、どのように解釈されるかは POSIX では指定されていません。（多くの実装は正規表現の `^` アンカーとして解釈されるようですが、`^` で始まる文字列と比較した場合どうするのがいいのか？頭にダミーの文字をつけるのが一番かなぁ？）

#### 空の正規表現の意味

正規表現が空文字の場合の挙動は POSIX では何も指定されてないようですが、GNU 版 `expr` は、一致せず `0` を出力し終了ステータス 1 になったのに対して、macOS（BSD 版）では、一致せずエラーメッセージを標準エラー出力に出力し、終了ステータス 2 （無効な式）と違いがでたので、POSIX 標準規格に書かれてないことを指摘したら「終了ステータスは 0 以外で、その出力は未定義である」とかになると思います。

#### 部分式によるマッチ部分の出力

部分式 `\( \)` には後方参照の他にもう一つの役目があります。それは `expr` の出力です。通常はマッチした文字列の長さを返すのですが、部分式を使うとマッチした文字列を返すようになります。返す文字列は後方参照の `\1` に相当する部分だけです。

```sh
expr "abc123" : "[a-z]*[1-3]*"
6

expr "abc123" :  "\([a-z]*\)\([1-3]*\)"
abc
```

### `grep` コマンド

#### 固有の正規表現の拡張機能

`grep` は BRE または ERE （`-E` オプション）を採用しています。

一般的に正規表現に改行文字を入れることは可能ですが、`grep` の場合は正規表現に改行文字を入れると OR 結合の複数の正規表現を意味するので注意してください。つまり以下のように機能します。

```sh
$ seq 100 | grep "10
^2" | xargs # 出力が縦に長いので xargs で 1 行にしています
2 10 20 21 22 23 24 25 26 27 28 29 100
```

上記の場合 `10` または `^2` にマッチする文字列を検索でしており `grep -e "10" -e "^2"` と同じ意味です。

#### 空の正規表現の意味

空文字の正規表現はすべての行にマッチします。

#### [GNU] Perl 互換の正規表現検索

GNU grep では `-P` オプションで Perl 互換の普段良く使っている（？）高度な正規表現 (PCRE) を使うことができます。この記事では詳細を省きますが GNU grep 以外との移植性が必要ない場合は `-P` オプションを使うのも良いでしょう。その場で実行するだけのものに移植性は必要ないですからね。移植性よりも効率です。まあ私は 普段 PCRE がデフォルトの [ag - The Silver Searcher](https://github.com/ggreer/the_silver_searcher) を使っているわけですが。

### `sed` コマンド

#### 固有の正規表現の拡張機能

`sed` は BRE または ERE （`-E` オプション） を採用しています。

POSIX の正規表現では `\n` は改行文字とはみなされません。しかし `sed` は例外で `\n` を改行文字とみなす（これは GNU 拡張ではありません）ため `sed` の置換命令を使って改行文字を削除することができます。ただし通常は 1 行ずつ読み込んで置換処理を行うため（この読み込んだメモリ領域のことをパターンスペースと言います）、置換対象のパターンスペースには改行文字が含まれていません。そこで全ての行をパターンスペースに読み込んでから改行文字を削除します。

```sh
# パターンスペースには改行文字が含まれている
$ seq 5 | sed -e ':loop' -e 'N; $!b loop'
1
2
3
4
5

# パターンスペースの改行文字を削除できる
$ seq 5 | sed -e ':loop' -e 'N; $!b loop' -e 's/\n//g'
12345

# 読みやすく（？）書き直すと
$ seq 5 | sed '
:loop
N
$!b loop
s/\n//g
'
```

コードの意味

1. `:loop` 戻り先用のラベル
2. `N` 次の行を（改行文字を追加して）パターンスペースに追加する
3. `$`（最終行） `!`（でなかったら） `b`（分岐する） `loop` に
4. `s/\n//g` パターンスペースに読み込まれた全てのデータの改行文字を削除する

上のコードで複数の `-e` を利用しているのは、BSD 版の `sed` がラベル名と次の命令を `;` で繋いで一行で書くことがことができないからです。

#### 正規表現を囲む文字のエスケープ

POSIX の正規表現では規定された特殊文字以外を `\` でエスケープした場合、その解釈は未定義となっていますが、`sed` では例外的に正規表現を囲った文字をエスケープすることができます。つまりこういう事です。

```sh
$ echo foobar | sed 's/foobar/baz/g'    # 一般的な書き方
$ echo foobar | sed 's|foobar|baz|g'    # / の代わりに任意の文字を使うことができる
$ echo foobar | sed 'sbfoo\barb\bazbg'  # b を使った場合
$ echo foo1bar | sed 's1foo\1bar1baz1g' # 1 を使った場合（\1 は後方参照としての意味を失う）
```

通常は `\b` の解釈は未定義なのですが `b` を正規表現を囲う文字として使った場合 `foobar` は `foo\bar` とエスケープして書かなければならなくなるので、この場合に限っては通常の文字として解釈されると POSIX で規定されています。また `\1` は本来は後方参照を意味しますが `1` を区切り文字として使った場合は、正規表現の中の `1` は `\1` と書くため後方参照ではなくなります。

ただ念の為ですが**こんなややこしい書き方はしないでください！** 区切り文字には正規表現にも置換文字列にも使われてない記号文字を使うものです。

#### 空の正規表現の意味

正規表現が空文字の場合、最後に使用した正規表現を意味します。これを利用すると簡単に m 番目と n 番目にマッチするものだけを変換するというようなことができます。

```sh
$ echo "a a a a a" | sed 's/a/B/2; s//C/3'
a B a C a

# 一度も正規表現を使ってなければエラーになる
$ echo "a a a a a" | sed 's//C/3'
sed: first RE may not be empty
```

#### 置換文字列での参照と特殊記法

置換文字列とは `s/正規表現/置換文字列/` の「置換文字列」の部分のことです。置換文字列自体は正規表現ではありませんが、マッチした全体またはキャプチャした文字列を参照することができます。その他置換文字列で使用可能なエスケープシーケンスは以下のとおりです、

|   置換文字列   | 意味                                         |
| :------------: | -------------------------------------------- |
|      `&`       | マッチした文字列全体に置換                   |
|      `\&`      | `&` そのものに置換                           |
| `\1` ... `\9`  | 後方参照に対応するキャプチャした文字列に置換 |
|   `\`\<改行>   | 改行文字に置換                               |
|      `\\`      | `\` そのものに置換                           |
| `\`\<上記以外> | 未定義                                       |

#### [GNU] 固有の正規表現の拡張機能

さて、先程のコードを少し書き換えて、改行を削除する代わりに、行頭に `@` を追加するコードに変更します。

```sh
$ seq 3 | sed -e ':loop' -e 'N; $!b loop' -e 's/^/@/g'
@1
2
3
```

見ての通り、このコードは最初の行の行頭だけに `@` を追加します。GNU sed にはマルチラインモードというのがあり、これを使うと 1 行毎に処理を行うことができます。

```sh
$ seq 3 | sed -e ':loop' -e 'N; $!b loop' -e 's/^/@/gM'
@1
@2
@3
```

そしてこのマルチラインモードで、行頭ではなく全体の最初にマッチするエスケープシーケンスが <code>\\`</code> です。（一周しただけの気がしますが・・・）

```sh
seq 3 | sed -e ':loop' -e 'N; $!b loop' -e 's/\`/@/gM'
@1
2
3
```

ということで GNU sed だけで使える追加の正規表現のエスケープシーケンスです。

|                  | 意味                                       |
| ---------------- | ------------------------------------------ |
| <code>\\`</code> | マルチラインモードで最初の行の行頭にマッチ |
| <code>\\'</code> | マルチラインモードで最後の行の行末にマッチ |

### `tr` コマンド

**このコマンドは正規表現と全く関係ありません。**

正規表現のブラケット表現と似ている表記で、範囲、POSIX 文字クラス、等価クラスが使えるため、勘違されそうなので念のために書いておきます。このコマンドも使い方を勘違いしている人が多いため、以下の参考リンクを紹介しておきます。

[trの範囲指定は POSIX準拠のシェルスクリプトなら 0-9 で動きます。だから [0-9] や 0123456789 と書く必要はなかったんですね](https://qiita.com/ko1nksm/items/9e9972c5fef87ab6d677)

### `vi` / `ex` コマンド

#### 固有の正規表現の拡張機能

`vi` は `ex` と同じ正規表現が利用されています。`vi` / `ex` では BRE が使われていますが、以下の拡張機能が POSIX で要求されています。

|       | 意味                       |
| :---: | -------------------------- |
| `\<`  | 単語の先頭にマッチ         |
| `\>`  | 単語の末尾にマッチ         |
|  `~`  | 前回置換した時の置換文字列 |

#### 空の正規表現の意味

最後に使用した正規表現です。

#### [vim] ERE 対応

多くの人は vi の代わりに拡張された vim を使っているのではないかと思いますが、vim では検索で使うパターンが以下の 4 種類あります。

- very magic ・・・ ERE 相当
- magic ・・・ デフォルト BRE 相当
- nomagic
- very magic

検索する時のパターンに `\v` と書くと、それ以降は very magic (ERE) として扱われます。デフォルトを very magic にする直接の方法はないため `:nnoremap / /\v` でキーの割当を変更する方法がよく紹介されています。

## 参考文献

- [詳説 正規表現 第 3 版](https://www.oreilly.co.jp/books/9784873113593/)
- [詳解 シェルスクリプト](https://www.oreilly.co.jp/books/4873112672/) 「3.2　正規表現」
- [The Open Group Base Specifications Issue 7, 2018 edition](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/)
  - [Base Definitions - 9. Regular Expressions](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html)
  - [Rationale: Base Definitions - A.9 Regular Expressions](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/xrat/V4_xbd_chap09.html)