🔰【正規表現】前方一致、前方不一致、後方一致、後方不一致

正規表現で次のようなパターンの文字列を検索したり、置換したりしたいときのメモです。

:::note
**💡 胡瓜の絵文字はプレースホルダです**
本記事内のサンプルコードに含まれる「🥒」は、任意の文字列に置き換えてください。
:::

:::note
**💡 このサンプルはJavaScriptで描かれています**
JavaScriptでは、正規表現を表すためにスラッシュデリミタで囲む必要があります。

```js
const 正規表現ではない = '^hoge$';
const 正規表現 = /^hoge$/;
```
:::



## 1. 文字列全体について

- `^🥒`: 特定の文字で始まるパターン
- `^(?!🥒)`: 特定の文字で始まらないパターン
- `🥒$`: 特定の文字で終わるパターン
- `^(?!.*🥒$).*`: 特定の文字で終わらないパターン

:::note
**💡 正規表現フラグ**
JavaScriptでは、正規表現フラグという概念があります。
大文字小文字を区別しない、文全体ではなく行ごとに検査する、などです。

```js
// hoge という文字で始まり、終わっているかを確認する
const 改行ごとにチェックする = /^hoge$/m;
const 文字列全体をチェックする = /^hoge$/;

const 文字列 = `hoge
piyo
ponyo`;

改行ごとにチェックする.test(文字列); // -> true
文字列全体をチェックする.test(文字列); // -> false
```

See Also: [正規表現 - JavaScript | MDN  #フラグを用いた高度な検索](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_expressions#%E3%83%95%E3%83%A9%E3%82%B0%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E9%AB%98%E5%BA%A6%E3%81%AA%E6%A4%9C%E7%B4%A2)
:::

### 前方一致：特定の文字で始まっていることを確認したい

```js:正規表現
const regExp = /^🥒/

// hogeで始まってるかどうか
/^hoge/.test('hoge文字列') // -> true
/^hoge/.test('文字列') // -> false
```

```js:実行例
// 文字列の先頭の「abc」を★にしたい
'abcdef'.replace(/^abc/, '★') // -> ★def
'abcDEF'.replace(/^abc/, '★') // -> ★DEF
'ABCdef'.replace(/^abc/, '★') // -> ABCdef
'_abcdef'.replace(/^abc/, '★') // -> _acbdef
```

### 前方不一致：特定の文字で始まっていないことを確認したい

```js:正規表現
const regExp = /^(?!🥒)./

// hogeで始まってないかどうか
/^(?!hoge)./.test('文字列') // -> true
/^(?!hoge)./.test('hoge文字列') // -> false
```

```js:実行例
// 「abc」で始まっていない文字列の最初の1文字を★にしたい
'abcdef'.replace(/^(?!abc)./, '★') // -> abcdef
'abcDEF'.replace(/^(?!abc)./, '★') // -> abcDEF
'ABCdef'.replace(/^(?!abc)./, '★') // -> ★BCdef
'_abcdef'.replace(/^(?!abc)./, '★') // -> ★abcdef
```

### 後方一致：特定の文字で終わっていることを確認したい

```js:正規表現
const regExp = /🥒$/

// hogeで終わってるかどうか
/hoge$/.test('文字列hoge') // true
/hoge$/.test('文字列') // false
```

```js:実行例
// 文字列の最後の「def」を★にしたい
'abcdef'.replace(/def$/, '★') // -> abc★
'abcDEF'.replace(/def$/, '★') // -> abcDEF
'ABCdef'.replace(/def$/, '★') // -> ABC★
'_abcdef'.replace(/def$/, '★') // -> _abc★
```

### 後方不一致：特定の文字で終わっていないことを確認したい

単一の文字で終わっていないことを確認するケースを除いて、基本的には後方一致（`$`）で検査して、「一致していない」ことを確認したほうがよい。

```js:正規表現
const regExp = /^(.*)([^🥒])$/

// hogeの4文字いずれかで終わっていないかどうか
/^(.*)([^hoge])$/.test('文字列') // true
/^(.*)([^hoge])$/.test('文字列hoge') // false
/^(.*)([^hoge])$/.test('文字列hego') // false
/^(.*)([^hoge])$/.test('文字列geo') // false
/^(.*)([^hoge])$/.test('文字列o') // false
```

```js:実行例
// 「d」「e」「f」で終わっていない文字列の最後の1文字を★にしたい
'abcdef'.replace(/^(.*)([^def])$/, '$1★') // -> abcdef
'abcDEF'.replace(/^(.*)([^def])$/, '$1★') // -> abcDE★
'ABCdef'.replace(/^(.*)([^def])$/, '$1★') // -> ABCdef
'_abcdef'.replace(/^(.*)([^def])$/, '$1★') // -> _abcdef

'abcfed'.replace(/^(.*)([^def])$/, '$1★') // -> abcfed
'deABCf'.replace(/^(.*)([^def])$/, '$1★') // -> deABCf
'_abdfce'.replace(/^(.*)([^def])$/, '$1★') // -> _abdfce
```


## 2. 文中について

- `(?<=🥒)`: 特定の文字から続くパターン
- `(?<!🥒)`: 特定の文字から続かないパターン
- `(?=🥒)`: 特定の文字が後に続くパターン
- `(?!🥒)`: 特定の文字が後に続かないパターン

:::note warn
**丸各個についての注意点**
前方一致、後方一致で登場するパーレン（丸括弧）は[キャプチャグループ](https://ja.javascript.info/regexp-groups)として機能しません。

```js:パーレンの中身が参照できる例
// 文字列の`$1`で参照できる状態
'abcdef'.replace(/(.*?)def/, '$1'); // -> 'abc'

// 第2引数`p1`で参照できる状態
'abcdef'.replace(/(.*?)def/, (_, p1) => {
  console.log(p1); // > 'abc'

  return p1;
}); // -> 'abc'
```

```js:パーレンの中身が参照できない例
'abcdef'.replace(/(?<=abc)def/, '$1'); // -> 'abc$1'
```
:::

### 前方一致：特定の条件の後に続く文字列を探索したい

```js:正規表現
const regExp = /(?<=🥒)/
```

```js:実行例
// abcに続く「def」を★にしたい
'_abcdef'.replace(/(?<=abc)def/, '★') // -> _abc★
'_abcDEF'.replace(/(?<=abc)def/, '★') // -> _abcDEF
'_ABCdef'.replace(/(?<=abc)def/, '★') // -> _ABCdef
'_123def'.replace(/(?<=abc)def/, '★') // -> _123def
```

### 前方不一致：特定の条件以外の後に続く文字列を探索したい

```js:正規表現
const regExp = /(?<!🥒)/
```

```js:実行例
// abc以外の文字列に続く「def」を★にしたい
'_abcdef'.replace(/(?<!abc)def/, '★') // -> _abcdef
'_abcDEF'.replace(/(?<!abc)def/, '★') // -> _abcDEF
'_ABCdef'.replace(/(?<!abc)def/, '★') // -> _ABC★
'_123def'.replace(/(?<!abc)def/, '★') // -> _123★
```

### 後方一致：特定の条件が後に続く文字列を探索したい

```js:正規表現
const regExp = /(?=🥒)/
```

```js:実行例
// defが後に続く「abc」を★にしたい
'_abcdef'.replace(/abc(?=def)/, '★') // -> _★def
'_abcDEF'.replace(/abc(?=def)/, '★') // -> _abcDEF
'_ABCdef'.replace(/abc(?=def)/, '★') // -> _ABCdef
'_abc123'.replace(/abc(?=def)/, '★') // -> _abc123
```

### 後方不一致：特定の条件が後に続かない文字列を探索したい

```js:正規表現
const regExp = /(?!🥒)/
```

```js:実行例
// defが後に続かない「abc」を★にしたい
'_abcdef'.replace(/abc(?!def)/, '★') // -> _abcdef
'_abcDEF'.replace(/abc(?!def)/, '★') // -> _★DEF
'_ABCdef'.replace(/abc(?!def)/, '★') // -> _ABCdef
'_abc123'.replace(/abc(?!def)/, '★') // -> _★123
```

## 📝 参考文献

- [正規表現 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions)
- [グループと範囲 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Ranges)