A Riddle Wrapped in a Mystery

中身はエニグマ・Emacs

Emacs で Zsh の ヒストリファイルを閲覧・編集する

zsh のヒストリファイル /.zsh_history は、ASCII以外の文字に対してエス ケープをしているため、通常はエディタで閲覧したり編集できない。

Emacsで、 zsh_history 独自のデコーダ・エンコーダを実装することで、 zshヒストリファイルの閲覧・編集できるようにする。

zsh ヒストリファイルの構造

zshのヒストリファイルは、保存するデータの各バイトにおいて、それが 0x80-0x9d または 0xa0 のとき、その前に 0x83 を入れて、続くバイトの 6bit 目を反転させるようになっている。詳細は zsh のソースコードの meta.c を参照。

zsh ヒストリファイルのデコーダ

以下に zshのヒストリファイルのデコーダをCCLで示す。ただし、本来はCCLは UCS符号を出力することになっているが、このCCLデコーダはUTF-8のバイトシー ケンスを出力する。(UTF-8のデコーダを別途接続する必要がある。)

(define-ccl-program zsh-history-decoder
  '(1 ((loop
        (read-if (r0 == #x83)
                 ((read r0) (r0 ^= #x20)))
        (write r0)
        (repeat))))
  "decode .zsh_history file.")

zsh ヒストリファイルのエンコーダ

(define-ccl-program zsh-history-encoder
  '(2 ((loop
        (read r0)
        (r1 = (r0 < #x9e))
        (r2 = (r0 == #xa0))
        (if (((r0 > #x82) & r1) | r2)
            ((write #x83) (write (r0 ^ #x20)))
          (write r0))
        (repeat))))
  "encode .zsh_history file.")

zsh ヒストリファイルの符号体系

上記のデコーダ・エンコーダは、UTF-8 バイト列を対象としているが、これら はEmacsではLatin-1に解釈されてしまう。そのため、一旦Latin-1と解釈された 符号をUCS符号に直さなければならない。そのために、post-read-converter と、 pre-write-converter を用意する。

(defun post-read-decode-utf8 (len)
  (save-excursion
    (save-restriction
      (narrow-to-region (point) (+ (point) len))
      (encode-coding-region (point-min) (point-max) 'latin-1)
      (decode-coding-region (point-min) (point-max) 'utf-8)
      (- (point-max) (point-min)))))

(defun pre-write-encode-utf8 (_ignored _ignored2)
  (encode-coding-region (point-min) (point-max) 'utf-8)
  (decode-coding-region (point-min) (point-max) 'latin-1))

上記をまとめて、zsh-history という符号化体系を作成し、これを zsh_history という文字列を含むファイルに結び付ける。

(define-coding-system 'zsh-history "ZSH history"
  :coding-type 'ccl
  :charset-list '(unicode)
  :mnemonic ?Z :ascii-compatible-p t
  :eol-type 'unix
  :ccl-decoder 'zsh-history-decoder
  :post-read-conversion 'post-read-decode-utf8
  :ccl-encoder 'zsh-history-encoder
  :pre-write-conversion 'pre-write-encode-utf8)

(modify-coding-system-alist 'file "zsh_history" 'zsh-history)

なお、Emacsは保存する際に最適な符号化を方法を見つけて再割当てを行うため、 一旦、この符号化方式でファイルを保存した後は再度、ファイルを読みなおす 必要がある。