Bash – Update Command History on Other Terminals When Exiting

bashcommand historyshellsignals

I know this question is not obscure, as it is asked here keep updating (and duplicated here).

What I'm trying to achieve is a bit different. I don't like the idea of my prompt rewriting a file every ls I type (history -a; history -c; history -r).

I would like to update the file on exit. That's easy (actually, default), but you need to append instead of rewriting:

shopt -s histappend

Now, when a terminal is closed, I would like to make all others that remain open to be aware of the update.

I prefer to do this without checking via $PS1 on every command that I type. I think it would be better to capture some sort of signal. How would you do that? If not possible, maybe a simple cronjob?

How can we solve this puzzle?

Best Answer

Creative and involving signals, you say? OK:

trap on_exit EXIT
trap on_usr1 USR1

on_exit() {
    history -a
    trap '' USR1
    killall -u "$USER" -USR1 bash
}

on_usr1() {
    history -n
}

Chuck that in .bashrc and go. This uses signals to tell every bash process to check for new history entries when another one exits. This is pretty awful, but it really works.


How does it work?

trap sets a signal handler for either a system signal or one of Bash's internal events. The EXIT event is any controlled termination of the shell, while USR1 is SIGUSR1, a meaningless signal we're appropriating.

Whenever the shell exits, we:

  • Append all history to the file explicitly.
  • Disable the SIGUSR1 handler and make this shell ignore the signal.
  • Send the signal to all running bash processes from the same user.

When a SIGUSR1 arrives, we:

  • Load all new entries from the history file into the shell's in-memory history list.

Because of the way Bash handles signals, you won't actually get the new history data until you hit Enter the next time, so this doesn't do any better on that front than putting history -n into PROMPT_COMMAND. It does save reading the file constantly when nothing has happened, though, and there's no writing at all until the shell exits.


There are still a couple of issues here, however. The first is that the default response to SIGUSR1 is to terminate the shell. Any other bash processes (running shell scripts, for example) will be killed. .bashrc is not loaded by non-interactive shells. Instead, a file named by BASH_ENV is loaded: you can set that variable in your environment globally to point to a file with:

trap '' USR1

in it to ignore the signal in them (which resolves the problem).

Finally, although this does what you asked for, the ordering you get will be a bit unusual. In particular, bits of history will be repeated in different orders as they're loaded up and saved separately. That's essentially inherent in what you're asking for, but do be aware that up-arrow history becomes a lot less useful at this point. History substitutions and the like will be shared and work well, though.

Related Question