This post is part 6 of 6 in the series Bash to Zsh

Having used Zsh rather than Bash for over a week it was time to make the move permanent by migrating my shell customisations from ~/.bashrc to ~/.zshrc. Your milage may vary, but I was pleased to find I didn’t need to make any changes, and, that I could get rid of one command from the script because Zsh defaults to a behaviour I had to explicitly opt in to with Bash.

TL;DR: environment variables (including PATH) and aliases work just the same in Zsh as they do in Bash, so if those are the only things you alter in your ~/.bashrc, then you can just copy it over to ~/.zshrc. But, if you alter Bash settings in your ~/.bashrc, you’ll need figure out the equivalent Zsh options and replace the relevant lines with the appropriate Zsh setopt or unsetopt Zsh commands.

Note: The original version of this post wrongly asserted that Zsh avoided shell history duplication by default. This is not correct, and the post was updated with the details of how to update your ~/.zshrc file to avoid duplication of commands when you hit the up arrow in the shell.

My ~/.bashrc file did just three things:

  1. Export environment variables
  2. Set command aliases
  3. Set non-default Bash Options

Environment Variables

As an example, my ~/.bashrc sets my SVN editor to vi by exporting an environment variable:

# set the editor for SVN
export SVN_EDITOR=/usr/bin/vi

Copying these lines un-changed to ~/.zshrc worked perfectly.

Command Aliases

As an example, my ~/.bashrc defines an alias to replace ls with ls -GF so I get colour-coded output when I list the contents of folders:

# Get LS to work in colour
alias ls="ls -GF"

Again, copying these lines un-changed to ~/.zshrc worked perfectly.

Non-default Bash Options

On the whole I chose not to alter Bash’s default behaviour, but I did make one exception to that rule.

On MacOs, when you enter the same command multiple times in a row in the same terminal window each copy of the command gets saved into the Bash history, so, when you hit the up arrow to get to previous commands, you have to hit up repeatedly to get to the command you entered before the one you repeated. I can’t comprehend why anyone would want this behaviour as the default, so, I changed it!

Bash uses environment variables to control shell options, so, my ~/.bashrc contains the following lines to stop the duplication of repeated commands in the history:

# stop bash saving duplicates to the history
export HISTCONTROL=ignoredups

Zsh doesn’t manage its optional settings via environment variables, it provides dedicated commands for that purpose — setopt to set an option, and unsetopt to un-set an option.

In general, you’ll need to find a matching Zsh option for each of your custom Bash options, and then add the appropriate setopt or unsetopt command into your ~/.zshrc.

In this specific case, I needed to find the Zsh option that’s equivalent to HISTCONTROL=ignoredups in Bash.

In both Bash and Zsh, when you hit the up arrow you are working your way through the shell’s history. Setting HISTCONTROL=ignoredups in Bash tells the shell not to add a command to the history if it’s the same as the previous command added to the history.

The history file is not just used for the up arrow, it also serves as a record of the commands executed. By telling Bash to ignore duplicates that history is actually incomplete, so this is an imperfect solution. The problem is we are filtering the writes to the history, not the reads from the history.

Zsh gives us much better options for controlling the history.

If we want to filter what gets written to the history we can use the option HIST_IGNORE_DUPS to tell the shell not to store a command into the history if it’s a duplicate of the previously executed command. Or, we can be even more aggressive and use the HIST_IGNORE_ALL_DUPS option to tell the shell not to store a command into the history if it already exists anywhere within the entire history.

A much better option IMO is Zsh’s HIST_FIND_NO_DUPS option. This does not filter what gets written to the history, but rather, what gets read back from the history when we do thinks like hit the up arrow. Do note that this filter does not just filter contiguous repeats, but repeats anywhere in in the history. This is how the zshoptions man page (man zshoptions) describes the option:

HIST_FIND_NO_DUPS: When searching for history entries in the line editor, do not display duplicates of a line previously found, even if the duplicates are not contiguous.

I decided that of the three options, HIST_FIND_NO_DUPS made the most sense for me, so I added the following to my ~/.zshrc file:

# prevent duplicates when hitting the up arrow in the shell

Update — 26 September 2019: HIST_FIND_NO_DUPS: is not working for me on MacOS Mojave, so switched to HIST_IGNORE_DUPS::

# prevent duplicates when hitting the up arrow in the shell