Bash history with multiple sessions

Today I spent a bit of time investigating a rather annoying buglet that seems to have shown up since Fedora 21. I’d been noticing, vaguely, that things I was sure should be in my bash history weren’t there.

So far we’re guessing the bug happens when you reboot with a terminal app running; it seems sometimes the active sessions don’t get time to write the history out to the bash history file before they’re killed. It may be related to this bug, though waiting to hear back from the systemd devs on that.

Anyway, while looking into bash’s behaviour in this area, I came across a nice snippet from Bradley Kuhn, and I’ve taken the liberty of adopting that (it’s GPL, right Bradley? :>) and tweaking it a bit:

HISTSIZE=1048576
HISTFILESIZE=1048576

LAST_HISTORY_WRITE=$SECONDS
function prompt_command {
    if [ $(($SECONDS - $LAST_HISTORY_WRITE)) -gt 60 ]; then
        history -a && history -c && history -r
        LAST_HISTORY_WRITE=$SECONDS
    fi
}

PROMPT_COMMAND="$PROMPT_COMMAND; prompt_command"

I saved this as /etc/profile.d/zz-happyassassin-history-safe.sh. There’s a /etc/profile.d/vte.sh which overwrites any previous PROMPT_COMMAND, so you need to make sure it’s alphabetically after that, and I have a convention of including happyassassin in the names of config files that are my own customizations, so I can conveniently identify them.

Basically, every time I run a command, if it’s been more than 60 seconds since the last time this happened, it saves the current session’s history to the history file (history -a), clears the current session’s history (history -c) and reads the history back in from the file (history -r).

This both protects against the problem where you lose commands from active sessions at shutdown, and means when you have lots of shells open at once (in tabs or windows or whatever), every 60 seconds each will pick up history from all the others – I get a bit annoyed when I have six terminals open and I realize I need something from the history of a different terminal but I can’t remember which one…

You can tweak the frequency by changing 60 to something else – Bradley’s snippet had 300 so it’d happen every 5 minutes, for testing I set it to 5. You could even drop the timer entirely and have it happen every time you run a command, for maximum safety (and to keep the order of the commands as accurate as possible, I guess). On an SSD at least, this happens so fast you don’t notice it – it might take a bit longer if you have the 11MiB history file discussed in the initial thread, so tweak to your desires.

I’ve been running this for a whole twenty minutes so far, so I’ll update this post if it turns out to cause terrible casualties or anything, but just thought I’d note it quickly first 🙂

EDIT: I’ve been using this since and generally like it, though it does have drawbacks. Reading the history from other consoles back into the current console regularly changes the order of the history – you can’t be sure that pressing ‘up’ three times will give you the third-last command from that console. Which is sometimes annoying. But overall, I’m happy with it.

5 Responses

  1. Dusty
    Dusty January 18, 2015 at 9:38 pm | | Reply
  2. Sergek
    Sergek February 8, 2015 at 7:54 am | | Reply

    Tried this snippet in ubuntu and bash didn’t like the semicolon in the last string. Don’t know why.
    Something like -bash: syntax error near unexpected token `;’

    1. Matyas
      Matyas February 16, 2015 at 8:10 am | | Reply

      If you don’t already have a $PROMPT_COMMAND (or $PROMPT_COMMAND is blank) by the time you get to the last line, then the first part will expand to an empty statement, i.e.
      PROMPT_COMMAND=”; prompt_command”
      and bash does not like empty statements.

      You could instead do something like:
      PROMPT_COMMAND=”${PROMPT_COMMAND:-:}; prompt_command”
      which will cause it to expand to “:” (the no-op statement) if $PROMPT_COMMAND is empty or undefined.

  3. Anon Hacker
    Anon Hacker September 6, 2016 at 11:44 pm | | Reply

    This is interesting, because I google’d for why /bin/bash has ‘session unique’ history files;
    yet my beloved /bin/ksh93 has ‘shared-for-one-user’ history files. It has pissed me off for years that I never understood WHERE that command went that I just ran 37 minutes ago…

    So you taught me something about bash scripting.

    However, rather than write and test and troubleshoot such a tortured string of bash code, might I introduce you to zfs on Solaris? My home directory (which contains my .sh_history) is snapshotted every 15 minutes, every hour, every day, every week, every month, every year – and are browseable as a simple timeline in GNOME 2 Nautilus (the good GNOME, you know… MATE?)

Leave a Reply

Your email address will not be published. Required fields are marked *