正規表現の置換文字列でLisp式を使う

# 正規表現の置換文字列でLisp式を使う

この記事は[Emacs Advent Calendar 2020](https://qiita.com/advent-calendar/2020/emacs)の24日目の記事です。空いているともったいないので、12/26に急遽参加しました^^;;;

## はじめに
Emacsの特長のひとつが、パワフルな正規表現置換。インクリメンタル置換の`query-replace-regexp`（標準で、`C-M-%`にキーバインド）や一括置換の`replace-regexp`は、Emacsでもっとも多くの人々に愛用されている関数の一つではないでしょうか。

そんな正規表現置換をさらに便利にするのが置換する文字列の一部でLisp式を使う機能です。[Emacsのマニュアル](https://www.gnu.org/software/emacs/manual/html_node/emacs/Regexp-Replace.html)にも書いてあるように、置換する文字列の中に`\,`を記述するとその後の`()`にLisp式を記述できます。実例があった方がわかりやすいと思うので、サンプルとして次のCSV形式データを用意しました。

```plaintext:サンプル
名前,名前のローマ字表記,住所,電話番号,メールアドレス
竈門 炭治郎,tanjiro kamado,東京都西多摩郡奥多摩町日原65536,070-1234-xxxx,tanjiro@example.com
竈門 禰豆子,nezuko kamado,東京都西多摩郡奥多摩町日原65536,080-6543-xxxx,nezuko@example.com
我妻 善逸,zenitsu agatsuma,東京都新宿区箪笥町8192,080-3598-xxxx,zenitsu@example.com
嘴平 伊之助,inosuke hashibira,東京都西多摩郡檜原村海澤32678,080-9234-xxxx,inosuke@example.com
不死川 玄弥,genya shinazugawa,東京都中央区京橋4096-8192,090-2234-xxxx,genya@example.com
栗花落 カナヲ,kanao tuyuri,東京都墨田区向島2048-32-8192,090-2234-xxxx,kanawo@example.com
```

このサンプルから、次のような置換をします。

1. 「名前のローマ字表記」列で、単語（名字と名前）の先頭を大文字にする。「メールアドレス」列は元のままにする
1. 「住所」列をすべて全角文字にする。ほかの列はそのままにする
1. データ行の先頭に1からはじまる連番を挿入する

### 文字列の一部のだけアルファベット単語の先頭を大文字にする

Emacsでは、指定した範囲の全体でアルファベット単語の先頭を大文字にするためには、`capitalize-region`関数を使います。すべてのアルファベットを大文字にする`upcase-region`（`C-x C-u`）や小文字にする`downcase-region`（`C-x C-l`）とともに覚えておくと便利でしょう。しかし今回のサンプルでは、「メールアドレス」列も変換されてしまうため、`capitalize-region`関数をそのまま使うことはできません。

代わりに、`query-replace-regexp`や`replace-regexp`で、置換前と置換後の文字列を次のように指定すれば、「名前のローマ字表記」列だけ単語の先頭を大文字にできます。この場合、「メールアドレス」列はもちろん元のままです。サンプル全体を選択してから次の文字列で置換します。

```:置換前の文字列
\(.+?\),\(.+?\),\(.+?\),\(.+?\),\(.+\)
```

```:置換後の文字列
\1,\,(capitalize \2),\3,\4,\5
```

置換後のサンプルデータは次のようになります。

```plaintext:置換後のサンプル
名前,名前のローマ字表記,住所,電話番号,メールアドレス
竈門炭治郎,Tanjiro Kamado,東京都西多摩郡奥多摩町日原65536,070-1234-xxxx,tanjiro@example.com
竈門禰豆子,Nezuko Kamado,東京都西多摩郡奥多摩町日原65536,080-6543-xxxx,nezuko@example.com
我妻善逸,Zenitsu Agatsuma,東京都新宿区箪笥町8192,080-3598-xxxx,zenitsu@example.com
嘴平伊之助,Inosuke Hashibira,東京都西多摩郡檜原村海澤32678,080-9234-xxxx,inosuke@example.com
不死川玄弥,Genya Shinazugawa,東京都中央区京橋4096-8192,090-2234-xxxx,genya@example.com
栗花落カナヲ,Kanao Tuyuri,東京都墨田区向島2048-32-8192,090-2234-xxxx,kanawo@example.com
```

少し説明しますと、`\(.+?\),\(.+?\),\(.+?\),\(.+?\),\(.+\)`のような指定の仕方はコンマ区切りデータを置換するときの常套手段。最後以外の列データは`\(.+?\)`で最後の列データだけ「?」のない`\(.+\)`で表します。このうち、2列目のデータに対してだけ`\,`でLisp式を適用し、`capitalize`関数で単語の先頭を大文字に変換するよう指定しています。

### 文字列の一部のだけ全角文字にする

アルファベットの大文字小文字の変換や単語の先頭を大文字にする作業は、日本ではあまり必要ないかもしれません。代わりに求められるのが、全角や半角の変換作業。住所欄への入力が全角でないと叱られるWebサービスはストレスフルですね。全角半角変換のようなくだらない作業をなんで人様に押し付けるんだろう^^;;; というわけで、「住所」列だけをEmacsを使って全角に変換しましょう。Emacsでは半角文字を全角文字に変換する関数として`japanese-zenkaku`が用意されています。サンプル全体を選択してから次の文字列で置換します。

```:置換前の文字列
\(.+?\),\(.+?\),\(.+?\),\(.+?\),\(.+\)
```

```:置換後の文字列
\1,\2,\,(japanese-zenkaku \3),\4,\5
```

置換後のサンプルデータは次のようになります。

```plaintext:置換後のサンプル
名前,名前のローマ字表記,住所,電話番号,メールアドレス
竈門炭治郎,Tanjiro Kamado,東京都西多摩郡奥多摩町日原６５５３６,070-1234-xxxx,tanjiro@example.com
竈門禰豆子,Nezuko Kamado,東京都西多摩郡奥多摩町日原６５５３６,080-6543-xxxx,nezuko@example.com
我妻善逸,Zenitsu Agatsuma,東京都新宿区箪笥町８１９２,080-3598-xxxx,zenitsu@example.com
嘴平伊之助,Inosuke Hashibira,東京都西多摩郡檜原村海澤３２６７８,080-9234-xxxx,inosuke@example.com
不死川玄弥,Genya Shinazugawa,東京都中央区京橋４０９６−８１９２,090-2234-xxxx,genya@example.com
栗花落カナヲ,Kanao Tuyuri,東京都墨田区向島２０４８−３２−８１９２,090-2234-xxxx,kanawo@example.com
```

### 連番を挿入する

Emacsでは、データに連番を挿入するために[rectangle-number-lines（C-x r N）](https://qiita.com/kwappa/items/639b40ef0e18170edf43)が使えます。でも、キーバインドも関数の使い方もなかなか覚えられないし、思った通りに連番が入れられないことも多い…というわけで、置換機能を使って連番を入れるのが便利な場合も多いと私は思っています。

Emacsの正規表現置換では置換後の文字列に`\#`を使えば置換された件数を出力できるので、これで連番を入れられます。ところが、連番はたいてい1, 2, 3, ...と1からはじめたいのに、`\#`は0, 1, 2, ...と0からはじまってしまいます。でも、そういう悩みも`\,`を使えばたちまち解決します。ちょっと変わった名前の`1+`という関数をいっしょに使います。サンプルのデータ部分である2行目以降を選択してから次の文字列で置換します。

```:置換前の文字列
^.+$
```

```:置換後の文字列
\,(1+ \#),\&
```

```plaintext:置換後のサンプル
名前,名前のローマ字表記,住所,電話番号,メールアドレス
1,竈門炭治郎,Tanjiro Kamado,東京都西多摩郡奥多摩町日原６５５３６,070-1234-xxxx,tanjiro@example.com
2,竈門禰豆子,Nezuko Kamado,東京都西多摩郡奥多摩町日原６５５３６,080-6543-xxxx,nezuko@example.com
3,我妻善逸,Zenitsu Agatsuma,東京都新宿区箪笥町８１９２,080-3598-xxxx,zenitsu@example.com
4,嘴平伊之助,Inosuke Hashibira,東京都西多摩郡檜原村海澤３２６７８,080-9234-xxxx,inosuke@example.com
5,不死川玄弥,Genya Shinazugawa,東京都中央区京橋４０９６−８１９２,090-2234-xxxx,genya@example.com
6,栗花落カナヲ,Kanao Tuyuri,東京都墨田区向島２０４８−３２−８１９２,090-2234-xxxx,kanawo@example.com
```

### まとめと続編予告（？）
というわけで、Emacsの正規表現置換では、置換後の文字列の中に`\,`を記述することでLisp式を入れることができます。この機能により、正規表現置換をさらに便利に使えるのでぜひ試してください。

なお、正規表現置換でのLisp式では、`\1`, `\2`, ...のようなグループにされた部分にマッチする文字列で数値を取得して計算することもできます。その機能を使えばEmacsの正規表現置換はさらにパワフルで便利になるのですが…長くなるのでその話はまた別にします。それでは、クリスマスは過ぎてしまいましたけどよい年末年始を！