# Emacs Lispの文字列操作まとめ

Date: 2020-05-26
URL: https://s.conao3.com/blog/2020/8e5d61
Author: conao3 (Naoya Yamashita)

[元記事](http://emacs.g.hatena.ne.jp/kiwanami/20110809/1312877192)の[アーカイブ](https://web.archive.org/web/20150325073218/http://emacs.g.hatena.ne.jp/kiwanami/20110809/1312877192)のアーカイブ。なにか問題があれば教えて下さい。以下、アーカイブ。

---

基本的にはEmacs LispのInfoを読むと何となくパーツはそろっていることは分かる。
ただ、パーツが特殊だったりしてよく使い方を忘れるのでよく見る情報をメモ。

# 基本的情報源

以下の情報はローカルにブックマークするなどして即引けるようにしておくと便利。

-   Info 4 Strings and Characters
    -   4. 文字列と文字
    -   文字列の操作、比較、変換など
-   Info 32.2 Examining Buffer Contents
    -   31.2バッファの内容を調べる
    -   バッファの文字列取得
-   Info 32.19 Text Properties
    -   31.19テキスト属性
    -   テキストプロパティ（テキスト属性）のいろいろ
-   Info 34 Searching and Matching
    -   33. 探索と一致
    -   探索、正規表現
-   Info 38.10 Width
    -   38.9表示幅
    -   非ASCII文字列を含む文字や文字列の幅（画面上の長さ）、端っこ切る
-   逆引きEmacs Lisp文字列
    -   それなりに数が多い
-   no title
    -   実は必要なことはここに大体書いてあったりする

# Rubyとの文字列操作対応表

参照: 文字列操作の比較表: Ruby, Python, JavaScript, Perl, C++ - bkブログ
基本的に破壊的操作は無し。

<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">

<colgroup>
<col  class="org-left" />

<col  class="org-left" />

<col  class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">Ruby (String)</th>
<th scope="col" class="org-left">Emacs Lisp</th>
<th scope="col" class="org-left">注釈</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">s = "abc"</td>
<td class="org-left">(setq s "abc")</td>
<td class="org-left">変数にバインドするならlet使う方が一般的</td>
</tr>

<tr>
<td class="org-left">s = x + y</td>
<td class="org-left">(concat x y)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s == x</td>
<td class="org-left">(equal s x)</td>
<td class="org-left">string-equalが正式</td>
</tr>

<tr>
<td class="org-left">s % [x, y]</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">sprintf(s, x, y)</td>
<td class="org-left">(format s x y)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">[x, y, z].join(s)</td>
<td class="org-left">(mapconcat 'identity (list x y z) s)</td>
<td class="org-left">単に結合するなら (apply 'concat (list x y z)) もアリ</td>
</tr>

<tr>
<td class="org-left">s.capitalize</td>
<td class="org-left">(capitalize s)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.capitalize!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.center(x)</td>
<td class="org-left">(cfw:render-center x s)</td>
<td class="org-left">calfw.el参照</td>
</tr>

<tr>
<td class="org-left">s.chomp</td>
<td class="org-left">(regexp-in-string "[\n\r]+$" "" s)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.chomp!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.chop</td>
<td class="org-left">(substring s 0 -1)</td>
<td class="org-left">改行文字の扱いが違う</td>
</tr>

<tr>
<td class="org-left">s.chop!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.clear</td>
<td class="org-left">(clear-string s)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.concat(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.count(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">ループで回すか、一時バッファを使ってre-search-forwardで回数を数えるとか</td>
</tr>

<tr>
<td class="org-left">s.crypt(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">rot13-stringとか、EPAとか</td>
</tr>

<tr>
<td class="org-left">EzCrypt</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.delete!(x)</td>
<td class="org-left">(regexp-in-string x "" s)</td>
<td class="org-left">破壊的には行えない</td>
</tr>

<tr>
<td class="org-left">s.downcase</td>
<td class="org-left">(downcase s)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.downcase!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.each_byte { x &hellip; )</td>
<td class="org-left">(loop for x across s do … )</td>
<td class="org-left">文字単位でループ、xは文字コード（数値）文字コードからstringにするにはchar-to-string</td>
</tr>

<tr>
<td class="org-left">s.each_line { x &hellip; )</td>
<td class="org-left">(loop for x in (split-string s "[\n\r]") do … )</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.empty?</td>
<td class="org-left">(= 0 (length s))</td>
<td class="org-left">nullチェックが必要</td>
</tr>

<tr>
<td class="org-left">s.end_with?(x)</td>
<td class="org-left">(string-match (concat x "$") s)</td>
<td class="org-left">sに改行が入らないことが前提</td>
</tr>

<tr>
<td class="org-left">s.gsub(x, y)</td>
<td class="org-left">(regexp-in-string x y s)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.gsub!(x, y)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.hex</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.to_i(16)</td>
<td class="org-left">(string-to-number s 16)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.include?(x)</td>
<td class="org-left">(string-match x s)</td>
<td class="org-left">もし見つかれば開始位置が返ってくる</td>
</tr>

<tr>
<td class="org-left">s.index(x)</td>
<td class="org-left">(string-match x s)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.insert(i, x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">一時バッファでやるか、自分でちぎってつなげるか</td>
</tr>

<tr>
<td class="org-left">s.intern</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.to_sym</td>
<td class="org-left">(intern s)</td>
<td class="org-left">シンボルテーブルに登録したくなければmake-symbol</td>
</tr>

<tr>
<td class="org-left">s.length</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.size</td>
<td class="org-left">(length s)</td>
<td class="org-left">文字数が返る</td>
</tr>

<tr>
<td class="org-left">s.ljust(x)</td>
<td class="org-left">(cfw:render-left x s)</td>
<td class="org-left">calfw.el参照</td>
</tr>

<tr>
<td class="org-left">s.lstrip</td>
<td class="org-left">(regexp-in-string "^[ \t]+" "" s)</td>
<td class="org-left">sに改行が入らないことが前提</td>
</tr>

<tr>
<td class="org-left">s.lstrip!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.match(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">x.match(s)</td>
<td class="org-left">(string-match x s)</td>
<td class="org-left">マッチ結果はグローバルに保持しているmatch-string, match-beginning, match-end等参照</td>
</tr>

<tr>
<td class="org-left">s.next!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.succ!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.oct</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.to_i(8)</td>
<td class="org-left">(string-to-number s 8)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.partition(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">string-matchで自分でちぎる</td>
</tr>

<tr>
<td class="org-left">s.replace(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.reverse</td>
<td class="org-left">(apply 'string (reverse (append s nil)))</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.reverse!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.rindex(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">一時バッファでsearch-backward等を使う</td>
</tr>

<tr>
<td class="org-left">s.rjust(x)</td>
<td class="org-left">(cfw:render-right x s)</td>
<td class="org-left">calfw.el参照</td>
</tr>

<tr>
<td class="org-left">s.rpartition(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">string-matchで自分でちぎる</td>
</tr>

<tr>
<td class="org-left">s.rstrip</td>
<td class="org-left">(regexp-in-string "[ \t]+$" "" s)</td>
<td class="org-left">sに改行が入らないことが前提</td>
</tr>

<tr>
<td class="org-left">s.rstrip!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.scan(x) { &hellip; }</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">一時バッファでre-search-forward等を使う</td>
</tr>

<tr>
<td class="org-left">s[i]</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.slice(i)</td>
<td class="org-left">(aref s i)</td>
<td class="org-left">文字コード（数値）が返ってくる文字コードからstringにするにはchar-to-string</td>
</tr>

<tr>
<td class="org-left">s[i..-1]</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.slice(i..-1)</td>
<td class="org-left">(substring s i)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s[i, l]</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.slice(i, l)</td>
<td class="org-left">(substring s i (+ i l))</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s[i..j]</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.slice(i..j)</td>
<td class="org-left">(substring s i (1+ j))</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s[i&hellip;j]</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.slice(i&hellip;j)</td>
<td class="org-left">(substring s i j)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.split(x)</td>
<td class="org-left">(split-string s x)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.start_with?(x)*4</td>
<td class="org-left">(string-match (concat "^" x) s)</td>
<td class="org-left">sに改行が入らないことが前提</td>
</tr>

<tr>
<td class="org-left">s.strip</td>
<td class="org-left">(regexp-in-string "\\(^[ \t\n\r]+\\  [ \t\n\r]+$\\)" "" s)</td>
<td class="org-left">sに改行が入らないことが前提</td>
</tr>

<tr>
<td class="org-left">s.strip!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.sub(x, y)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">string-matchを使って自分で初回を取ってくる</td>
</tr>

<tr>
<td class="org-left">s.sub!(x, y)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.swapcase</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.to_f</td>
<td class="org-left">(string-to-number s)</td>
<td class="org-left">整数に見える場合は整数が返る</td>
</tr>

<tr>
<td class="org-left">s.to_i</td>
<td class="org-left">(string-to-number s)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.to_s</td>
<td class="org-left">(substring s 0)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.tr!(x, y)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.tr_s!(x, y)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.unpack(x)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">bindatを使う</td>
</tr>

<tr>
<td class="org-left">s.upcase</td>
<td class="org-left">(upcase s)</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">s.upcase!</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">Regexp.escape(s)</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left">Regexp.quote(s)</td>
<td class="org-left">(regexp-quote s)</td>
<td class="org-left">&nbsp;</td>
</tr>
</tbody>
</table>

※もし間違いを見つけたら教えてください。

## 表示幅について

formatの桁指定（"%5s" など）で表示幅のパディングが出来るが、あふれた文字列は切り落とされずに全部表示されてしまう。

指定幅で切り取りたい場合は、自分であらかじめ文字列の長さを見て切り落と(truncate)しておく必要がある。

切り落としにはsubstringとかではなくて、全角文字などについても適切に幅を見てくれるtruncate-string-to-widthを使うと良い感じ。

## 文字列内の複雑な検索、置換など

replace-regexp-in-stringを使えば大抵のことが出来る。関数も取れるのでRubyのgsub相当も出来る。

ただ、複雑な正規表現をこねくり回したい場合は以下のように一時バッファを使う。

```emacs-lisp
;; input に文字列が入っている
;; output に変換後の文字列を入れたい
(setq output
  (with-temp-buffer
    (insert input)
    (goto-char (point-min))
    (while (re-search-forward REGEXP nil t)
        ;; ここで match-string などを使って何かする
     )
    (buffer-string)))
```

## テキストプロパティについて

HTMLのDOMオブジェクトに独自属性を付加するように、自由に属性を入れることが出来る。
HTMLとは違って1文字単位でプロパティが設定できるが、大抵はまとまった領域にプロパティを設定する。

テキスト属性を使えるようになると、かなり複雑なアプリケーションが作れるようになる（と思う）。

## アプリケーションからの利用

テキストプロパティに必要な値をごっそり入れておき、キー操作でカーソル上の文字列からテキストプロパティを取ってきて必要なアクションを行うというような方法が一般的に行われている。大抵は、行全体や必要な領域全体にプロパティをつけておいて、キー操作で取得するときは行頭の1文字目のプロパティを見るということがよく行われている。

Diredやorgmodeなど、多くのプログラムがこのような設計になっている。しかしながら、場当たり的にテキストプロパティを使い始めると、テキストプロパティを舞台にしたスパゲティコードになるため、本格的に使う場合はあらかじめ構造化したデータを代表して入れておく等の工夫をした方がよいと考えている。

## はまりポイントなど

属性は文字列の操作などでもそのままコピーされてしまうので、欲しくない場合はsubstring-no-propertiesなどで消す。

属性の操作は破壊的なので、うっかり変えてはいけないところまで変えてしまうことがある。また、ソースコードリテラルの文字列オブジェクトには属性の操作ができない。なので、外から来た文字列に対して属性を操作する場合は、文字列をコピーしておくと安全。

font-lockが有効なバッファでfaceに何か入れても速攻でクリアされてしまう。そんなときはfont-lock-faceに入れる。

テキスト属性のループパターン
バッファの中の特定のテキストプロパティについてループを書きたいときがある。そんなときはnext-property-changeまたはnext-single-property-changeなどを使う。Infoのnext-property-changeにループのサンプルがある。

以下はfaceの付いている領域についてループするイディオム。

```emacs-lisp
(defun iterate-text-prop (prop func)
  (loop with pos = (point-min)
        for next = (next-single-property-change pos prop)
        for text-val = (and next (get-text-property next prop))
        while next do
        (when text-val
          (funcall func pos next text-val))
        (setq pos next)))

(iterate-text-prop 'face
  (lambda (begin end val)
    (and val (message ">> %S : %S" val (buffer-substring-no-properties begin end)))))
```

Nオーダーではあるけどもなかなか高速に動作するので、O(N^2) のアルゴリズムでなければ問題なさそう。
