grepやsedの「-eと-Eの違い」は、基本正規表現と拡張正規表現じゃねーぞ！

## はじめに

`grep` コマンドや `sed` コマンドの `-e` オプションや `-E` オプションの意味を勘違いしている例を見かけるので訂正です。

- `-e` オプションは正規表現またはスクリプトを追加指定するためのオプションです
- `-E` オプションは拡張正規表現モードを有効にするためのオプションです

`-e` と `-E` は反対の意味を持つオプションではありません。まったく異なる機能を提供しているオプションです。`-e` オプションは拡張正規表現でも使うことができます。

## -e オプションは正規表現の追加指定

まず、なぜ `-e` オプションというものが作られたのか？ それはハイフンで始まる正規表現を指定したり、sed スクリプトを組み立てたりするためです。

### grep コマンドの話

例えば `grep` コマンドで `-a` という文字列を検索したい場合はどうするでしょうか？

```sh
# 通常ならこう書ける
grep 'abc'

# -で始まるとオプションとみなされてしまう
grep '-a'
```

おそらく `grep '\-a'` でも問題なく動作すると思いますが、正規表現としては未定義の動作なので信頼性がありません。このような場合に `-e` オプションで指定します。

```sh
# おそらくこれでも動くと思うが...
grep '\-a'

# -e オプションのオプション引数（次の引数）が検索パターンであることは明らかである
grep -e '-a'
```

このような「ハイフンで始まる引数をオプションと解釈されないようにするためのオプション」はいろんなコマンドで使われています。

`grep` コマンドの `-e` オプションのもう一つの使い方は検索パターンの追加です。正規表現を駆使して同じことをすることはできますが、`-e` オプションを使うと「または」という意味で検索パターンを追加することができます。

```console
$ seq 10 | grep -e 2 -e 4
2
4
```

ちなみにこの動作は検索パターンを改行でつないで書くこともできます。言い換えると `-e` オプションは検索パターンを改行でつなげて書くのと同じ意味になるということです。

```sh
$ seq 9 | grep '2
4'
2
4
```

また、`grep` コマンドの正規表現リストはファイルから読み込むことも可能です。

```console
$ cat pattern.txt
2
4

$ seq 9 | grep -f pattern.txt
2
4
```

`-e` オプションの本質はこの正規表現リストの組み立てです。

### sed コマンドの話

`sed` コマンドも理屈は同じです。ただし `-` で始まる正しく解釈可能な sed スクリプトは存在しないはずなので、`-e` オプションを使う理由は、（`grep` コマンドの検索パターンの追加と同じように）sed スクリプトの組み立てです。

```sh
$ echo test | sed -e 's/t/T/g' -e 's/e/E/g'
TEsT

# 「;」区切りでつなげて書くこともできる（BSD系コマンドでは一部「;」ではつなげられない場合がある）
$ echo test | sed 's/t/T/g; s/e/E/g'
TEsT
```

実は `sed` コマンドでも `grep` コマンドと同じように `-e` オプションでの追加指定は改行でつなぐのと同じ意味となります。

```sh
$ echo test | sed 's/t/T/g
s/e/E/g'
TEsT
```

`sed` コマンドの命令は本質的にはスクリプト言語であり、長くて複雑なコードはワンライナーで書くべきではなく独立したスクリプトファイルにすべきものです。`-e` オプションはそのスクリプトをコマンドライン引数から組み立てるためのオプションといえます。

```console
$ cat script.sed
s/t/T/g
s/e/E/g

$ echo test | sed -f script.sed
TEsT
```

## -E オプションは拡張正規表現の有効化

`grep` コマンドまたは `sed` コマンドの `-E` オプションは使用する正規表現をデフォルトの基本正規表現から拡張正規表現に変更するためのオプションです。つまりこのように書きます。

```console:基本正規表現の場合（カッコはただの文字）
$ echo '(test)' | sed -e 's/(test)/[TEST]/g'
[TEST]
```

```console:拡張正規表現の場合（カッコはグループ）
$ echo '(test)' | sed -E -e 's/(test)/[TEST]/g'
([TEST])
```

### なぜ -e を基本正規表現だと勘違いする人がいるのか？

`-e` オプションは検索パターンやスクリプトを**追加**するオプションです。そして追加する必要がないのであれば `-e` オプションは省略することができます。したがって

- 「`-E` `-e 正規表現`」の `-e` オプションを省略して書くと
- 「`-e 正規表現`」と
- 「`-E` `正規表現`」は同じような形になってしまいます

```console:スクリプトが一つしかない場合は-eオプションは省略可能
$ echo '(test)' | sed -E 's/(test)/[TEST]/g'
([TEST])
```

実際には、`-e 正規表現` の正規表現は `-e` オプションのオプション引数で、`-E` オプションにはオプション引数はありません。コマンドライン引数の構造を理解せずに書いていれば、勘違いするだろうなとは思いますが、そのような雑な使い方をしていると罠にハマります。

### -E でつなぐとファイル名が見つからないと言われる？？？

たしかに言われますね。これも `-E` オプションの意味を勘違いしているからです。

```console
$ echo test | grep -E 'aaa' -E 'bbb' -E 'ccc'
grep: bbb: そのようなファイルやディレクトリはありません
grep: ccc: そのようなファイルやディレクトリはありません

$ echo test | sed -E 's/a/A/g' -E 's/b/B/g' -E 's/c/C/g'
sed: s/b/B/g を読み込めません: そのようなファイルやディレクトリはありません
sed: s/c/C/g を読み込めません: そのようなファイルやディレクトリはありません
```

`-E` オプションはオプション引数を取りません。GNU コマンドではオプションを引数の後ろに書くことができるので、`-E` オプションを複数指定しているのと同じ意味になります。すなわち簡略化して書くと次のように解釈されています。

```console
$ echo test | grep -E 'aaa' 'bbb' 'ccc'

$ echo test | sed -E 's/a/A/g' 's/b/B/g' 's/c/C/g'
```

`grep` コマンドまたは `sed` コマンドの書式は次のとおりです。

```
Usage: grep [OPTION]... PATTERNS [FILE]...

Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...
```

つまり、2番目3番目の正規表現やスクリプトはファイル名を書く場所にあります。ファイル名が正規表現文字列になっているので、エラーになるのは当然です。

正しい書き方は `-E` オプションで拡張正規表現を有効にし、`-e` オプションで正規表現やスクリプトをつなげます。

```console
$ echo test | grep -E -e 'aaa' -e 'bbb' -e 'ccc'

$ echo test | sed -E -e 's/a/A/g' -e 's/b/B/g' -e 's/c/C/g'
```

## さいごに

ドキュメントをちゃんと読んで正しい使い方を調べましょう。