Keeping a long shell history
registerspill.thorstenball.com
registerspill.thorstenball.com
In ~/.inputrc:
"\e[A": history-search-backward
"\C-p": history-search-backward
"\e[B": history-search-forward
"\C-n": history-search-forward "\e[A": history-search-backward
"\e[B": history-search-forward
"\eOA": history-search-backward
"\eOB": history-search-forward
The only downside ist, don't over-rely on history muscle memory! Regularly every few months I use p+arrowUp and expect to get "ping" but get the exceptional "pip" instead.The one thing that computers are really good at is storing data (especially text) so I believe it should be a core function of any computer that it should never loose anything a user has input.
Sadly the reality is far from the truth. The number of times I have entered something into a webform or an app, only to have it disappear for some weird reason...
Personally, I can't remember the solution to everything I do so having that deduplicated log gives me the full history of what I did and how I resolved it, amongst other uses. Sure, removing duplicate 'ls' entries is fine for example, but for every command? Not so sure.
(Author of post)
sudo chattr +a .sh_history
to ensure that my shell history file isn’t deleted.Being append-only, it also ensures that some type of timing issue in my shell which means my HISTFILESIZE or the equivalent isn’t set to something larger than default, meaning my history isn’t deleted.
When a command has some cognitive requirements I create a script with some ${1:-default} values and I store them all in $PATH enabled local/bin
All my scripts start with __ and then some capital letters indicating the topic.
I can copy them to new machines, which is something I would not do with my history.
Sadly git doesn't allow for aliases with an comma-prefix, so all git aliases starts with a dot.
The fzf search syntax [1] can help, if you become familiar with it. It is also supported in atuin [2].
[1]: https://github.com/junegunn/fzf#search-syntax
[2]: https://docs.atuin.sh/configuration/config/#fuzzy-search-syn...
Like if I tend to go down the wrong path, fix it, and then go down the right path, maybe that's a pattern that can be identified and then when I search history it finds two or three commands which together set me on the right path in the first place.
I mean, thinking ahead is always gonna be better, but I don't always.
cd ~/x/y/z && my-command
So that when I CTRL-R for my-command it automatically reminds me. (cd ~/x/y/z && my-command) $ history | get 0
╭─────────────────┬─────────────────────────────╮
│ start_timestamp │ 2023-12-15 15:39:12.872 UTC │
│ command │ ls │
│ cwd │ /home/matt/src/configs │
│ duration │ 31 ms │
│ exit_status │ 0 │
╰─────────────────┴─────────────────────────────╯
It would take some smarts to figure out when to bind the cwd and when not to, but that would be the interesting part.Whenever I'm satisfied with a pretty long command, I add a comment at the end that explain what it does (#blablabla). Makes searching sort of easier.
Some time ago I found sd [1] though. It's a light wrapper around your own scripts which provides namespaces, autocompletion, custom help texts + some other QoL enhancements around that. It improves discoverability and usability a lot, very happy with it.
I would consider using just for this:
https://stackoverflow.com/search?q=user%3A5783745+created%3A...
Looks like on that day I was having trouble aligning a navbar (no surprise), and figured out how to render a view without a layout in rails 5.
setopt HIST_IGNORE_SPACE> setopt HIST_IGNORE_SPACE
Except that I think it's called `histignorespace` (I haven't caught up with when and how much zsh cares about lowercased names), I think this is just about zsh, not about macOS specifically.
HISTCONTROL=ignorespace
I use a zsh precmd() along the lines of
echo "$(date "+%Y-%m-%d.%H:%M:%S") $(pwd) $(fc -ln -1)" >> ~/bash/bash-history-$(date "+%Y-%m-%d").log;
Should be doable with bash's PROMPT_COMMAND if you are still on bashLines 11-21 can be ignored, they detect if the folder you were in got moved/deleted from another shell, to avoid the confusing behavior you get in that case.
Possibly worth noting for others that you'd want to think this through if you're using any of the history eliding options(hist_ignore_space² for example), as one history file may contain secrets when you're really expecting that it wouldn't.
There is also a better interface to work with hook functions through the add-zsh-hook mechanism³, which allows stacking multiple hook functions together.
¹ https://zsh.sourceforge.io/Doc/Release/Functions.html#index-...
² https://zsh.sourceforge.io/Doc/Release/Options.html#History
³ https://zsh.sourceforge.io/Doc/Release/User-Contributions.ht...
Already done, with also PS0 and a sqlite backend: https://github.com/csdvrx/bash-timestamping-sqlite
script -q "$LOGDIR/$LOGTIME.log" --timing="$LOGDIR/$LOGTIME.timing"
The log files are noisy with ANSI codes, but they can be stripped, and I only refer to the logs extremely rarely (but when I do, it's extremely useful).export PROMPT_COMMAND='if [ "$(id -u)" -ne 0 ]; then echo "$(date "+%Y-%m-%d %H:%M:%S") $(pwd) $(HISTTIMEFORMAT= history 1)" >> ~/.logs/bash-history-$(date "+%Y-%m-%d").log; fi'
This give an easily greppable shell history. An entry looks like this:
2022-10-13 09:14:54 /home/bhaak 1000 vim ~/.bashrc
It's a hacky script but the idea is sound. Good thing about SQL is that I can fine tune the query before feeding to fzf. A combination of order by recency and frequency of commands with and without context (pwd) works very well for me. With proper indexing I am not close to feeling slowdown, but if that happens I can probably just export data to duckdb and use that.
This helps (I use bash rather than zsh):
# Sort out history$
export HISTIGNORE="&:ls:vi:bg:fg:history"
export HISTCONTROL=ignoredups:ignorespace
export HISTSIZE=100000
export HISTFILESIZE=100000
shopt -s histappend
shopt -s checkwinsize
But the long-term (multi-machine) solution is to keep another append-only copy, one per machine: function storehist() {
# Bug in mac version of bash stops HISTCMD getting set in function called from PROMPT_COMMAND...
CMDCNT=`history 1 | sed -e 's/\w* *\(.*\)/\1/'`;
if [ "$CMDCNT" != "$LASTCMDIND" ] && [ -n "$LASTCMDIND" ] # First time, no prev, do not log random
then
DATE=`date '+%Y%b%d %H%M'`;
if [ "$LASTCMDIND" != "$CMDCNT" ]
then
echo "$DATE $HOSTNAME:$BASHTTY $CMDCNT" >>~/.custom_history;
fi
LASTCMDIND="$CMDCNT";
fi
LASTCMDIND="$CMDCNT";
}
export PROMPT_COMMAND='storehist; ....
A little bit over the top and crusty but it has worked like that for years so I've not updated it in a while. There is a separate sync script that puts the .custom_history from each machine / container into a folder on each machine with the orginating machine name in the file-name.This keeps ^-R working over a long period of time, but eventually that stops being a good way of find obscure commands from long ago and then something like this works:
cat .histories/* | grep CMD | grep WERID_ARG | grep RANDOM_RELEVANT_STR | sort -u | less
The overall cost of running some script on each prompt is negliable on a modern machine and as the OP notes the disk space does not amount to much. It becomes very useful over time. I have about 20MB of shell history that stretches back to '06. It is one of things that is easy to setup, and yet the value accrues steadily over time. Across the dozen or so machines involved I doubt that the archive would have survived long-term without the step to export it outside of the shell's real history file. export HISTCONTROL=ignoreboth
which I think is shorthand for ignoredups:ignorespace shopt -s histappend
export HISTFILESIZE=1000000
export HISTSIZE=1000000
export HISTTIMEFORMAT="%m/%d/%Y %T "
export HISTIGNORE="exec env*:history*"
export HISTCONTROL="ignoreboth"
It seems my history preceeds "recorded time", because timestamps start in 2016, which is probably when I discovered HISTTIMEFORMAT. 110k commands btwthe "exec env*" is because emacs tramp uses that when remotely logging into a machine and fill the logs with junk.
The fist thing I do on any machine is to set HISTSIZE and HISTFILESIZE to large numbers. It is wonderful to refer back to how you set up a machine from scratch for instance.
How did I set up this. what did I set up. What packages did I install.
I've always kind of wanted to create some sort of "annotated history" program. The idea would be, to ask you what you are doing (maybe in a separate window to the side) so you could properly document something fiddly.
I like features like this that build off of how _i_ use the shell, and it's up to me to figure out how I want to use it, and it's so simple but it makes using the shell all that much more efficient and it's easier for me to express my intent to the shell. just very cool, like learning a bit of the coreutils to make every day stuff in system administration easier.
It works pretty well for me, it often reminds me of things I was working on but haven't yet finished.
Since I have so many tabs, I also created a firefox extension to pin tabs on the right, so that they are close to the "new tab" button.
open tabs is more like a cluttered desk.
You know, I would love a browsing history (as a log file). I think it would be helpful and useful.
It isn't massive, but there's real shell startup latency associated with loading lines from your histfile. A few hundred isn't really noteworth, but tens and eventually hundreds of thousands of lines can add up.
(In my case, i store them in a database and I synthesize histfiles from the db. Keeping the permanent storage in a separate file also probably works.)
Even if it were slower, I also think the overhead of something like that is more than made up for by the help it provides.
Here’s the ZSH configuration I used for many, many years:
possibly cannot fully answer the question as there are settings to scrub duplicate commands ... meaning the target day will show commands unique to that day but not necessarily all commands from that day.It's a small quibble but potentially an issue for those that really need to Indiana Jones their command history for some specific clue.
This leads to "I can see I tidied up a bunch of project files first monday of last month ... but which project was that??"
I work at a large facility with a shared filesystem and many computers, so often I need to answer questions like “What did I run the last time I was sitting at computer X”. I log: command, timestamp, PID (to separate terminal streams), hostname and cwd.
I’ve set up ctrl-r to search this “.fullhistory” file instead.
if [[ -n ${ZSH_VERSION-} ]]; then
# ZSH doesn't split command over multiple variables
preexec_custom_history() {
echo "$HOSTNAME:\"$PWD\" $$ $(date "+%Y-%m-%dT%H:%M:%S%z") $1" >> "$CUSTOM_HISTORY_FILE"
}
else
preexec_custom_history() {
echo "$HOSTNAME:\"$PWD\" $$ $(date "+%Y-%m-%dT%H:%M:%S%z") $*" >> "$CUSTOM_HISTORY_FILE"
}
fi
# Add it to the array of functions to be invoked each time.
preexec_functions+=(preexec_custom_history)
It's harder to search for multiline but I haven't gotten to fixing that yet. I've thought about putting it into sqlite (pwd makes the file large) but since everything is on network filesystems I don't think that's a place sqlite has any guarantees; screwing up a single line of a bare log doesn't break anything.Edit: The fzf is kind of hacked together but is here: https://pastebin.com/sTxsvZAf . It has a few bugs but nothing annoying enough to have made me fix it.
I've been using it for 6+ months and it's actually good. I also like having an sqlite database with my commands so I can search it even more freeform if I want.
sometimes I ‘sort | uniq’ the bash history to filter about duplicates
So a couple of thousand seems to do well by me.
$MaximumHistoryCount = 9999I do not get this. I have over 30 years certainly cobbled some complicated commands, but I never need an ancient one.
If it's complicated and used more than one day, it's a script.
Complicated or not and used a long time ago, it's no longer valid.
These days "so old it's no longer valid" is barely a year. Back in the sco Unix days you could have 10 year old commands that are still valid references, but Linux never had that except for trivial things.
There are exceptions but they are exceptions and so by definition not reasonable to focus on.
I think that building essentially mini apps right on the command line and then carrying around years of shell history to reuse them is solving a problem the wrong way. A stupid and backwards way.
It's losing sight of what the problem actually is and like growing one weird finger muscle to the size of a leg instead of realizing maybe you're not supposed to still be using a finger for that job.
> These days "so old it's no longer valid" is barely a year
I don't think this is true (generally). At my work, we use old boring technology for most things (as we should). I don't see how it could be true enough to warrant not keeping a command line log.
I suppose everyone’s workflows are different though. I keep a long history but rarely use it.
These are rarely commands so complex that they represent serious programming effort, but that's not why I want to save them.
(My approach: https://www.jefftk.com/p/you-should-be-logging-shell-history)
That was a great solution prior to tools like fzf. It's more overhead creating such a script than using the OP's solution.
In ye old days, I would sort/organize bookmarks. Bookmark search solved that problem. And for a long while, Google solved the problem of even having bookmarks.
Same idea here. Why go through the trouble of making a script (and remembering its name), when you can find the command within seconds?
In the browser context, auto completion search from history in the search/URL bar hugely reduces the need to even make bookmarks for top level sites. It's just much easier to type the first few characters of the site I want then hit enter.
just a simple example:
history | grep pacman