sqlite-history.sh Buy me a Coffee GitLab Repository menu close

sqlite-history.sh

sqlite-history.sh is a handcrafted complete replacer for builtin shell fc, history, and Ctrl+R interactive search.

Why use sqlite-history.sh?

sqlite-history.sh is a ground-up reimagining of how shell history can, and arguably should function in the modern day. Most popular shells have been around since the 1970s and 1980s, and their history mechanics are based on the typical use case for the time.

sqlite-history.sh is designed with the knowledge that these days people rarely ever have just a single shell open. Most people will have multiple shells open simultaneously; whether in separate windows, multiple terminal tabs, or a terminal multiplexer such as tmux or screen.

In order to support this, sqlite-history.sh makes use of a sqlite database file which each terminal writes to as commands are run.

Pros & Cons (compared to builtin shell history)

Pros

  • Commands are added to the database as you run them, instead of waiting for shell exit and clobbering the history file.
  • Old commands aren't dropped from history as new ones are added due to arbitrary size limits.
  • All shells configured to use sqlite-history.sh can search the database via Ctrl+R and read/execute commands that were run in other shells without needing to reload history and lose their live history order.
  • There is a distinction between live history and database history ignore lists, as opposed to a single HISTIGNORE which applies to both, allowing you to maintain new commands in live history but not in the database, and vice-versa.
  • Re-running a given command increments the run count in the database instead of adding a new entry, meaning for large history files the equivalent database is smaller, and more unique commands can be loaded into a new shell.
  • A given command's last run time is always saved regardless of whether HISTTIMEFORMAT is specified, giving more meaningful output when running history when HISTTIMEFORMAT is set, but was not for some commands.
  • Advanced match expressions are available for various operations, including but not limited to history ignore entries.
  • Each shell configured to use sqlite-history.sh effectively shares history.

Cons

  • Requires sqlite3 to be installed.
  • Slightly slower than shell history; most operations still take less than 20 milliseconds on the high end.
  • For small shell histories, the sqlite3 database takes more space than a plaintext file.
  • Requires more memory than builtin history to store live history information.

Installation

Prerequisites

sqlite-history.sh requires sqlite3 version 3.24.0 or higher, and shell support for: both normal & associative arrays, coproc, and EPOCHREALTIME.

The installation script requires curl and either git or jq.

Quickstart

To install sqlite-history.sh, you can copy the command below and paste it into your console.

curl -fsSL https://sqlite-history.dev/install.sh | bash Copy to clipboard

Once the installer has run, you will need to open your shell's rc file (e.g. .bashrc for bash, .zshrc for zsh, etc.) and add the following:

source ~/.tools/sqlite-history.sh/sqlite-history.sh sqlh init Copy to clipboard

If you customized the target installation directory via the --install-dir argument to the installation script, make sure to use the correct path to the installation in your rc file.

Alternate

If you don't want to run the install script, you can also simply clone the repository:

git clone https://gitlab.com/prodigal.knight/sqlite-history.sh.git Copy to clipboard

Advanced

If you want to customize your installation, you can pass additional arguments to the installer by adding -s -- after bash and then the arguments you want to specify, e.g.:

curl -fsSL https://sqlite-history.dev/install.sh | bash -s -- [arguments...] Copy to clipboard

Some arguments you may want to customize are as follows.

-i, --install-dir
Default Value: ~/.tools/sqlite-history.sh/
The folder to install sqlite-history.sh in.
-t, --target
Default Value: master
The target branch, tag, or commit to install.
-C, --prefer-curl
Default Value: false
Use curl instead of git for installation.
-R, --skip-readme
Default Value: false on fresh installation, otherwise true
Do not display the README when done installing.
-S, --sqlite-path
Default Value: $(command -v sqlite3)
Specify an alternate location for sqlite3.

Setting Up sqlite-history.sh

Basic Setup

By default, sqlite-history.sh will inherit from your existing HISTIGNORE and HISTSIZE variables.

sqlite-history.sh's defaults can be further customized by changing the following variables before sourcing the script:

SQLH_HISTIGNORE
Defaults to the expanded value of HISTIGNORE, or an empty array if unset.
An array of commands to omit from the history database. These commands will still show up in live history unless also specified in SQLH_LIVE_HISTIGNORE.
SQLH_LIVE_HISTIGNORE
Defaults to the expanded value of HISTIGNORE, or an empty array if unset.
An array of commands to omit from live history. These commands will still show up in the database unless also specified in SQLH_HISTIGNORE.
SQLH_HISTSIZE
Defaults to HISTSIZE, or 500 if unset.
An array of commands to omit from the history database. These commands will still show up in live history unless also specified in SQLH_HISTIGNORE.

Additional variables you can use to tweak behavior are documented in the Advanced Usage section.

Using sqlite-history.sh

sqlite-history.sh provides the sqlh function, which has subcommands to access most of the functionality of the script, and sqlite-i-search, which replaces your shell's builtin Ctrl+R search.

sqlh Subcommands

sqlh init

Initializes sqlite-history.sh. Takes no additional arguments at this time.

sqlh fc

Runs sqlite-history.sh's replacement of builtin fc. Understands all arguments to builtin fc, as well as the following long-form versions:

-e, --editor
Specify an editor to use. If not specified, defaults to FCEDIT or EDITOR.
-l, --list
Prints history entries to the console rather than invoking the editor.
-n, --no-numbers
Omits history indices from list output.
-r, --reverse
Reverses list output.
-s, --substitute
Performs substitution on a command.

sqlh fc also understands -h and --help and will print a help message to the console, unlike builtin fc.

sqlh history

Runs sqlite-history.sh's replacement of builtin history. Understands most arguments to builtin history, as well as the following long-form versions:

-a, --append
Append live history to the history file. Ignored by sqlite-history.sh because it is not necessary.
-c, --clear
Clears live history.
-d, --delete
Deletes entries from live history.
-n, --read-new
Reads new entries from the history file into live history.
-p, --substitute
Performs substitution on the given command without adding it to live history, printing the result to the console.
-r, --reload
Loads from the history file into live history.
-w, --write
Write live history to the history file, overwriting any previous contents. Ignored by sqlite-history.sh because it is not necessary.

sqlh history also understands -h and --help and will print a help message to the console, unlike builtin history.

The following entirely new options are added in addition to the above:

-f, --filter
Takes an advanced match expression and filters output to only those entries matching the expression. May be used in conjunction with -o.
-F, --prune-from
Takes a comma-separated list of prune targets consisting of live or db, followed by a series of advanced match expressions. Prunes entries matching the given expression(s) from live or database history, as specified.
-o, --omit
Takes an advanced match expression and filters output to only those entries not matching the expression. May be used in conjunction with -f.
-P, --pager
Outputs history to the system pager instead of printing to the console.
-R, --prune
Takes a series of advanced match expressions. Prunes entries matching the given expression(s) from both live and database history.

sqlh update and sqlh update-check

Runs the installer script with pre-filled arguments for your current installation.

sqlh version

Reports the version of sqlite-history.sh to the console.

sqlite-i-search (Ctrl+R search)

sqlite-i-search is a drop-in replacement for shell builtin Ctrl+R search (reverse-i-search in bash, reverse-history-search in zsh), but instead of only being able to search the local shell's live history, it also searches in the database, pulling in any new commands run in other shells.

Usage is nearly identical to that of bash's reverse-i-search, with the following key bindings:

Escape
Exit sqlite-i-search and set the current command buffer to whatever the current selection was.
Up/Down Arrow Keys
Attempts to find the index of the currently selected command in live history and navigates through live history from that point, selecting the previous/next result. Exits sqlite-i-search.
Left/Right Arrow Keys
Selects the current command and exits sqlite-i-search, then moves the cursor either to the left or the right of the beginning of the first instance of matched text in the command; e.g.: if searching for "hello" and the command is echo "hello world", the left arrow key would move the cursor to the first double quote, and the right arrow key would move the cursor to the 'e' in "Hello".
Ctrl+R
Selects the next oldest (previous) matched entry, unless there are no more matches.
Ctrl+N
Selects the next newest matched entry, unless you are at the first (most recent) match.
This key binding is different from reverse-i-search's binding of Ctrl+S because that keystroke is normally swallowed by the terminal.
Home
Selects the current command and exits sqlite-i-search, then moves the cursor to the beginning of the line.
End
Selects the current command and exits sqlite-i-search, then moves the cursor to the end of the line.
Enter/Return
Selects the current command and exits sqlite-i-search, then moves the cursor to the end of the line.
This currently does not attempt to automatically run the selected command like reverse-i-search would for consistency across shells and operating systems, because we have been unable to find a truly cross-platform of sending an extra keystroke to the parent terminal.
Tab
Selects the current command and exits sqlite-i-search, then moves the cursor to the first instance of matched text in the command.

All other "special" keystrokes are ignored at this time.

By default, sqlite-i-search is configured to be run in place of the standard Ctrl+R search, but the keybind to start it can be customized.

Advanced Usage

Advanced Match Expressions

sqlh fc, sqlh history, and sqlite-history.sh's live/database history ignore arrays all allow usage of advanced match expressions, on top of standard shell history ignnore patterns. Advanced match expressions consist of a standard shell history ignore pattern preceded by one of the following special characters:

= for exact matching of an entire command
=l[ls] will match either ll or ls, but not ls -l.
^ for the start of a command
^ls will match any of the following: ls, ls -l, ls ~, etc.
$ for the end of a command
$| less will match any command piped to less.
/ for substring matching anywhere in a command
/kill will match commands containing kill, pkill, skill, etc.
! for exact word matching anywhere in a command
!kill will only match commands containing the exact word kill, and not commands containing other words that include "kill" like pkill, skill, etc.

Not having a special character, or escaping one of the above special characters with a backslash (\) will cause the match expression to use default behavior as defined by the value of SQLH_IMPLICIT_STARTSWITH as detailed below.

Environment Variables

Core Behavior

NO_COLOR
No Default Value
Disables ANSI color codes in output, following the no-color.org standard.
SQLH_SQLITE
Default Value: $(command -v sqlite3)
The path to the sqlite3 executable you wish to use. Will also be eventually used to allow using sqlcipher.
SQLH_HISTFILE
Default Value: ~/history.sqlite
The path sqlite-history.sh's history file.
SQLH_NO_CLOBBER_BUILTINS
No Default Value
Prevent clobbering (shadowing) shell builtins fc and history with shell functions, only providing sqlh fc and sqlh history.
SQLH_IMPLICIT_STARTSWITH
No Default Value
Use startswith matching to determine if a given shell command should be ignored, instead of mimicing default shell behavior of matching the full command.
SQLH_BETA_OPT_IN
No Default Value
Opt in to beta features available on the develop branch of the repository instead of waiting for them to reach an official release.
SQLH_UPDATE_CHECK_INTERVAL
Default Value: "1d"
A duration string describing how often to check for updates. Supports discrete units from seconds to weeks.
SQLH_UPDATE_MSG_QUEUE
Default Value: "precmd"
The name of the deferred action queue to use for printing "updates are available" messages. Valid values are currently precmd and preprompt.

Miscellaneous Behavior

The following variables are intended primarily for debugging, and have no default values.

SQLH_ALLOW_DEBUG_LOGS
If set to any value other than false, enables debug logging. Otherwise, debug logging is a no-op to keep sqlite-history.sh as fast as possible.
SQLH_DEBUG_LEVEL
An integer value between 0 and 3 (inclusive) which controls the level of debug logging verbosity. Has no effect if SQLH_ALLOW_DEBUG_LOGS is set to false.
SQLH_LOG_FILE
The path of a log file to write to.
SQLH_SUPPRESS_LOGS_IN_TERM
Suppresses log messages in the terminal, printing them only to the log file if explicitly set to anything other than false.

Custom Keybinds

You can customize several key bindings for sqlite-history.sh via the following additional environment variables:

SQLH_REVERSE_I_SEARCH_KEYBIND
Ctrl+R
Initiates sqlite-i-search.
SQLH_PREV_HIST_ENTRY_KEYBIND
Up Arrow
Navigates to previous history entry when not in sqlite-i-search.
SQLH_NEXT_HIST_ENTRY_KEYBIND
Down Arrow
Navigates to next history entry when not in sqlite-i-search.

Custom Colors

If you have not opted to disable colorization entirely by enabling NO_COLOR above, you can customize the colors sqlite-history.sh uses in various situations by changing these environment variables:

SQLH_INTERACTIVE_SUCCESS_COLOR
Used in sqlite-i-search for successful search indication; default value: \e[36m (ANSI Cyan)
SQLH_INTERACTIVE_FAILURE_COLOR
Used in sqlite-i-search for failed search indication; default value: \e[31m (ANSI Red)
SQLH_INTERACTIVE_TERM_COLOR
Used in sqlite-i-search for search term; default value: \e[93m (ANSI Bright Yellow)
SQLH_INTERACTIVE_RESULT_COLOR
Used in sqlite-i-search for search result; default value: \e[94m (ANSI Bright Blue)
SQLH_LOGGING_HIGHLIGHT_COLOR
Used in logging to highlight [sqlite-history.sh]; default value: \e[36m (ANSI Cyan)
SQLH_LOGGING_ERROR_COLOR
Used in error logs; default value: \e[31m (ANSI Red)
SQLH_LOGGING_WARN_COLOR
Used in warning logs; default value: \e[33m (ANSI Yellow)
SQLH_LOGGING_INFO_COLOR
Used in info logs; default value: \e[93m (ANSI Bright Yellow)
SQLH_LOGGING_DEBUG_COLOR
Used in debug logs; default value: \e[37m (ANSI White)
SQLH_HYPERLINK_COLOR
Used in Markdown conversion; no default value
SQLH_ASIDE_COLOR
Used in Markdown conversion; default value: \e[90m (ANSI Bright Black)

Handcrafted Code

sqlite-history.sh and its maintainers are proud of the fact that 100% of the repository has been written without AI.

Our Promise

We will do everything in our power to keep sqlite-history.sh AI-free forever - that is to say, we will do everything we reasonably can to ensure no code is merged that was created using AI "contributions" or "assistance", and we will never introduce any sort of AI assistant to sqlite-history.sh itself.

The Reason

AI companies are currently gobbling up all of the computing hardware in the world, crowding out the individual consumer, preventing normal people from being able to afford a computer. Whether this is by design or not isn't important - we believe that everyone should be able to do what they want on their computer without everything being funneled through the cloud or some AI agent.

We also believe that the shell in particular should be a place that never be able to mislead the user. AI "assistants" will always hallucinate fake information, regardless of how well they're trained on real data.

This is not to say that we believe AI is useless. We just believe that jamming AI into anything and everything possible is the peak of stupidity, and we would like to leave users with at least one tool that isn't trying to be smarter than it should be.

Keeping AI out of the mix also helps us better tune the performance of sqlite-history.sh. AI is trained on "good" coding patterns, which for shell scripts almost always means using subshells. This kills the performance of sqlite-history.sh in critical areas such as the Ctrl+R search, which needs to be able to re-render itself in realtime as the user types.

The Benefits

You don't need to upgrade your device in order to run sqlite-history.sh.

  • It typically requires less than 1 megabyte of additional RAM per shell, and doesn't require a GPU (much less an NPU) at all.
  • It reliably initializes in less than 100 milliseconds on a 10+ year old PC running in Cygwin on Windows 10 even when debug logs are enabled (i.e. extra work is being done).
  • sqlite-i-search re-renders itself within 10 milliseconds of both user input and reading results from the database