1. Overview

In this tutorial, we’ll explore the various configuration files for Zsh, such as .zshenv.zprofile, and .zshrc. Besides, we’ll also go through example configuration files to get a better understanding.

Finally, we’ll discuss the ZDOTDIR environment variable.

2. Zsh Configuration Files

Zsh has a number of configuration files that allow us to customize its behavior and appearance.

Usually, these files are located in the user’s home directory and have the dot (.) prefix. Therefore, they’re hidden by default.

Let’s go through each of these configuration files to see how it works and what purpose it serves.

2.1. .zshenv

.zshenv contains exported environment variables that are available to other programs in a Zsh session. For instance, we can set the locations for other programs’ dotfiles. In addition, the file can also include the ZDOTDIR environment variable.

It’s read very early in the shell init process so that the variables are readily available.

Here’s a simple .zshenv file:

export XDG_CONFIG_HOME="$HOME/.config"
export ZDOTDIR="$HOME/.config/zsh"

export EDITOR="nano"

export LC_ALL="en_US.UTF-8"
export GOPATH="$HOME/.local/share/go"

export LESS="-R"

umask 022

2.2. .zprofile

.zprofile is used for login shells. Basically, it’s identical to .zlogin, which we’ll cover later. However, .zlogin is sourced after .zshrc. In contrast, .zprofile is sourced before .zshrc.

Conventionally, the .zlogin and .zprofile aren’t used together. Nonetheless, it’s possible to have both.

In .zprofile, we can set environment variables as well as other shell-specific options.

Also, we can create required directories and source additional shell scripts. Moreover, we can add paths to the $PATH environment variables.

Here’s an example of a simple .zprofile file:

export RUSTUP_HOME="$HOME/Library/Rust"
export WGETRC="$XDG_CONFIG_HOME/wget/wgetrc"
export ZDOTDIR="$HOME/Library/Shell"
export NVIM_CONFIG="$XDG_CONFIG_HOME/nvim/lua"

! [ -f "$WGETRC" ] && mkdir -p "$XDG_CONFIG_HOME/wget" && touch "$WGETRC"
! grep -q hsts-file "$WGETRC" && echo "hsts-file = $XDG_CACHE_HOME/wget-hsts" >> "$WGETRC"

export PATH="$PATH:$GOPATH/bin"
export PATH="$PATH:$CARGO_HOME/bin"

export PYTHONDONTWRITEBYTECODE=1

Sometimes, users prefer to use the generic .profile file instead of .zprofile, which is sourced just before .profile. For that reason, the user will symlink the .zprofile to .profile. This way, it makes more sense to put the generic options and variables inside the .profile and the Zsh-specific variables inside .zshenv.

2.3. .zshrc

.zshrc is used for interactive shells. So, it contains behavior and appearance settings specific to interactive shells. For instance, if we need to change the shell prompt or add aliases, we’d put them inside this file.

.zshrc is executed every time we open a new interactive shell session. Therefore, it would run each time we open a new terminal window. For that reason, we should avoid filling it out with instructions that are time-consuming to avoid performance issues.

In addition, if we need to extend Zsh, we’ll source the Zsh plugins in this file. As an example, here’s an advanced .zshrc file:

autoload -U colors && colors
PS1="%{$fg[green]%}[%{$reset_color%} %{$fg[blue]%}%1~%{$reset_color%} %{$fg[green]%}]%{$reset_color%}$%b "
setopt autocd
setopt interactive_comments

export HISTSIZE=268435456
export SAVEHIST="$HISTSIZE"
export HISTFILE="$ZDOTDIR/.zsh_history"
setopt INC_APPEND_HISTORY

bindkey '^R' history-incremental-search-backward

autoload -U compinit
zstyle ':completion:*' menu select
zmodload zsh/complist
compinit
_comp_options+=(globdots)

bindkey -v
export KEYTIMEOUT=1

bindkey -M menuselect 'h' vi-backward-char
bindkey -M menuselect 'k' vi-up-line-or-history
bindkey -M menuselect 'l' vi-forward-char
bindkey -M menuselect 'j' vi-down-line-or-history
bindkey -v '^?' backward-delete-char

function zle-keymap-select () {
    case $KEYMAP in
        vicmd) echo -ne '\e[1 q';;
        viins|main) echo -ne '\e[5 q';;
    esac
}
zle -N zle-keymap-select
zle-line-init() {
    zle -K viins # initiate `vi insert` as keymap (can be removed if `bindkey -V` has been set elsewhere)
    echo -ne "\e[5 q"
}
zle -N zle-line-init
echo -ne '\e[5 q'
preexec() { echo -ne '\e[5 q' ;}

bindkey -s '^a' 'bc -lq\n'
bindkey -s '^f' 'cd "$(dirname "$(fzf)")"\n'

bindkey '^[[P' delete-char

autoload edit-command-line; zle -N edit-command-line
bindkey '^e' edit-command-line

source /usr/local/opt/zsh-fast-syntax-highlighting/share/zsh-fast-syntax-highlighting/fast-syntax-highlighting.plugin.zsh
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh

alias p="ping google.com"
alias ll="/usr/local/bin/lsd --long --group-dirs=first"
alias lla="/usr/local/bin/lsd --long --all --group-dirs=first"
alias llt="/usr/local/bin/lsd --tree --all"
alias shell="vim $ZDOTDIR/.zshrc"
alias profile="vim $HOME/.zprofile"
alias rm="trash"

pk() {
  pgrep -i "$1" | sudo xargs kill -9
}

Let’s break this down:

  • At the top, we define the prompt and enable colors
  • Next, we export Zsh history-specific environment variables
  • Afterward, we define the bindings for our shell as well as bindings that enable us to use the shell using Vim motions
  • Next, we source the syntax highlighting plugin and define aliases for common commands
  • Finally, we define our own custom shell function

It’s a best practice to modularize this file to avoid clutter. So, we can put the logic for aliases, plugins, and bindings into their separate files and simply source them in the .zshrc file.

2.4. .zlogin

.zlogin is used for login shells. It’s sourced after .zshrc. So, usually, we put the instructions for starting a desktop session in this file. However, some systems start the desktop session on boot, so this file isn’t used as much as the other configuration files.

Here’s a simple .zlogin file:

# Start the i3 window manager
exec i3

2.5. .zlogout

.zlogout is sourced once we log out of a session. Typically, it performs cleanup or runs specific commands when we log out.

The following .zlogout file clears the terminal screen and prints a farewell message once we exit the shell:

clear
echo "Goodbye, $(whoami)! Have a great day!"

2.6. Summary

The following table outlines the differences between these files:

Config File

Description

.zshenv

Contains exported environment variables

.zprofile

Contains environment variables and shell-specific options

.zshrc

Contains settings for interactive shell

.zlogin

Contains instructions to execute on session login

.zlogout

Contains instructions to execute on session logout

3. The ZDOTDIR Environment Variable

The ZDOTDIR environment variable specifies the configuration directory for Zsh. It’s not necessary, but it’s useful in cases like decluttering our home directory.

We can define it either in .profile or .zprofile:

export XDG_CONFIG_HOME="$HOME/.config"
export ZDOTDIR="$XDG_CONFIG_HOME/.config/zsh"

Once defined, Zsh looks for .zshrc in the given directory.

4. Conclusion

In this article, we learned the purpose of various configurations for Zsh. Besides, we also discussed simple Zsh configuration files. Finally, we understood the purpose of the ZDOTDIR environment variable.