Emacsをワンライナーとして使う


Emacsをワンライナーとして使う

この記事はEmacsのAdventCalandar2016の20日目の記事です。

前日の記事はkaz-yosさんのmu4eでメールでした。

この記事に触発されてこの記事を書くに至りました。
vimをパイプにする - 余白の書きなぐり

「Emacsだって同じことができるのでは?」と考えました


まず、Vimではパイプでテキストを渡していました。1

Emacsでもパイプでテキストを渡せなければ話になりません。

そこで単純にパイプでテキストを渡したみた結果......

$ echo "Hello! \nOneLinerEmacs" > oneliner.txt # サンプルテキストの作成
$ cat oneliner.txt | emacs
emacs: standard input is not a tty

...

......

どうやら標準入出力が渡せないようです......

しかし、これで終わりにするわけにはいかないです。

「それなら、Emacsに標準入出力を渡すためのオプションがあるのでは」と考え、GNUのEmacsリファレンスを漁ってみました。

すると以下の記述を見つかりました。

GNU Emacs Manual: Initial Options

‘-batch’
‘--batch’
Run Emacs in batch mode. Batch mode is used for running programs written in Emacs Lisp from shell scripts, makefiles, and so on. To invoke a Lisp program, use the ‘-batch’ option in conjunction with one or more of ‘-l’, ‘-f’ or ‘--eval’ (see Action Arguments). See Command Example, for an example.

-batchというオプションをつけるとシェルスクリプトなどからEmacsLispを実行できるとのこと。
また、Action Argumentsというオプションを組み合わせることでいろいろできるとある。

GNU Emacs Manual: Action Arguments

そして、ご丁寧にサンプルまで用意してくれていました

GNU Emacs Manual: Command Argument Example

$ emacs --batch foo.c -l hack-c -f save-buffer >& log

Emacsでfoo.cを読み込み、hack-c.elを読み込みsave-bufferという関数を実行する。そしてlogというファイルに出力を行います。

-l-fはそれぞれ以下のことをやってくれます。

引数 概要
-l 指定されたElispをロードする。
-f 指定されたEmacsLispの関数を実行する。

他にも以下のようなオプションが使えそうです。

引数 概要
--eval 引数に指定したElispを実行する。

ここまでを踏まえてoneliner.txtの中身をlogに出力するという単純なものをまず書いてみます……

$ emacs --batch oneliner.txt --eval="(princ (buffer-string))" >& log
$ cat log
Hello 
OneLinerEmacs

これでようやくEmacsでテキスト処理をワンライナーでおこなう準備ができました。

けれども、この形ではcatechoなどをパイプで渡した標準入出力を渡すことができないという問題があります。

それはbuffer-stringが現在のバッファの内容を文字列として返すするという関数だからです。

そこでElispで標準入出力を受け取ってバッファに入力し、最後にそのバッファを出力するという関数を書いてみます。

elisp.el
(defun get-stdin ()
(let ((buf (generate-new-buffer "buf")))
    (set-buffer buf)
    (condition-case nil
        (let ((line))
          (while (setq line (read-string ""))
            (insert line "\n")))
      (error nil))
     (buffer-string)))

こうなるだろうでしょうか。

1行だけだったら(read-string "")でも受け取れるのだけれども、複数行を受け取るためにwhileで回している。
また、condition-caseはEOFによるエラーの対策です。

これを実際に使って「文字列の置き換えをElispでおこない、最後に置き換えた行をgrepで抽出する」というのをやってみます。

まずは適当に単語を並べたテキストファイルを作ります。

hello.txt
Go
Hello
Yeah
NewYear
OneLiner

$ cat hello.txt | emacs --batch -l ~/.emacs.d/elisp/get-input.el --eval='(princ (replace-regexp-in-string "Hello" "Go" (get-stdin)))' | grep Go
Go
Go

これでEmacsでワンライナーが書けることがわかりましたよね……?

普段利用する関数をユーティリティ関数としてElispとして書いておけば、Emacs上でもBash上でも使うことができるようになるというわけです!

いやあ、Emacsはまるでスイスアーミーナイフですね!

・・・・・・

ここまで書いておいて、最後にアレですが……

ここまでしてElispをコマンドラインで使いたいならeshellを使いますよね……。

ここまでで「面白そう!」と思ってくれた方はElispを実行できるシェル環境であるeshellを使うことをオススメします……。


修正:直したはずの箇所が修正できていなかったので修正を加えました。編集履歴が無駄に増えてしまってすいません……。


  1. -eオプションをつけないとダメなようです。