You could do something like:
fixhist() {
local cmd histnum
cmd=$(HISTTIMEFORMAT=/ history 1)
histnum=$((${cmd%%[*/]*}))
cmd=${cmd#*/} # remove the histnum
case $cmd in
(rm\ *|mv\ *|...)
history -d "$histnum" # delete
history -s "#$cmd" # add back with a #
esac
}
PROMPT_COMMAND=fixhist
The idea being that before each prompt, we check the last history entry (history 1
) and if it's one of the dangerous ones, we delete it (history -d
) and add it back with a #
with history -s
.
(obviously, you need to remove your HISTIGNORE
setting).
An unwanted side effect of that though is that it alters the history time of those rm
, mv
... commands.
To fix that, an alternative could be:
fixhist() {
local cmd time histnum
cmd=$(HISTTIMEFORMAT='<%s>' history 1)
histnum=$((${cmd%%[<*]*}))
time=${cmd%%>*}
time=${time#*<}
cmd=${cmd#*>}
case $cmd in
(rm\ *|mv\ *|...)
history -d "$histnum" # delete
HISTFILE=/dev/stdin history -r <<EOF
#$time
#$cmd
EOF
esac
}
PROMPT_COMMAND=fixhist
This time, we record the time of the last history, and to add back the history line, we use history -r
from a temporary file (the here document) that includes the timestamp.
You'd want the fixhist
to be performed before your history -a; history -c; history -r
. Unfortunately, the current version of bash
has a bug in that history -a
doesn't save that extra line that we've added. A work around is to write it instead:
fixhist() {
local cmd time histnum
cmd=$(HISTTIMEFORMAT='<%s>' history 1)
histnum=$((${cmd%%[<*]*}))
time=${cmd%%>*}
time=${time#*<}
cmd=${cmd#*>}
case $cmd in
(rm\ *|mv\ *|...)
history -d "$histnum" # delete
history -a
[ -f "$HISTFILE" ] && printf '#%s\n' "$time" "$cmd" >> "$HISTFILE";;
(*)
history -a
esac
history -c
history -r
}
PROMPT_COMMAND=fixhist
That is to append the commented command to the HISTFILE ourselves instead of letting history -a
do it.
Turns out revert-all-at-newline
is the answer. I needed to include set revert-all-at-newline on
in my ~/.inputrc
file, since using the set
command at the bash prompt had no effect. (Then, of course, I had to start a new shell.)
Also, I found that ~/.inputrc
is loaded instead of /etc/inputrc
if present, which means that any defaults defined in the latter are no longer active when you create ~/.inputrc
. To fix this, start ~/.inputrc
with $include /etc/inputrc
.
Thanks to @StéphaneChazelas for pointing me in the right direction.
Best Answer
You can use the
PROMPT_COMMAND
feature, add to your.profile
/.bashrc
:This stuffs a (commented out)
cd
command into your history each time the current directory changes. It stores$PWD
, so anything likecd ..
will still record the full path. This appears in your normal history, so it's not the most convenient, and it break shorthands like!$
...Here's better (i.e. over-engineered) version, it checks if the previous command changed directory to an absolute directory to prevent polluting the history:
It may need minor tweaking if you have a custom
HISTTIMEFORMAT
.You can tweak the
history -s
operation to instead delete and reinsert/modify thecd
,pushd
,popd
commands, e.g.(A tempting option is to change
HISTTIMEFORMAT
dynamically in thePROMPT_COMMAND
function, but this is not added to the history file, it's only applied when you display the history, so it won't work as hoped.)Bash saves history timestamps as a
#nnnn
(epoch-seconds) line above each command entry. In principle the$PWD
could be added to that line after the timestamp without breaking anything, but some non-trivial code changes would be required to properly support this.