Before switching to eshell I had zsh setup so that it would:

  • Write out to the history file after every command
  • Append to rather than overwrite the history file so when running multiple shells they would all get merged into one big history

Both zsh options are documented here (see APPEND_HISTORY and INC_APPEND_HISTORY).

This is super useful when combined with a large history size because you can open a new shell weeks after issuing a command and find it in your history (without these options a large history is useless since it will only contain the history of the most recently closed shell). It also means you can open new shells and have them immediately know your most recent commands in other shells. Is there any way to setup eshell with this behavior? The first bullet seems easy enough but appending looks trickier...

up vote 3 down vote accepted

Disclaimer: I don't use eshell, so take this with a grain of salt.

eshell appears to call eshell-write-history to write history, which takes an optional argument append which defaults to nil. This argument seems to be unused in eshell presently, but does appear to work (it passes the argument through to write-region, which does properly append).

There are a couple of options here.

  1. (setq eshell-save-history-on-exit nil) and call eshell-write-history yourself
  2. Redefine eshell-write-history to satisfy your requirement.

Personally, I'd go with 1.

As an example:

(setq eshell-save-history-on-exit nil)
(defun eshell-append-history ()
  "Call `eshell-write-history' with the `append' parameter set to `t'."
  (when eshell-history-ring
    (let ((newest-cmd-ring (make-ring 1)))
      (ring-insert newest-cmd-ring (car (ring-elements eshell-history-ring)))
      (let ((eshell-history-ring newest-cmd-ring))
        (eshell-write-history eshell-history-file-name t)))))
(add-hook eshell-pre-command-hook #'eshell-append-history)

Thanks to @joseph-garvin for the corrected, working eshell-append-history function

This doesn't handle dynamically loading the new history contents into a shell (eg run command X in shell A, and having it appear in history in shell B without reloading; like zsh's SHARE_HISTORY). I don't know how efficient eshell-read-history is, so I'd be hesitant to run it in a hook.

It is also possible that you will end up with duplicate entries with this eshell-append-history function. You may need to do some shenanigans with clearing all but the most recent entry from eshell-history-ring, then resetting it to the old value after writing history.

E.g.

(let ((old-ring (copy-list eshell-history-ring)))
  (setq eshell-history-ring (list (car eshell-history-ring)))
  ; write
  (setq eshell-history-ring old-ring))
  • Thanks, this looks great. Your first snippet is missing a quote at the end of the docstring though. – Joseph Garvin Dec 7 '15 at 15:08
  • I had to edit it some, turns out rings need to modified by the ring-* functions or you run into errors. Also I use dynamic binding to temporarily override the definition of eshell-history-ring instead of making a copy. If you put the code here into your answer I'll mark as accepted since you got the ball rolling: pastebin.com/zEE7B6d7 – Joseph Garvin Dec 7 '15 at 16:10
  • @JosephGarvin done! – J David Smith Dec 7 '15 at 17:24
  • This solution works great, but I also had to set eshell-exit-hook to nil because this automatically gets loaded when eshell starts: (add-hook 'eshell-exit-hook 'eshell-write-history nil t). I set the hook to be locally nil like so (it is globally nil by default): (add-hook 'eshell-mode-hook '(lambda () (setq eshell-exit-hook nil))) – GDP2 Jul 18 '16 at 22:59

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.