※オチがあります
問題
Emacs のもっとも基本的なファイル出力関数である write-region
は、保存時に Wrote <filename>.
というメッセージを表示してしまうので、 Emacs アプリやスクリプト内部でファイル出力を扱う場合にはうるさくなってしまうことがあります。
(write-region (point-min) (point-max) "file")
他のファイル保存関数も、内部で write-region
を利用しているので同様です。
だめだったアイデア
1. with-temp-message
ミニバッファの表示を固定しながらフォームを評価する、 with-temp-message
というマクロがあるので、基本的にはこれで足ります。
(with-temp-message ""
(let ((message-log-max nil))
(write-region (point-min) (point-max) "file")))
ただこれはたんに表示を固定するだけで、メッセージを表示するルーチン自体は呼ばれているので、たとえば emacs --script
や emacs --batch
でターミナルから Emacs が実行されている場合は、普通に stderr に Wrote <filename>.
が出力されてしまいます。
2. message 関数を再定義
write-region
が内部で message
関数を呼んでいるなら、 message
関数を一時的に再定義してしまえばメッセージを表示させなくすることができそうです
(flet ((message (&rest _) nil))
(write-region (point-min) (point-max) "file"))
flet
が obsolete な最近のバージョンでは
(let ((orig-message-fn (symbol-function 'message)))
(unwind-protect
(progn (fset 'message (lambda (&rest _) nil))
(write-region (point-min) (point-max) "file"))
(fset 'message orig-message-fn)))
結果は、だめでした。なぜなら、 write-region
は C のコアレベルで定義されている関数なので、そもそも Emacs Lisp の世界の message
を呼んでいないからです。
余談:
関数再定義で挙動を変えられるのは動的束縛の強みです。個人的に Emacs Lisp は動的束縛であり続けて欲しいですし、 flet
を削除するのは賛成できないです。
3. シェルコマンドを呼ぶ
emacs には、指定した範囲の文字列を標準入力としてシェルコマンドを呼び出す shell-command-on-region
という関数があります。これを使ってファイルを保存すれば Emacs のメッセージは表示されません。
(shell-command-on-region (point-min) (point-max) (format "cat > file"))
Unix の場合これでうまくいくのですが、この方法だとバッファの coding-system が反映されないので、 windows や mac を使っているとたとえば CRLF で保存されてしまったりします。
うまくいった方法
どうにもうまくいかなかったので、 Emacs のソースを見てみました。
ファイル IO 関連の関数は src/fileio.c
にあります。 write_region
の実装を読んでみると、一番下にメッセージを表示している箇所が見つかります。
Lisp_Object
write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
Lisp_Object append, Lisp_Object visit, Lisp_Object lockname,
Lisp_Object mustbenew, int desc)
{
...
...
...
if (!auto_saving && !noninteractive)
message_with_string ((NUMBERP (append)
? "Updated %s"
: ! NILP (append)
? "Added to %s"
: "Wrote %s"),
visit_file, 1);
return Qnil;
}
よく見てみると、メッセージを表示する条件に if (!auto_saving && !noninteractive)
とあります。その手があった!
というわけで、 auto-save
機構を悪用してファイルを保存するこのコードに行き着きました。これはうまくいきます。
(let ((buffer-auto-save-file-name "file"))
(do-auto-save t t))
自動保存ファイルの向き先を一時的に目当てのパスに let
で上書きして、 do-auto-save
で自動保存をトリガーします。 do-auto-save
の第二引数に non-nil
な値を渡すとカレントバッファだけが対象になるので、それを利用すれば任意のバッファを任意のパスに、「自動保存機構を経由して」保存できます。
追記
write-region
の第5引数に nil
でも t
でもない値を明示するとメッセージ出ないらしい。がびーん。
(write-region (point-min) (point-max) "file" nil 'nomsg)