Emacs でもカッコいい置換がしたい


Emacs x 正規表現

Emacs の長年のライバルたる Vim は、正規表現を用いた置換がパワフルであることが知られています。
実際に、Vimmer が謎のタイピングをしてソースコードがコロコロ変わっていく姿を見たことがある人は、「俺もこんな編集が出来たら良いなぁ」と思うかもしれません。
本ページでは、この Vimmer のカッコいい置換を Emacser でも簡単に出来るようにすることを目的とします。

環境構築

Emacs は拡張力が尋常ではない (別に Vim も VSCode も拡張しないとただの文鎮ですが)、ことが知られ(原因で倦厭され)ていますが、この当たりを手っ取り早く解決するものに、Spacemacs があります。
Spacemacs は Emacs の拡張機能を簡単に導入できるようにしたものであり、本環境構築ではこちらを利用します。

rm -rf ~/.emacs.d
git clone https://github.com/syl20bnr/spacemacs ~/.emacs.d

上のコマンドを実行した後に、Emacs を立ち上げると、 Spacemacs の基本設定が聞かれると思います。注意すべき点は、 keybind を聞かれる部分で、 emacs を選択するべきであるという点です。こうすることで Emacs の標準的なキーバインドを用いることが出来ます。他のキーバインドを選択してもよいのですが、Emacser が Spacemacs を使うには、こちらを使うほうが楽です。

拡張機能の追加

C-x C-f ~/.spacemacs で、 Spacemacs の設定ファイルである .spacemacs を開きます。基本的には、 dotspacemacs-configuration-layersdotspacemacs-additional-packages#dotspacemacs/user-config を弄ることで様々な拡張機能の調整を行います。
今回は正規表現とその置換をサポートするためのパッケージである、 visual-regexpvisual-regexp-steroids を導入します。

dotspacemacs-configuration-layers
 '(
   ;;;
   ;; ----------------------------------------------------------------
   ;; Example of useful layers you may want to use right away.
   ;; Uncomment some layer names and press <SPC f e R> (Vim style) or
   ;; <M-m f e R> (Emacs style) to install them.
   ;; ----------------------------------------------------------------
   ;; 以下を追加
   ivy
   auto-completion
   better-defaults
   ;; ...
)

dotspacemacs-additional-packages '(
  ;; 以下を追加
  visual-regexp
  visual-regexp-steroids
)

(defun dotspacemacs/user-config ()
  "..."
  ;; 以下を追加
  (global-set-key (kbd "M-%") 'vr/query-replace)
)

 
これらを書ききったら、 M-m f e R (M-x dotspacemacs/sync-configuration-layers) で設定ファイルを反映します。

Tips:
Spacemacs では、 M-mM-x C-x をプレフィックスにして、適用可能なコマンドをミニバッファに表示することが出来ます。
例えば、 M-m とすると、コマンド一覧が見えます。そして major mode commands である m を次に押し、 e (emacs/spacemacs) r (dotspacemacs/sync-configuration-layers) とインタラクティブにコマンド入力を行い、目的のコマンドを実行することが出来ます。

大学などで無理やり Emacs を用いることを強要させられる場合、これらのコマンドは暗記させられることが一般ですが、 Spacemacs ではこれらの機能を確認しながら実行することが出来ます。 きっと彼らは Emacs を嫌いにさせるために授業をしているに違いない。

Emacs の正規表現

まずはシンプルに選択・置換してみる

例として次のテキストを用いて説明を進めていきます。

Hello.
Hey!
Hi!
Hoge.

まずは Emacs で検索をしてみましょう。コマンドは C-s または M-m s s です。すると、 Swiper: という入力バッファが表れるので、ここに Hello. と入力してみましょう。タイピングするごとに絞り込みが行われていることを確認できると思います。
絞り込みが行えれば、次に置換をしてみましょう。
M-% で置換を始めてみます。まず M-% Hello. [RET] Hello. としてみて下さい。何から何へ置換するのかが視覚的にわかると思います。 ? でどのように置換するかを選択できます。例えば y で今選択されている文字列を置換し、 n で置換せず次の選択肢へ遷移します。 ! は全ての文字列を一括で置換する方法で、範囲選択(C-SPC + 矢印) と組み合わせることで範囲内一括置換が出来ます。

正規表現上の特殊記号

Emacs における正規表現の特殊記号は次のとおりです。

記号 意味 例(regex -> match)
. 何らかの1文字 h.llo -> hello
x\* xの0回以上の繰り返し hel\*o -> helllllllo
x+ xの1回以上の繰り返し hel\*o -> helllllllo
x? x が 存在してもしなくても良い hela?lo -> hello
x\{i,j\} x がi 回以上 j 回以下存在する。 hel\{1,3\}o->helllo
[…] … 内のいずれかにマッチする h[A-z]\*o -> hello
[^…] … 内のいずれにもマッチしない [1-9]\*1 -> hello1
行の先頭にマッチする ^hello->hello
行の末尾にマッチする hello$ -> hello
\ 1. 特殊記号のエスケープ he\$llo -> he$llo
  2. 特殊な正規表現システムの導入  
C-q C-j 改行(\n)  

バックスラッシュ (\) を用いることで Emacs 特有の正規表現システムを用いることが出来ます。
この例を幾つか紹介します。

Or 選択 (\|)

hello\|hy -> hello

グルーピング (\(...\))

\(hello\|hy\)! -> hy!

単語 (\b)

\bhello -> hello

これは hogehellohy にはマッチしません。変数名などの置換に向いていると言えます。なお、 \B はその逆です。

全ての文字 (\w)

h\w+o -> hello

全ての特殊文字 (\W)

h\w+\W+o -> hell___o

空白文字 (\s-)

hogeho\s-+geho -> hogeho    geho

選択したものの復元 (\)

置換を行う上で、元の語を用いた変換をしたい場合があると思います。そんなときには \{idx} を使うことでマッチした文字列を利用することが出来ます。
例えば、hello について、 \(hel\)\(lo\) [RET] \1\1\2 とすることで、 helhello と出来ます。

emacs-lisp の関数呼び出し (\,)

\, を用いることで emacs-lisp の関数の幾つかを用いることが出来ます。
例えば、hello について、 \(\w+\) [RET] \,(capitalize \1) とすることで、 Hello と出来ます。

Tips:

#capitalize 先頭を大文字
#downcase 小文字化
#upcase 大文字化
replace-count 今までの置換回数

正規表現を用いて置換してみる

先程の例を思い出してみましょう。

Hello.
Hey!
Hi!
Hoge.

例1

Q.次のように置換して下さい。

Hello.
Hey.
Hi.
Hoge.

A.回答例

! [RET] .

例2

Q.次のように置換して下さい。

Hello.
HeyHey!
HiHi!
Hoge.

A.回答例

\(\w+\)\(!\) [RET] \1\1\2

応用的な置換

例1.

jan feb mar apr may jun jul aug sep oct nov dec

これを、

Jan 1
Feb 2
Mar 3
Apr 4
May 5
Jun 6
Jul 7
Aug 8
Sep 9
Oct 10
Nov 11
Dec 12

とします。

A. 回答例

\(\w+\)\s-? [RET] \,(capitalize \1) \,(+ 1 replace-count) [C-q C-j]