16

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?

DrBeco
  • 774

1 Answers1

15

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.

Michael Homer
  • 76,565
  • I wonder if you enabled time stamping within the .bashrc file, and then had something like a cronjob come along and resort the file periodically, sending a SIGUSR1 as per you snippet, whether you could regain chronological up-arrow history? – forquare Jun 28 '15 at 09:15
  • It's a neat solution, but for the fact other processes terminate upon USR1. Does this behavior also happens with USR2? – DrBeco Jun 28 '15 at 17:49
  • I'll add a second comment, because I'm astonished. How come process associate TERM with USR1? Isn't USR1 something to be used only by the user?? Is this some linux bug?? Why other process are catching USR1? (I know its not meant to be discussed here, but maybe I'll bring this to light in the correct place) – DrBeco Jun 28 '15 at 17:53
  • The default behaviour of almost all signals is to terminate; it's specified in POSIX. It's not a bug. Bash will let you specify that the signal will be ignored for all its processes, though, so that's fine for our purposes. – Michael Homer Jun 28 '15 at 20:50
  • Thanks @MichaelHomer . Is this specification you meant done by BASH_ENV as stated in this answer? Or do you know a more standard way to turn off this evil? – DrBeco Jun 29 '15 at 03:47
  • Yes, I described how to disable it in the answer. – Michael Homer Jun 29 '15 at 05:16
  • Instead of on_exit() function, can I just include the code into .bash_logout? Its a trap less to handle. – DrBeco Jun 30 '15 at 00:13
  • To answer my own comment, because .bash_logout wont run in a terminal. Only in login shells. here – DrBeco Jun 30 '15 at 03:02
  • Now, I tested this script! Awsome! I just made 2 small changes: use USR2, because you never know when you are going to need to trap USR1. And an echo message right in the begin of function on_usr2() stating: echo 'I sense a disturbance in the force...'. Star wars rocks. And it will remind you that the history has changed, to be more carefull with a fast "up+enter" – DrBeco Jun 30 '15 at 03:06