15

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...

1 Answer 1

7

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))
5
  • Thanks, this looks great. Your first snippet is missing a quote at the end of the docstring though. Dec 7, 2015 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 Dec 7, 2015 at 16:10
  • @JosephGarvin done! Dec 7, 2015 at 17:24
  • 1
    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, 2016 at 22:59
  • The hook param for the add-hook call needs a leading ' I think. ie (add-hook 'eshell-pre-command-hook ...... )
    – Iain
    Nov 12, 2022 at 12:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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