eshell
香港・マカオの旅が台風で中止になって残念な思いをした義姉さん。リベンジで2度目のベトナムにチャレンジしてきたとか。土産を頂いた。餞別もあげなかったのに、気を使って頂いてありがとう。
ベトナムコーヒーのセット。挽いた豆が250g、アルミ製のフィルターがセットになったやつ。もう、これだけでエスニックですよ。所で、どうやってコーヒーを淹れるの?
ベトナムコーヒーの淹れ方って、聞いてみた。
なる程、2重フィルター式なのね。半永久的に使えるってのがエコでいいな。たっぷりの豆を入れるのがこつらしいけど、初回はちょっと薄かった。とか言ってたら、女房が新手法を編み出した。
そんなの麦茶と一緒で、鍋で煮出しちゃえばいいのよ。茶漉しでフィルターして、美味しいのが入りましたよ。あんたは、コーヒーバリスタか? 既成概念に捕らわれないのが女房流。何事もチャレンジが大事ってか?
eshell
カスタマブルな Emacs Lisp 製シェル「eshell」これ、るびきちさんの記事。るびきちさんって、あのruby大好きさんだよねとか、過去を懐かしみながら読んでます。
起動するには、M-x eshell ってするんだな。
Welcome to the Emacs shell /tmp $ pwd /tmp /tmp $ (+ 1 2 3 4) 10 /tmp $ + 1 2 3 4 10
カッコを省ける所が、かっこいいな。まて、shellのコマンドでも、括弧を省けるぞ。
/tmp $ bc bc 1.06.95 Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc. This is free software with ABSOLUTELY NO WARRANTY. For details type `warranty'. 1 + 2 + 3 + 4 10 ;; EOFを送って終了するには、C-c C-d なんだな。要注意の事 /tmp $
幾つも+を入力させられるって、苦痛ですわ。ああ、eshellの終了は普通にexitね。
/tmp $ (dotimes (i (length obarray)) (prin1 (aref obarray i)) (terpri))
調子に乗ってobarrayの結果を表示しようとした。アレー形式になってるんだけど、リストと同じような扱いをしてくれると思って、最初は一つ覚えのdolistを使ったんだ。そしたらアレーって事になっちゃたんで、subr.elを漁ってdotimesを見つけ出したよ。
配列へのアクセスなんで、obarray[i]と書きたい所だけど、elispの作法に従って、aref関数を使ったよ。脳内変換が強化される事を希望する。
0 ert-run-tests-interactively pre-redisplay-functions cl-format eshell-needs-pipe-p separator-frame Info-check-pointer boron-theme
これが結果なんだな。後は、これをリダイレクトすればいいのか。 ちゃんとファイルは作られるものの、中身は空だなあ。もしやと思ってエラー出力も一緒にしてみたけど、やはり空だった。
/tmp $ echo Hello Elisp World >ew /tmp $ cat ew ("Hello" "Elisp" "World")/tmp $ echo and goodby java >>ew /tmp $ cat ew ("Hello" "Elisp" "World")("and" "goodby" "java")/tmp $
こういうのは、普通に出来ているんで、eshellの秘孔を突いたって事ですかな。
macro
上で使った、dotimesなマクロ、どんなコソコソをやってるか調べてみる。調べる対象は、quoteしておくのが肝心。
(macroexpand '(dotimes (i 10)(print i))) (let ((--dotimes-limit-- 10) (i 0)) (while (< i --dotimes-limit--) (print i) (setq i (1+ i))))
結果は、人様が読む必要無しって事で、最密表示される。んな訳で、オイラーが少々整理してあげたよ。名前が衝突しないように、ひねくれた変数名(--dotimes-limit-- なんて言う、知らない人からすると卒倒するような名前。lispは名前に関しては、制限が緩いので、これもりっぱな名前として扱える)を採用してるな。 特に変な事をやってる訳ではないので、安心した。
ついでに、dolistも展開してみるか。
(macroexpand '(dolist (e (garbage-collect)) (prin1 e) (terpri))) (let ((--dolist-tail-- (garbage-collect)) e) (while --dolist-tail-- (setq e (car --dolist-tail--)) (prin1 e) (terpri) (setq --dolist-tail-- (cdr --dolist-tail--))))
whileの条件句は、nilか否も使えるのね。
これらのマクロは、共にコレクション(配列、リスト)のイテレーターを実現してる。使うユーザー側からすれば、あたかもelispの文法?が拡張されたように見える。これぞ、マクロが持つ威力だ。久しぶりに、マクロの魔導書 On Lisp を眺めてみるかな。
macroじゃないけど、過去の資料を見ていたら、obarrayに登録されてる個数を正確にカウントする例が載ってた。
(let ((cnt 0)) (mapatoms '(lambda (x) (setq cnt (1+ cnt)))) cnt) 23800 (length obarray) 15121
随分違いが有るね。この違いって、ハッシュの衝突の頻度を表しているのかな? keyのhashを計算。衝突してたら、keyをリストに保存ってのが、よく使われる手法。
obarrayで出て来るのは、衝突していない数。これなら、直ぐに求まる。衝突してたら、リストを辿って、正確な数を求める。こちらがmapatomsで出て来る数(って予想)。詳しい事は、ソース嫁。
eshellの二枚舌
上で出て来た、obarrayの出力がリダイレクトされない件(勿論パイプも駄目)を、秘孔と考えないで、深堀してみる。
実行時に、画面が割れて、下側に結果の一部が出て来た。次のコマンドを入力すると、その画面は自動消滅。これが、観察結果である。
emacsでは、何かを表示する場合、必ずbufferに書き込むのが約束。ああ、bufferってのは、表示窓ってぐらいの意味だ。だから、一時的であれ、結果が出て来るって事は、何かのbufferに書き込んでいるはず。
こう思って、C-x C-b して、bufferのリストを出してみた所、*Messages* buffer が、異常に膨らんでいた。*Messages*を覗いてみると、oblistの結果が収録されてた。
そうか、そういう事か。実行結果は、*Messages* に書き込まれ、そのtail部分を、親切にeshell君が見せてくれてるのね。
そうなると、S式の実行結果が、eshellの画面(buffer)に出て来たり、*Messages*に出て来たりの使い分けが知りたい。
過去のLispの知識を総動員して、オイラーのAIに聞いてみた。して、その答えは?
副作用の結果は、*Messages* に行くんじゃよ。S式が返すやつは、eshell画面に出て来るんじゃよ。
何の事か? と思わる人向けに実験。
/tmp/emacs-26.1/src $ (concat "Hello" "Emacs") HelloEmacs
concat関数の結果(文字列の連結)が、eshell画面に表示された。
/tmp/emacs-26.1/src $ (dotimes (i 20) (print "Hello elisp")) /tmp/emacs-26.1/src $
dotimesの結果はnilなんで、表示無し。dotimesの副作用(で、print)の結果は、黙って、*Messages*に書き込まれた。そして、親切に、eshellが一部を表示してくれた。
"Hello elisp" "Hello elisp" "Hello elisp"
/tmp/emacs-26.1/src $ (let ((res "")) (dotimes (i 3) (setq res (concat res "Hello Elisp" "\n"))) res) Hello Elisp Hello Elisp Hello Elisp /tmp/emacs-26.1/src $
letでローカル変数resを作り、dotimesで文字列をresに連結。dotimesを抜けたら、結果としてresを返す。これで目出度く、eshell画面に結果が出てきた。
/tmp/emacs-26.1/src $ (let ((res "")) (dotimes (i 3) (setq res (concat res "Hello Elisp" "\n"))) res) | wc 3 6 36
結果がeshellの画面に出てくれば、しめたもの。上のように、パイプに繋いだり、リダイレクトも、おちゃのこさいさい、であーる。
秘孔と勝手に決めて、うっちゃっておかなくて良かったわーい。
このeshell、TABで補完が効くし、評価でエラーになっても、M-pで前のコードを呼び出して、C-nとかで、コード内を飛び回って、修正実行(が、RETで)出来るんで、気にいった。
スクラッチバッファーで、補完しようとすると、C-M-i なんて叩かないいけないので、少々うんざりしてましたからね。まあ、こういうキーバインドは即変更しちゃうって手もありますけど。
etag
gdbを使ってemacs内を観光するのもいいけど、もっとダイナミックに飛び回りたい。それには、やっぱりetagsでTAGファイルを作っておくのが良い。
emacsはetagsの本家。試用する為の機構が用意されているに違いない。
cent:emacs-26.1$ make tags cent:emacs-26.1$ find . -name TAGS ./lisp/TAGS ./src/TAGS ./lwlib/TAGS cent:emacs-26.1$ sudo cp lisp/TAGS /usr/local/share/emacs/26.1/lisp/
Makefileを点検する事なくターゲットを指定したら、出来上がっちゃったぞ。elisp用のものは、実環境にも配備しておいた。これで、ソース系を一式消してしまっても、安心かな?
Emacs C source dir: /usr/local/share/emacs/26.1/lisp/
と、思っていたら、elisp内をすいすい飛び回っていて、知らないでプリミティブ関数を指定しちゃうと、上記のようにソースは何処やって質問が出て来る。(ソースが入っていると、いつの間にか、C語の世界に切り替わっている。シームレスは有り難いんだけど、ちょいと案内が欲しいぞ。)
バイリンガルな人は、言語が違っても同時に聞き分けられるんですかね? 今のは日本語だな、今度は英語が聞こえてきたぞ、こういう経験は余りない。翻って、昔青森にスキーに行った時、骨休めで酸ヶ湯温泉に行った。 古老に話しかけられたけど、さっぱりでしたよ。日本語と思って聞いても、意味不。片言でいいから、英語でお願いしますってマジで思ったものだ。
少々脱線したけど、tagを作る時のは、コンパイルが終了した直後が良い。真っ新なソースが ある所でtagを作ろうとするとエラー、若しくは再コンパイルが走っちゃうから。 再コンパイルったって、1分少々で済むから、大した手間ではないけどね。
edebug
折角TAGSが出来たんで、eshell問題を解いてみたい。ええ、上で出て来た、副作用は*Messages*バッファーへ行っちゃうというやつね。何処かをごにょごにょしたら、表舞台のeshell画面に出てこないかなって、期待を込めて。
eshell関係のソース一式は、
/tmp/emacs-26.1/lisp/eshell $ ls *.el em-alias.el em-hist.el em-smart.el esh-cmd.el esh-opt.el em-banner.el em-ls.el em-term.el esh-ext.el esh-proc.el em-basic.el em-pred.el em-tramp.el esh-groups.el esh-util.el em-cmpl.el em-prompt.el em-unix.el esh-io.el esh-var.el em-dirs.el em-rebind.el em-xtra.el esh-mode.el eshell.el em-glob.el em-script.el esh-arg.el esh-module.el
総行数は13740行有った。docと称する説明文とかが大量に内包されてるから、実コードは、半分ぐらいでしょうかね。
何処から手を付けたらいいものかと気ままにzappingしてたら、面白いコメントを発見。
(defmacro eshell-trap-errors (object) "Trap any errors that occur, so they are not entirely fatal. : Someday, when Scheme will become the dominant Emacs language, all of this grossness will be made to disappear by using `call/cc'..."
これが、公式なdocの一部になってるって、どうよ? Schemeが優勢になるemacs。オイラーも切に願っておりますだ。
でも、 Emacs、Guile、Emacs Lispの未来を見ると、現実は厳しいような。UTFな文字コード一択に反対を唱えた殉教者のあの人みたいな心境だな。まあ、日本は漢字、ひらがな、カタカナ、ローマ字、英語と、屈託がない国ですからね。
沢山あるファイルをじっと見ると、頭にem-が付いたやつと、esh-が付いたものに分かれている。前者は多分elispで書かれたモジュールだろう。eshellの本体は後者と思われる。
そこで、後者に的を絞って、一番怪しそうなesh-cmd.elあたりを見る。unixライクなコマンドとS式の切り分けをやっているに違いない。
M-x occur RET (defun RET して、定義名をリストしてみる。
: 1223:(defun eshell-plain-command (command args) 1235:(defun eshell-exec-lisp (printer errprint func-or-form args form-p) 1303:(defun eshell-lisp-command (object &optional args)
lispと名前が付くやつが2つ見つかった。そのうちのeshell-exec-lispを眺めると、いかにも怪しそうな関数と思えてきた。
/tmp/emacs-26.1/lisp/eshell $ (progn (prin1 "HOGE") "lastval") lastval
最後の式と言うか文字列が出て来た。この時に*Messages*は、下記のようになってた。em- モジュール説はビンゴでしたね。良く観察して考察するが大事だな。
Making completion list... Mark set Loading em-alias...done : Loading em-unix...done "HOGE"
esh-cmd.elを表示させて、edebugを使うように関数を登録。eshell-exec-lispの所にカーソルを持って行って、C-u C-M-x するんだった。
それから、eshell画面に戻って、progn... を再実行。果たしてヒットしてくれるかな。
(defun eshell-exec-lisp (printer errprint func-or-form args form-p) "Execute a lisp FUNC-OR-FORM, maybe passing ARGS. PRINTER and ERRPRINT are functions to use for printing regular messages, and errors. FORM-P should be non-nil if FUNC-OR-FORM represent a lisp form; ARGS will be ignored in that case." (eshell-condition-case err (let ((result (save-current-buffer (if form-p => (eval func-or-form)★ (apply func-or-form args))))) (and result (funcall printer result)) result) (error :
カーソルが★の所での結果は、mini-bufferに Result: "lastval" って出たた。オイラーが与えたS式は評価が終わったって事だ。メッセージバッファーは
Result: (progn (prin1 "HOGE") "lastval") "HOGE" Result: "lastval"
このようにHOGEが送りこまれていた。他のmsgはedebugが出したものだろう。
はて、この後どうする?
debug
別のdebug方法も試してみる。 debugモードでお手軽にやるなら、止めたい関数を指定してから実行。
/usr/pkg/share/emacs/26.1/lisp/eshell $ (debug-on-entry 'eshell-exec-lisp) /usr/pkg/share/emacs/26.1/lisp/eshell $ (progn (prin1 "hoge") "LAST")
画面が割れて、バックトレース画面が出て来る。
Debugger entered--entering a function: * eshell-exec-lisp(eshell-print eshell-error (progn (prin1 "hoge") "LAST") nil $ eshell-lisp-command((progn (prin1 "hoge") "LAST")) eval((eshell-lisp-command '(progn (prin1 "hoge") "LAST"))) : command-execute(eshell-send-input)
dでステップ実行、cで継続するとな。(cancel-debug-on-entry)で、キャンセル出来る。
これで追っても、やはりevalした時点で、メッセージバッファに出力されちゃうんだよな。
gdb
prin1の主力がメッセージバッファに行っちゃうなら、その設定が何処かでなされているはず。 grep Message *el しても、引っかからず。
ならばCのソースレベルまで立ち返る必要が有るな。やおら、 prin1の説明を見る
(prin1 OBJECT &optional PRINTCHARFUN) Optional argument PRINTCHARFUN is the output stream, which can be one of these: - a buffer, in which case output is inserted into that buffer at point; - a marker, in which case output is inserted at marker's position; - a function, in which case that function is called once for each character of OBJECT's printed representation; - a symbol, in which case that symbol's function definition is called; or - t, in which case the output is displayed in the echo area.
optionで、schemeのport相当が使えるんだな。これがセットされていないか?
(gdb) b Fprin1 Breakpoint 3 at 0x8168050: file print.c, line 625. (gdb) c Continuing. Breakpoint 3, Fprin1 (object=XIL(0x99fc0fc), printcharfun=XIL(0)) at print.c:625 625 { (gdb) p object $1 = XIL(0x99fc0fc) (gdb) xpr Lisp_String $2 = (struct Lisp_String *) 0x99fc0f8 "HELLO" (gdb) p printcharfun $3 = XIL(0) (gdb) xpr Lisp_Symbol $4 = (struct Lisp_Symbol *) 0x846d8a0 <lispsym> "nil"
Uum 予想が外れたわい。説明によるとprintcharfunがnilの場合、出力はstdout送りになるそうな。
諦めて、eshellのelispレベルのバックトレースをgdbのbtの最後から拾ってみた。
Lisp Backtrace: "prin1" (0xbfdda030) "progn" (0xbfdda0cc) "eval" (0xbfdda19c) "eshell-exec-lisp" (0xbfdda3c0) "eshell-lisp-command" (0xbfdda5d0) "eval" (0xbfdda73c) "eshell-do-eval" (0xbfddab48) "eshell-do-eval" (0xbfddaf00) "condition-case" (0xbfddb0ac) "eval" (0xbfddb174) "eshell-do-eval" (0xbfddb530) "let" (0xbfddb6cc) "eval" (0xbfddb794) "eshell-do-eval" (0xbfddbba8) "eshell-do-eval" (0xbfddbf60) "catch" (0xbfddc0ec) "eval" (0xbfddc1b4) "eshell-do-eval" (0xbfddc5c8) "eshell-do-eval" (0xbfddc980) "let" (0xbfddcb1c) "eval" (0xbfddcbe4) "eshell-do-eval" (0xbfddcff0) "eshell-resume-eval" (0xbfddd1e0) "eshell-eval-command" (0xbfddd414) "eshell-send-input" (0xbfddd788) "funcall-interactively" (0xbfddd784) "call-interactively" (0xbfddd900) "command-execute" (0xbfdddb6c)
こちらは、参考までに、スクラッチでの実行時バックトレース結果。物の資料の説明を余す事なく再現してるな。
Lisp Backtrace: "prin1" (0xbfddd030) "progn" (0xbfddd0cc) "eval" (0xbfddd1b8) "elisp--eval-last-sexp" (0xbfddd39c) "eval-last-sexp" (0xbfddd56c) "eval-print-last-sexp" (0xbfddd788) "funcall-interactively" (0xbfddd784) "call-interactively" (0xbfddd900) "command-execute" (0xbfdddb6c)
scsh
eshellばかりがshellじゃ無い。記憶を頼りに探したら、見つかった。
nb8$ cat DESCR Scsh is a Unix shell in that is has significant syntax extensions to make writing Unix shell scripts easy (constructing pipelines, setting I/O redirection, conditional execution etc.). It also offers access to lower-level functionality like all Posix system calls, TCP/IP sockets and a full-featured regular expression library. This is embedded into a general-purpose programming language with real data types, extensive, syntactically clean control constructs and "real" quoting rules. Scsh is also a full implementation of R4RS Scheme with some non-standard behavior (required for scripting). As a result, a wide variety of existing Scheme code can be used. The underlying Scheme implementation is a virtual machine for compact byte code.
NetBSDに有った案内。以外に不人気みたいでFreeBSDにもウブにも収録されていなかった。 本山は、 scshだよ。 そして、使用例は SCSH(Scheme Shell)スクリプト入門に出てた。
後でDebianで試してみるか。