Apple has announced that in macOS 10.15 Catalina the default shell will be zsh
.
In this series, I will document my experiences moving bash
settings, configurations, and scripts over to zsh
.
This series has grown into a book: reworked and expanded with more detail and topics. Like my other books, I plan to update and add to it after release as well, keeping it relevant and useful. You can order it on the Apple Books Store now.
Now that we have chosen a file to configure our zsh
, we need to decide on ‘what’ to configure and ‘how.’ In this post, I want to talk about zsh
’s shell options.
As I have mentioned in the earlier posts, I am aware that there are many solutions out there that give you a pre-configured ‘shortcut’ into lots of zsh
goodness. But I am interested in learning this the ‘hard way’ without shortcuts. Call me old-fashioned. (“Uphill! In the snow! Both ways!”)
In the previous post, I listed some features that I would like to transfer from my bash
configuration. While researching how to implement these options in zsh
, I found a few, new and interesting options in zsh
.
The settings from bash
which I want in zsh
were:
- case-insensitive globbing
- command history, shared across windows and sessions
Note: bash
in this series of posts specifically refers to the version of bash
that comes with macOS as /bin/bash
(v3.2.57).
Note 2: Mono-typed lines starting with a %
show commands and results from zsh
. Mono-typed lines starting with $
show commands and results in bash
What are Shell Options?
Shell options are preferences for the shell’s behavior. You are using shell options in bash
, when you enable ‘trace mode’ for scripts with the set -x
command or the bash -x
option. (Note: this also works with zsh
scripts.)
zsh
has a lot of shell options. Many of these options serve the purpose of enabling (or disabling) compatibility with other shells. There are also many options which are specific to zsh
.
You can set an option with the setopt
command. For compatibility with other shells the setopt
command and set -o
have the same effect (set an option by name). The following commands set the same option:
set -o AUTO_CD
setopt AUTO_CD
The names or labels of the options are commonly written in all capitals in the documentation but in lowercase when listed with the setopt
tool. The labels of the options are case insensitive and any underscores in the label are ignored. So, these commands set the same option:
setopt AUTO_CD
setopt autocd
setopt auto_cd
setopt autoCD
There are quite a few ways to negate or unset an option. First you can use unsetopt
or set +o
. Alternatively, you can prefix with NO
or no
to negate an option. The following commands all have the same effect of turning off the previously set option AUTO_CD
unsetopt AUTO_CD
set +o AUTO_CD
unsetopt autocd
setopt NO_AUTO_CD
setopt noautocd
Any options you change will only take effect in the current instance of zsh
. When you want to change the settings for all new shells, you have to put the commands in one of the configuration files (usually .zshrc
).
Showing the current Options
You can list the existing shell options with the setopt
command:
% setopt
combiningchars
interactive
login
monitor
shinstdin
zle
This list only shows options are changed from the default set of options for zsh
. These options are marked with <D>
(default for all shell emulations) or <Z>
(default for zsh
) in the documentation or the zshoptions
man page.
You can also get a list of all default zsh
options with the command:
% emulate -lLR zsh
Some zsh Options I use
As I have mentioned before in my posts on bash
configuration, I prefer minimal configuration changes, so I do not feel all awkward and lost when I have to work on an ‘un-configured’ Mac.
These configurations are a personal choice and you should pick and choose your own. You can find a full list of zsh
options in the zsh
Manual or with man zshoptions
.
On the other hand, exploring the options allows us to explore a few useful zsh
features.
Case Insensitive Globbing
Note: ‘Globbing’ is a unix/shell term that refers to the expansion of wildcard characters, such as *
and ?
into full file paths and names. I.e. ~/D*
is expanded into /Users/armin/Desktop /Users/armin/Documents /Users/armin/Downloads
Since the file system on macOS is (usually) case-insensitive, I prefer globbing and tab-completion to be case-insensitive as well.
The zsh
option which controls this is CASE_GLOB
. Since we want globbing to be case-insensitive, we want to turn the option off, so:
setopt NO_CASE_GLOB
You can test this in the shell:
% ls ~/d*<tab>
In zsh
tab completion will replace the wildcard with the actual result. So after the tab you will see:
% ls /Users/armin/Desktop /Users/armin/Documents /Users/armin/Downloads
Using tab completion this way to see and possibly edit the actual replacement for wildcards is a useful safety net.
In bash
hit the tab key will list possible completions, but not substitute them in the command prompt.
If you do not like this behavior in zsh
then you can change to behavior similar to bash
with:
setopt GLOB_COMPLETE
Automatic CD
Sometimes you enter the path to a directory, but forget the leading cd
:
$ Library/Preferences/
bash: Library/Preferences/: is a directory
% Library/Preferences
zsh: permission denied: Library/Preferences
With AUTO_CD
enabled in zsh
, the shell will automatically change directory:
% Library/Preferences
% pwd
/Users/armin/Library/Preferences
This works with relative and absolute paths, including the ..
:
% ..
% pwd
/Users/armin/Library
% ../Desktop
% pwd
/Users/armin/Desktop
I have an alias
in my .bash_profile
that sets the ..
command to cd ..
. Auto CD replaces that functionality and more.
Enable Auto CD with:
setopt AUTO_CD
Shell History
Shells commonly remember previously executed commands and allows you to recall them with the up and down arrow keys, search or special history commands.
Most of those keys work the same in zsh
. However, there are a few things you need to configure for zsh
history to work as you are used to with bash
on macOS.
By default, zsh
does not save its history when the shell exits. The history is ‘forgotten’ when you close a Terminal window or tab. To make zsh
save its history to a file when it exits, you need to set a variable in the shell:
HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history
Note: this is not a shell option but shell variable or parameter. I will cover some more of those later, You can find a list of variables used by zsh
in the documentation.
The HISTFILE
variable tells zsh
where to store the history data. The syntax ${ZDOTDIR:-$HOME}
means it will use the value of ZDOTDIR
when it is set or default to the value of HOME
otherwise. When a user has set the ZDOTDIR
variable to group their configurations files in a specific directory, the history will be stored there as well.
By default zsh
simply writes each command in its own line in the history file. You can view the file’s contents with any text editor or list the last few commands:
% tail -n 10 ~/.zsh_history
You can make zsh
add a bit more data (timestamp in unix epoch time and elapsed time of the command) by setting the EXTENDED_HISTORY
shell option.
setopt EXTENDED_HISTORY
You can set limits on how many commands the shell should remember in the session and in the history file with the HISTSIZE
and SAVEHIST
variables:
SAVEHIST=5000
HISTSIZE=2000
When the shell reaches this limit the oldest commands will be removed from memory or the history file.
By default, when you exit zsh
(for example, by closing the window or tab) this particular instance of zsh
will overwrite an existing history file with its history. So when you have multiple Terminal windows or tabs open, they will all overwrite each others’ histories eventually.
You can tell zsh
to use a single, shared history file across the sessions and append to it rather than overwrite:
# share history across multiple zsh sessions
setopt SHARE_HISTORY
# append to history
setopt APPEND_HISTORY
Furthermore, you can tell zsh
to update the history file after every command, rather than waiting for the shell to exit:
# adds commands as they are typed, not at shell exit
setopt INC_APPEND_HISTORY
When you use a shared history file, it will grow very quickly, and you may want to use some options to clean out duplicates and blanks:
# expire duplicates first
setopt HIST_EXPIRE_DUPS_FIRST
# do not store duplications
setopt HIST_IGNORE_DUPS
#ignore duplicates when searching
setopt HIST_FIND_NO_DUPS
# removes blank lines from history
setopt HIST_REDUCE_BLANKS
(some of these are redundant)
Most of the time you will access the history with the up arrow key to recall the last command, or maybe a few more steps. You can search through the history with ctrl-R
In zsh
, you can also use the !!
history substitution, which will be replaced with the entire last command. This is most commonly used in combination with sudo
:
% systemsetup -getRemoteLogin
You need administrator access to run this tool... exiting!
% sudo !!
sudo systemsetup -getRemoteLogin
Password:
Remote Login: On
By default, the shell will show the command it is substituting before it is run. But at that point, it is too late to make any changes. When you set the HIST_VERIFY
option, zsh
will show the substituted command in the prompt instead, giving you a chance to edit or cancel it, or just confirm it.
% systemsetup -getRemoteLogin
You need administrator access to run this tool... exiting!
% sudo !!
% sudo systemsetup -getRemoteLogin
Password:
Remote Login: On
This works for other history substitutions such as !$
or !*
, as well. You can find all of zsh
’s history expansions in the documentation.
Correction
When you mistype a command or path, the shell is usually unforgiving. In zsh
you can enable correction. Then, the shell will make a guess of what you meant to type and ask whether you want do that instead:
% systemprofiler
zsh: correct 'systemprofiler' to 'system_profiler' [nyae]?
Your options are to
n
: execute as typed
y
: accept and execute the suggested correction
a
: abort and do nothing
e
: return to the prompt to continue editing
I have found this far less annoying and far more useful than I expected. Especially, since it works together with AUTO_CD
:
% Dekstop
zsh: correct 'Dekstop' to 'Desktop' [nyae]?
You enable zsh
correction with these options:
setopt CORRECT
setopt CORRECT_ALL
Reverting to defaults
Most of the changes mentioned here affect the interactive shell and will have little impact on zsh
scripts. However, there are some options that do affect the behavior of things like variable substitutions which will affect scripts.
You can revert the options for the current shell to the default settings with the following command:
emulate -LR zsh
We encountered this command earlier when we listed the default settings. The -l
option will list the settings rather than apply them.
If in doubt, it may be useful to add this at the beginning of your zsh
scripts.
Next
In the next part we will take a look at aliases and functions.