シェル難民がeshellに漂流したついでに、 eshell tipsまとめてみた

NO IMAGE

こんばんわ、myuhe a.k.a シェルを揺蕩う旅人 です。

さて、皆さんシェルは何をお使いでしょうか?

bash? csh? zsh?

メインエディタと同じくらい意見が分かれるものに、シェルがあるかと思います。かくいう僕も、エディタについてはEmacsばかりですが、シェルに関してはいろいろと試行を繰り返してきました。

シェルに関してはEmacsと合わせて使うとなれば、Emacsの中で動かすか、外で動かすかという選択肢も出てくるので、いろいろと悩まねばなりません。

しばらく、Emacsのansi-termでzshというのが定着していたのですが、最近使いはじめたauto-fu.zshの相性があまりよろしくなく、このところはmlterm上でzshを動かしてました。

では、それで満足かというといろいろと不満もあって、mltermとEmacsの連携がうまくないとか、そもそもEmacsから出ずに渡世したいとか、いろいろあるわけです。

そんな感じで悶々としていた時にeshellの存在を思いだしました。デフォルトの状態では、とても使える代物でもありませんが、そこはEmacs、いろいろと手を加えることで格段に便利になりました。

というわけで、まだまだeshell道半ばの段階ですが、eshell周りで施した諸々をまとめておくことにします。コードは.emacsあたりに貼ってお使いください。

プロンプトの変更

何事も見た目重要です。まずはプロンプトから変えてみます。長たらしいパスとかだと格段に見にくくなるので、パスの後に改行を入れ、シェルの記号も変えてお茶目な印象に

(defun my-eshell-prompt ()
(concat (eshell/pwd) "n→ " ))

(setq eshell-prompt-function 'my-eshell-prompt)

(setq eshell-prompt-regexp "^[^#$n]*[#→] ")

こんな感じになります。

https://i0.wp.com/cacoo.com/diagrams/D85Mnj5qnKif3HX0-2FA1F.png?w=1100&ssl=1

sudoの後でコマンドを補完

メインマシンがUbuntuなので、sudoを多用します。zshだとsudoの後もコマンドを補完してくれるのですが、eshellの場合、sudoの後でTABを何度叩いても反応がないので悲しくなります。

というわけで、sudoの後でもコマンドを補完できるようにしてみました。

(defun pcomplete/sudo ()
  "Completion rules for the `sudo' command."
  (let ((pcomplete-help "complete after sudo"))
    (pcomplete-here (pcomplete-here (eshell-complete-commands-list)))))

ブックマークしているディレクトリをeshell上に展開

ネタ元はいつもお世話になっているEmacs wiki

EmacsWiki: Eshell Bmk

bmkと入力した後に、ブックマークしているディレクトリのブックーマーク名を入力するとそのパスがカレントディレクトリになります。当然補完もできて快適。こういったワザはeshellならではだなーと思いました。

;; eshell/bmk - version 0.1.3

(defun pcomplete/eshell-mode/bmk ()
  "Completion for `bmk'"
  (pcomplete-here (bookmark-all-names)))

(defun eshell/bmk (&rest args)
  "Integration between EShell and bookmarks.
For usage, execute without arguments."
  (setq args (eshell-flatten-list args))
  (let ((bookmark (car args))
        filename name)
    (cond
     ((eq nil args)
      (format "Usage: 
* bmk BOOKMARK to
** either change directory pointed to by BOOKMARK
** or bookmark-jump to the BOOKMARK if it is not a directory.
* bmk . BOOKMARK to bookmark current directory in BOOKMARK.
Completion is available."))
     ((string= "." bookmark)
      ;; Store current path in EShell as a bookmark
      (if (setq name (car (cdr args)))
          (progn
            (bookmark-set name)
            (bookmark-set-filename name (eshell/pwd))
            (format "Saved current directory in bookmark %s" name))
        (error "You must enter a bookmark name")))
     (t
       ;; Check whether an existing bookmark has been specified
       (if (setq filename (cdr (car (bookmark-get-bookmark-record bookmark))))
           ;; If it points to a directory, change to it.
           (if (file-directory-p filename)
               (eshell/cd filename)
             ;; otherwise, just jump to the bookmark 
             (bookmark-jump bookmark))
         (error "%s is not a bookmark" bookmark))))))

eshellでauto-completeを使う

実は、これが結構苦戦してしまってなかなか思うように動作しません。原因は、eshellで補完に使われているpcompleteが文脈によって動作がいろいろと異なるせいで、下のコードだと、シンボルの補完とかで変な動作をします。ハックしていただける方募集中です。

(add-to-list 'ac-modes 'eshell-mode)

;;pcomplete-parse-argumentsの一部を削除しただけ
(defun ac-pcomplete-parse-arguments (&optional expand-p)
  "Parse the command line arguments.  Most completions need this info."
  (let ((results (funcall pcomplete-parse-arguments-function)))
    (when results
      (setq pcomplete-args (or (car results) (list ""))
        pcomplete-begins (or (cdr results) (list (point)))
        pcomplete-last (1- (length pcomplete-args))
        pcomplete-index 0
        pcomplete-stub (pcomplete-arg 'last))
      (let ((begin (pcomplete-begin 'last)))
    (if (and pcomplete-cycle-completions
         (listp pcomplete-stub) ;??
         (not pcomplete-expand-only-p))
        (let* ((completions pcomplete-stub) ;??
           (common-stub (car completions))
           (c completions)
           (len (length common-stub)))
          (while (and c (> len 0))
        (while (and (> len 0)
                (not (string=
                  (substring common-stub 0 len)
                  (substring (car c) 0
                         (min (length (car c))
                          len)))))
          (setq len (1- len)))
        (setq c (cdr c)))
          (setq pcomplete-stub (substring common-stub 0 len)
            pcomplete-autolist t)
          (throw 'pcomplete-completions completions))
      (when expand-p
        (if (stringp pcomplete-stub)
          (if (and (listp pcomplete-stub)
               pcomplete-expand-only-p)
          ;; this is for the benefit of `pcomplete-expand'
          (setq pcomplete-last-completion-length (- (point) begin)
            pcomplete-current-completions pcomplete-stub)
        (error "Cannot expand argument"))))
      (if pcomplete-expand-only-p
          (throw 'pcompleted t)
        pcomplete-args))))))

(defun ac-pcmpl ()
  "Return a list of completions for the current argument position."
  (catch 'pcomplete-completions
    (when (ac-pcomplete-parse-arguments pcomplete-expand-before-complete)
      (if (= pcomplete-index pcomplete-last)
      (funcall pcomplete-command-completion-function)
    (let ((sym (or (pcomplete-find-completion-function
            (funcall pcomplete-command-name-function))
               pcomplete-default-completion-function)))
      (ignore
       (pcomplete-next-arg)
       (funcall sym)))))))

(ac-define-source pcomplete
  '((candidates . ac-pcmpl)))

(defun my-ac-eshell-mode ()
  (setq ac-sources
        '(ac-source-pcomplete
          ;;ac-source-words-in-buffer
          ac-source-dictionary)))


(add-hook 'eshell-mode-hook
          (lambda ()
            (my-ac-eshell-mode)))

eshellはzshを越えられるか

いろいろと書きましたが、正直なところまだzshの方が使いやすいです。悲しいことにeshell関連の資料がとっても少なく、結局コード読む羽目になるため、zshを越える環境になるのはまだ先になりそうです。つか、zshすごすぎます。

ただ、潜在的にはzshに到底できないこともeshellではできます。膨大なelisp資産を使役できるというのはeshellの最大の魅力だと思います。もっと早くeshellの良さを理解できていればなーとちょっぴり後悔しています。

というわけで、これを読んでいただいた方が少しでもeshellに興味をもってもらえたのであれば、嬉しい限りです。