Emacsの正規表現はPerlとackで置換する

perl regular expression

こんにちは、福山 @fokotate です。

最近、Emacsに凝ってたり、VPS(仮想プライベートサーバー)を借りたりとプログラムの方に突っ走ってます。ホームページを作るという点では道は逸れていません。
でも、ブログタイトル変えたほうがいいかな...

本題は、Emacsの正規表現置換が面倒なのでPerlの強力な正規表現を使う!というもの

Emacs Lisp use Perl RegExp

;; ack perlの正規表現を使った選択範囲置換 ---------------------------
;; ackコマンドの使い方  : http://d.hatena.ne.jp/toritori0318/20100404/1270395880
;; Emacsへの組み込み    : http://d.hatena.ne.jp/ryo1miya/20110112/1294827474
;; perlコマンドの使い方 : http://www.geocities.co.jp/SiliconValley-Sunnyvale/6128/perl/oneline.html

;; M-n M-p で専用の履歴をさかのぼれるようにする
(defvar perl-replace-history nil
  "History for the `perl-replace-command' and `perl-replace-command-ack-directory'.")

;; 編集中のバッファを対象
(defun perl-replace-command (command)
  "use perl regular expression for replace in current buffer.
USAGE:
  for example if you want to replace 'abc' to 'efg'.
  then select region area include 'abc' and below command
  M-x perl-replace-command  RET
      s/abc/efg/g           RET
 
  when you do not select region area,
  it will be whole region in current buffer."
  (interactive
   (list (read-shell-command "s[perl regexp] : "
                             ""
                             'perl-replace-history)))
  ;; クオートがあった場合、x27 を代わりに使う (コマンドがクオードで囲まれているため)
  (when (string-match "'" command)
    (setq command (replace-regexp-in-string "'" "\\x27" command)))
  (if (and mark-active transient-mark-mode)
      ;; 選択範囲があるとき
      (shell-command-on-region
       (region-beginning) (region-end) (concat "perl -0 -p -e '" command "'") nil t)
    ;; 選択範囲がない場合、バッファ全体を対象にする
    ;; 位置を記憶して操作が完了したら同じ位置に戻ってくる
    (let ((po (point))
          (win (window-start)))
      (mark-whole-buffer)
      (shell-command-on-region
       (region-beginning) (region-end)
       (concat "perl -0 -p -e '" command "'") nil t)
      (goto-char po)
      (set-window-start (selected-window) win))))
 
;; カレントディレクトリを一括対象
(defun perl-replace-command-ack-directory (command)
  "use perl regular expression for replace in current directory files.
USAGE:
  for example if you want to replace 'abc' to 'efg' in current directory.
  M-x perl-replace-command-ack-directory  RET
      s/abc/efg/g           RET"
  (interactive
   (list (read-shell-command "s[perl regexp dir] : "
                             ""
                             'perl-replace-history)))
  ;; クオートがあった場合、x27 を代わりに使う (コマンドがクオードで囲まれているため)
  (when (string-match "'" command)
    (setq command (replace-regexp-in-string "'" "\\x27" command)))
  (shell-command-to-string
   (concat "ack -f -n "
           "| xargs perl -0 -p -i -e '"
           command "'")))
;; quoted-insert "C-q" をプレフィックスキーに (もう使わないでしょ?)
(global-unset-key (kbd "C-q"))
(global-set-key (kbd "C-q r") 'perl-replace-command)
(global-set-key (kbd "C-q C-c r") 'perl-replace-command-ack-directory)
;; perl 正規表現--------------------------------------------------

使い方と注意

選択範囲を指定してから
M-x perl-replace-command または
キーボードから "C-q r" を入力して
s/対象/置換後/g
と入力してエンターを押すと選択範囲がPerlの正規表現で置き換わります。

選択範囲を指定しない場合は、バッファ全体が対象になります。

perl-replace-ack-directoryはカレントディレクトリにあるファイルをすべて対象にします。ackに-nオプションを付けているので、非再帰的です(再帰的はカレントディレクトリ以下のフォルダを芋づる式に対象にする)。広範囲で戻しが効かないので、バックアップのためperlの-iオプションに続けて任意の拡張子を付けることが可能です。perl -i.bkup ... とすると、foo.htmlのバックアップファイルfoo.html.bkupが同階層にできます。
すでに開いているバッファは再度読み込む必要があります。アンドゥで戻れるように再度読み込む便利な設定はこちらです。

使用するためには、perl, xargs, ack コマンドが必要になります。MacOSXの場合はxcode入れていればperlとxargsは揃っていますので、ackをインストールしてください。
ackを使おう - tototoshiの日記
ack 使い方 メモ - アルパカDiary
が参考になります。私の場合はbrewコマンドを入れて、ターミナル.appを開いて、"brew install ack" でOKでした。

(Linuxの場合はコマンド名がackではなくack-grepになっている場合があるので、上記コードのack部分をリネームしてください。)

こちらを参考にするとPerlの置換以外のコマンドも使えます。EmacsのバッファでPerl one-liner を手軽につかう - Tips

perl コマンド抜粋

-0
連続改行を含めたまとまった文字列を処理出来る。普段は一行ずつなのでs/nn/n/gといったことができない。このオプションを付けていたとしても、コマンド入力の際にs/../../gmと最後にmオプションをつければ打ち消せるし、全体ではなく各行ごとに行頭^ 行末$を使える。
-i
置換後にファイル内容を置き換える。指定しないとファイルに変化はない。続けて任意の文字を付けることでバックアップファイルを作成可能。
-n
while (<>) {...} バッファ内容全体を対象にする(最後以外にprintを使うときに必要)
-p
上記 -n の動作に加えて、最後に出力 print $_; をする。
-e
シングルクオート内のプログラムを実行 -e '...'

詳細はこちら Perl1行野郎

ack コマンド抜粋

-n
再帰、リカーシブしない。デフォルトは再帰する。-nを打ち消す場合は-rを明記する。
-f
ファイル名だけ表示する。
ack -f -n "./" でカレントディレクトリのファイル一覧を検索する。
--type-add
ack --css "文字列" とすると同階層以下のcssファイルだけが対象となるが、sass,scss,lessは対象にならない。以下の記述では任意の拡張子をcssの分類に追加して検索できる。
ack --type-add=css=.sass,.scss,.less --css "文字列" でOK
--type-set
特定の拡張子ファイルから検索したい場合
ack --type-set=new=.my,.mine --new "検索文字列"
-G REGEX
相対パスから、正規表現でファイル名を検索できる
-g REGEX
-f -G REGEX と同じ。どちらも次のオプションを使える。 -w 単語一致, -i 大文字小文字無視, -v 一致しない行に一致。
--nogroup
grepと同じように1行ずつファイル名とマッチした行を表示する。

ack 公式サイト

ちなみに ack --thpppt と打つと..

おわり

Regexp isearch みたいな検索もをperl正規表現でやってみたいなぁ。

追加 乱数と連番をつくる

eオプションを付ければ、もっと使い方が広がります。空行をいくつか範囲選択してからコマンドを実行

;; 1から1ずつ増やす
C-q r s/n/$ee++."n";/eg

;; 乱数を発生
C-q r s/n/$cc=int(rand 100)+1;$cc."n";/eg