If you work in the shell long enough your history will become your own personal reference. It's like automatic documentation which is the best kind of documentation.
Unfortunately, the default configuration for history is not always great.
You should already be familiar with variables such as $HISTFILE
and $HISTSIZE
.
Here are a few shell options that can improve your history recording and usage.
Drop this config in your .zshrc
file to improve history recording.
setopt EXTENDED_HISTORY # Write the history file in the ':start:elapsed;command' format.
setopt INC_APPEND_HISTORY # Write to the history file immediately, not when the shell exits.
setopt SHARE_HISTORY # Share history between all sessions.
setopt HIST_EXPIRE_DUPS_FIRST # Expire a duplicate event first when trimming history.
setopt HIST_IGNORE_DUPS # Do not record an event that was just recorded again.
setopt HIST_IGNORE_ALL_DUPS # Delete an old recorded event if a new event is a duplicate.
setopt HIST_FIND_NO_DUPS # Do not display a previously found event.
setopt HIST_IGNORE_SPACE # Do not record an event starting with a space.
setopt HIST_SAVE_NO_DUPS # Do not write a duplicate event to the history file.
setopt HIST_VERIFY # Do not execute immediately upon history expansion.
setopt APPEND_HISTORY # append to history file
setopt HIST_NO_STORE # Don't store history commands
For most people working with history means using sudo !!
or occationally ctrl+r
or history | grep
.
To master working with your history we should look at a few other commonly used shortcuts instead.
First of all, you should know !!
is essentially the same as
# Note: this doesn't actually work but the fc command is effectively the same
alias !!='fc -ln -1'
More on fc
later
But there are lots of other aliases that start with !
.
Here are some.
!*
or!:*
= All arguments (minus command)!$
or!:$
= Last argument/word!^
or!:1
= First argument (doesn't include command name)!foo
= The last command that started withfoo
If you want to use words from commands earlier in your history you can also use the !-n
format.
!-3:$
= Use last word from 3 commands ago!-2:*
= Use all arguments from 2 commands ago!-2; !-1
= Combine last two commands into a single line
You can also make this better with bindkey
and the insert-last-word zle widget.
More on what this means can be found in the widgets section.
# press Alt+. to insert the last word from the previous command
autoload -U smart-insert-last-word
zle -N smart-insert-last-word
bindkey "\e." smart-insert-last-word
It's great to be able to use parts from the last command, but what if you want to rerun the last command with edits? You have a few options.
echo foo foo
# using the above style !! we can do a single substitution with
!!:s/foo/bzz # bzz foo
# global substitutions need g
echo foo foo
!!:sg/foo/bzz # bzz bzz
# be careful though because global replace happen for the entire line
echo foo foo
!!:sg/o/z # echz: command not found...
There are a ton of other history modifiers if you read through the posix history man page. For me they're all hard to remember and I don't use them enough to list them here.
Sometimes !!:s/
is hard to type so there's also the shorthand replace with ^
systemctl status sshd
# replace status with restart
^status^restart # systemctl restart sshd
# use ^:G for global replace
echo foo foo
^foo^bzz^:G # bzz bzz
If you can't remember any of these formats (I don't blame you) then try fc
.
fc
by default will edit your last command in a visual editor.
This is great for long or multi-line commands.
It can also be used to list commands from history and do substitution.
Bash
fc
is a little different than zsh'sfc
so be careful which man page you read.apropos fc
to find the specific version and thenman 1p fc
.
echo 'foo
bar'
# edit last command in a visual editor and execute it
fc
For global string replacement you can use string=replace
syntax.
If you want to run the command without using an editor override $EDITOR
with -e -
.
echo foo bar baz foo
fc -e - foo=boo # echo boo bar baz boo
This works great as an alias alias r='fc -e -'
so running r foo=bar
will replace the string for you!
We've already discussed using ctrl+r
and fc -l
to search through your history.
By default the history
command in zsh only shows the past 16 entries.
If you want to list all of your history you can use history 1
.
Listing your entire history on its own is not very useful. Most often you want to search for something, to do that we can make a function to handle our searching.
function h() {
# check if we passed any parameters
if [ -z "$*" ]; then
# if no parameters were passed print entire history
history 1
else
# if words were passed use it as a search
history 1 | egrep --color=auto "$@"
fi
}
# search entire history for "foo" with
h foo
This will print a list of commands with numbers.
You can immediately re-execute any of the commands with !n
where n
is the command number.
h foo
100 echo foo
101 echo foo foo
102 echo foobar
Our command substitution from before works too!
!101:s/foo/bar # echo bar foo
Sometimes I want to combine multiple commands from my history into a single line. We can do that with a function and zle
# add the ability to print >> << for the portion of the cli we'll be using
autoload -Uz narrow-to-region
# define our function
function _history-incremental-preserving-pattern-search-backward
{
local state
MARK=CURSOR
narrow-to-region -p "$LBUFFER${BUFFER:+>>}" -P "${BUFFER:+<<}$RBUFFER" -S state
zle end-of-history
zle history-incremental-pattern-search-backward
narrow-to-region -R state
}
# load the function into zle
zle -N _history-incremental-preserving-pattern-search-backward
# bind it to ctrl+r
bindkey "^R" _history-incremental-preserving-pattern-search-backward
bindkey -M isearch "^R" history-incremental-pattern-search-backward
bindkey "^S" history-incremental-pattern-search-forward
If you load the above configuration then ctrl+r
will still work for normal history searching, but if instead of pushing <ENTER>
you push Alt+;
then a ;
will be added to the line and you can push ctrl+r
again to insert another command.
Another way to use your history is with interactive searching. This is most useful when you can remember exactly what command you wanted but remember some of the words.
To do this fzf
is a great tool.
Install the binary in your $PATH
and use a function like the one below to search command history.
function fh() {
eval $( ([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s --tac | sed 's/ *[0-9]* *//')
}