Lightweight, clean, simple. This is what I’m looking for in a zsh configuration

Starting the shell

First, let’s review the shell login and interaction

Interaction

The shell may or may not interact. Normal startup requires interaction if you type a command and wait for it to return, but no interaction is required if you run the script directly

echo $- determines if there is an interaction, each letter represents an attribute. i is for interactive (interactive)

The interactive shell will have PROMPT, so there will be a PS1 variable, and you can use the PS1 variable to determine if it is interactive or not.

Login

login shell, bash by shopt, zsh by setopt. (zsh’s echo $- can also determine if you are logged in). Remember that setopt is the command that sets all of zsh’s features with setopt, unsetopt noxxxorunsetopt`.

Open terminal and ssh are both logged-in shells by default. directly executing bash / zsh is a non-login shell, specify the ---login argument. The su command is also non-login by default, specify the su - or su -l argument. - and -l are exactly the same

The combination of interactive and login permutations can be divided into four cases.

  • non-interactive non-login
  • non-interactive login
  • Interactive non-login
  • Interactive login

They also load different configuration files. To simplify the process, just ignore the other files and use only bashrc or zshrc. Delete all the others (.bash_profile , .bash_login , .zprofile , .zlogin all of them), which will definitely read the .bashrc or .zshrc file

motd

Anyone familiar with Linux will be familiar with the full name of motd, Message Of The Day

Put the contents in /etc/motd and display the contents every time you log in

The motd itself is plain text, and traditionally motd is only plain text. Of course, we can do something similar to motd by displaying something when the shell starts executing commands

For example, use cowsay to display a welcome message (put this command in the shell’s configuration file)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ echo "Hello! ${USERNAME}" | cowsay -f tux
 ______________
< Hello! metal >
 --------------
   \
    \
        .--.
       |o_o |
       |:_/ |
      //   \ \
     (|     | )
    /'\_   _/`\
    \___)=(___/

Themes and configuration framework

If you don’t use any theme, you will have to deal with the $PS1 variables yourself, and there are four variables in PS. Consider the complexity of setting them up yourself. The preferred option is of course to use the big boys’ configuration

zsh is basically the same as it was decades ago due to its history, but nowadays zsh has a lot of very powerful features. It’s just that the default is not to turn on

oh-my-zsh

The most famous configuration framework for zsh. Configuring zsh can even be said for oh-my-zsh and non-oh-my-zsh

automatically sets the color section, just enable it to override the ugly as hell default color scheme. The default configuration is perfect

A lot of useful functions and themes are integrated (personally I recommend the ys theme), more extreme users will even use the ramdom theme and choose one randomly each time you turn it on to ensure freshness

oh-my-zsh has a lot of stuff to manage. oh-my-zsh’s various plugins are basically full of aliases. it is recommended not to use any of them

prezto

An alternative to oh-my-zsh, or an alternative to it. It’s a bit lighter than oh-my-zsh.

zimfw

Use ruby’s erb stencil engine to compile the final configuration file.

Configure the enabled plugins in zimrc, then compile to produce the ~/.zshrc file. You’ll have to recompile every time you make a change, but that’s not a big deal.

By default, the git plugin will be enabled, so be careful to disable it (I recommend disabling it), it’s all git aliases.

grml-zsh-config

The default zsh configuration for Grml OS, which is a Debian-based Linux distribution. It is mainly designed for live CDs

I like grml’s configuration, it’s simple and lightweight. This is the new age default. grml does not include the ability to manage plugins. It is a pure zshrc file

grml has several themes built in.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 列出主题
prompt -l

# 预览主题
prompt -p adam2

# 设置主题
prompt -s adam2

# 关闭主题,使用其他的主题
prompt off

The grml zsh configuration is very flexible, and the default grml theme supports vsc (git show current branch) which allows you to customize the various items displayed. For example, to make prompt display two lines like this.

1
2
3
4
5
# Set Theme
# http://bewatermyfriend.org/p/2013/001/
zstyle ':prompt:grml:left:setup' items rc change-root user at host path vcs newline percent

prompt grml

powerlevel series

Themes for the zsh version of the powerline series

powerline series

powerlevel9k is an extremely complex theme that will definitely catch your eye, or should be called the most powerful zsh theme on the planet. The most distinctive thing about this theme is probably the integration with various programming environments, even recognizing some web frameworks, VM environments of programming languages

Later on, someone developed powerlevel10k, which is just like powerlevel9k, but with good performance.

powerlevel10k

pure

This is a very nice theme. It calls itself prompt, but it should be classified as a theme. Because for the shell, the topic is prompt

Separate prompt

It’s too complicated to implement a complex prompt in the shell, so let’s just put a layer on the outside. Then came a new idea. Put an interactive shell on top of the old shell. I’ve taken it upon myself to call it “detached prompt”. The detached prompt is powerful, though. But do we really need such a powerful prompt?

Liquid Prompt

Liquid Prompt was probably the first to do this, making prompt a separate component. Configuration management is independent of other functions, and supports bash and zsh

spaceship-prompt

This is a prompt optimized for zsh, but still a pure shell implementation, so you could say that spaceship directly raises the complexity of prompt by one level. Towards a separate prompt for front and back ends

starship

Just a prompt, using a rust implementation that is exceptionally fast and efficient. This acts as an interactive front-end, with a shell as the back-end. It supports most shells, and has moved towards a completely separate prompt.

starship

oh-my-posh

It was originally written specifically for powershell, but was later rewritten in golang to make it a separate prompt for the front and back ends, and you can specify a different back end

Plugin Manager

I personally don’t use this plugin manager, it just makes it easier to toss back and forth, but it’s not right to toss shell configuration every day

antigen

This is the official zsh plugin management tool, and it’s absolutely clean. It’s a pure package manager. Oh no, it has special adaptations for oh-my-zsh and prezto

antibody

A package manager written in golang. As you can see from the name, it’s a counterpart to antigen

zplug

A vim-plug-like plugin manager

zplug

zgen

A lightweight plugin manager with only 500 lines of code

zinit

is a plugin manager implemented in C, with very high performance and probably the fastest. zplugin and zinit are one project, zplugin is the previous name.

Like zimfw, you can compile plugins to run faster. Unlike zimfw, no configuration is provided, this is just the plugin manager and compiler.

zsh plugins

fish has a lot of useful features, code highlighting, auto-suggestions. fish has a lot of interactive features that are already implemented in zsh

zsh-users family

zsh-users is an official zsh repository with several great plugins that are integrated into basically all zsh configuration frameworks:

  • zsh-syntax-highlighting (code highlighting)
  • zsh-autosuggestions (auto-suggestions)
  • zsh-history-substring-search (prefix search)

And for example: zsh-users/zsh-apple-touchbar My MBP doesn’t have a touchbar so I won’t comment on it

zinit series

  • fast-syntax-highlighting (high performance code highlighting)
  • history-search-multi-word (enhanced version of Crtl-R)

Plugins written by the author of zinit, can be used here to replace the zsh-users series

thefuck

to correct spelling errors. There seems to be a lot of people who use a plugin, I don’t use this and don’t recommend it.

I’d like to know what the mentality of people who use this plugin is fuck, fuck, fuuuu…

autojump

According to the use of habits to automatically jump to the directory, he will maintain a database to record which directories are usually used

Custom Functions

MacOS needs to set the default shortcuts, Crtl + left/right arrow is occupied by Mission Control function

Change the Crtl + left/right arrow shortcut in System Preferences > Keyboard > Shortcuts, otherwise the ability to jump a word will be lost. I changed it to Crtl + Command + left/right arrow. I remember that this is the default key for switching virtual desktops in Deepin Linux

Turn off flow control

The shell and terminal (iTerm2 ignores this feature) need to support both to see the effect, most terminals are supported. shell is enabled by default

This is an old feature, historically (and still is in some terminals), Ctrl-S pauses output while Ctrl-Q continues. From today’s point of view, this feature is basically useless. It also takes up two shortcut keys

The default setting in many frameworks is not to disable flowcontrol. Only oh-my-zsh is set to be disabled in the configuration framework

Let’s turn it off: setopt noflowcontrol

1
2
3
4
5
# 关掉 flowcontrol
unsetopt flowcontrol

# 上面的设置在 Tmux 里面无效
stty -ixon

push-line

I don’t know how to describe this function, but oh-my-zsh has a Ctrl-Q shortcut that saves the current input, then executes a new command, and releases the saved content afterwards. This feature is very useful.

Many setups and frameworks will set Ctrl-Q to push-line.

1
bindkey "\eq" push-line

But push-line can’t handle the here doc case.

You can set it to push-line-or-edit and it will handle here docs.

1
bindkey "\eq" push-line-or-edit

push-line and push-line-or-edit are useful, and are built-in features of zsh. They are not enabled by default, and there are many other features in zsh that are not enabled by default. Before writing a new feature, it’s best to check man zshzle to see if it’s available.

Quick sudo

Perhaps you will often sudo !! to use sudo to execute the previous command.

sudo is used a lot, so let’s customize a shortcut.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
sudo-command-line() {
  [[ -z $BUFFER ]] && zle up-history
  local cmd="sudo "
  if [[ ${BUFFER} == ${cmd}* ]]; then
    CURSOR=$(( CURSOR-${#cmd} ))
    BUFFER="${BUFFER#$cmd}"
  else
    BUFFER="${cmd}${BUFFER}"
    CURSOR=$(( CURSOR+${#cmd} ))
  fi
  zle reset-prompt
}

zle     -N   sudo-command-line
# Ctrl-S
bindkey '^S' sudo-command-line

This way you can quickly add sudo directly with Ctrl-S, and automatically remove sudo if it is preceded by sudo.

grml comes with a similar configuration, but it only supports adding sudo, so there is a difference between the way I changed it and grml.

Now the grml zsh configuration is the same as mine grml/zshrc#119

Command Complementary Load Optimization

The kubectl command is typical, this patch file is very large, with tens of thousands of lines

1
2
kubectl completion zsh | wc -l
13090

The official documentation puts this in .zshrc, which is a very poor way of writing performance

1
source <(kubectl completion zsh)

Generating a file and putting it in $FPATH would be a better option.

1
kubectl completion zsh > ~/.zsh-completions/_kubectl.zsh

Put this in the zsh configuration file.

1
2
3
if [[ -d "${HOME}/.zsh-completions" ]]; then
  FPATH=${HOME}/.zsh-completions:$FPATH
fi

Or use the Lazyload method, where you define a placeholder function first and then load it on input. But $FPATH is already a lazyload. I’ve always thought it was a mistake to use Lazyload for completion (I’ve seen people use it this way, feel free to argue me down).

fzf and shell

fzf provides a shortcut to the shell, you need to enable this feature yourself.

fzf and shell

1
2
3
4
5
# brew --prefix fzf
# source $(brew --prefix fzf)/shell/completion.zsh
# source $(brew --prefix fzf)/shell/key-bindings.zsh
source /usr/local/opt/fzf/shell/completion.zsh
source /usr/local/opt/fzf/shell/key-bindings.zsh

Three binding shortcuts are provided

  • Ctrl-R Enhanced history search
  • Crtl-T fuzzy path selection
  • Alt-C fuzzy path jumping

Complement function provides ** and then press <TAB> syntax, use fzf to select complement

Crtl-T is actually a zsh default shortcut, the fuzzy path selection function provided by fzf is too infrequently used, so here it is set back to the default shortcut.

1
bindkey '^T' transpose-chars

For Apple computers, the Alt-C shortcut has to be handled

1
2
3
4
# Default ALT-C, For Mac OS: Option-C
if [[ `uname` == "Darwin" ]]; then
  bindkey 'ç' fzf-cd-widget
fi

Recent directories

Remember Elvish’s Crtl-L list selection to jump to the most recent directory?

Use fzf to implement one, and since the Crtl-L shortcut is already occupied, use ALT-X

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fzf-dirs-widget() {
  # eval cd $(dirs -v | fzf --height 40% --reverse | cut -b3-)
  local dir=$(dirs -v | fzf --height ${FZF_TMUX_HEIGHT:-40%} --reverse | cut -b3-)
  if [[ -z "$dir" ]]; then
    zle redisplay
    return 0
  fi
  eval cd ${dir}
  local ret=$?
  unset dir # ensure this doesn't end up appearing in prompt expansion
  zle reset-prompt
  return $ret
}
zle     -N    fzf-dirs-widget

# Default ALT-X, For Mac OS: Option-X
if [[ `uname` == "Darwin" ]]; then
  bindkey '≈' fzf-dirs-widget
else
  bindkey '\ex' fzf-dirs-widget
fi

Elvish Crtl-N shortcut to start the navigation mode. Similar to ranger or nnn’s tui to select a directory inside

This feature can be adapted with ranger or nnn. Both projects provide examples, but I prefer ranger, nnn is faster. There is also a very lean implementation of a pure shell vifon/deer

  • ranger

    ranger

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    # Fork from: https://github.com/ranger/ranger/blob/master/examples/shell_automatic_cd.sh
    ranger_cd() {
    local temp_file="$(mktemp -t "ranger_cd.${USERNAME}")"
    ranger --choosedir="$temp_file" -- "${@:-$PWD}"
    if chosen_dir="$(cat -- "$temp_file")" && [ -n "$chosen_dir" ] && [ "$chosen_dir" != "$PWD" ]; then
        cd -- "$chosen_dir"
    fi
    rm -f -- "$temp_file"
    }
    
    # This binds Ctrl-N to ranger_cd:
    bindkey -s '^N' 'ranger_cd\n'
    
  • nnn

    Some packages include this script as well, such as Archlinux, so you can just source.

    1
    2
    
    source /usr/share/nnn/quitcd/quitcd.bash_zsh
    bindkey -s '^
    

    MacOS homebrew does not provide a quitcd script, so you need to set it up yourself.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    # Frok from: https://github.com/jarun/nnn/blob/master/misc/quitcd/quitcd.bash_zsh
    nnn_cd () {
    # Block nesting of nnn in subshells
    if [ -n $NNNLVL ] && [ "${NNNLVL:-0}" -ge 1 ]; then
        echo "nnn is already running"
        return
    fi
    
    # The default behaviour is to cd on quit (nnn checks if NNN_TMPFILE is set)
    # To cd on quit only on ^G, remove the "export" as in:
    #     NNN_TMPFILE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.lastd"
    # NOTE: NNN_TMPFILE is fixed, should not be modified
    export NNN_TMPFILE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.lastd"
    
    # Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn
    # stty start undef
    # stty stop undef
    # stty lwrap undef
    # stty lnext undef
    
    nnn "$@"
    
    if [ -f "$NNN_TMPFILE" ]; then
        . "$NNN_TMPFILE"
        rm -f "$NNN_TMPFILE" > /dev/null
    fi
    }
    
    bindkey -s '^N' 'nnn_cd\n'
    
  • lf

    If you think ranger is slow written in python and you don’t like nnn. Then you can try lf written by golang.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    Frok from: https://github.com/gokcehan/lf/blob/master/etc/lfcd.sh
    
    lfcd () {
    tmp="$(mktemp)"
    lf -last-dir-path="$tmp" "$@"
    if [ -f "$tmp" ]; then
        dir="$(cat "$tmp")"
        rm -f "$tmp"
        if [ -d "$dir" ]; then
        if [ "$dir" != "$(pwd)" ]; then
            cd "$dir"
        fi
        fi
    fi
    }
    
    bindkey -s '^N' 'lfcd\n'
    
  • hunter

    This is a command line file manager written in Rust. There is no way to link with the shell yet, maybe in the future this feature will be available.

Variables

We’ve certainly all seen this written before

1
2
3
if [ -d "$HOME/go/bin" ]; then
  PATH="$HOME/go/bin:$PATH"
fi

There are more paths, let’s unify them

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Load path
_enabled_paths=(
  'go/bin'
  '.bin'
  '.local/bin'
  '.cargo/bin'
)

for _enabled_path in $_enabled_paths[@]; do
  [[ -d "$HOME/${_enabled_path}" ]] && PATH="$HOME/${_enabled_path}:$PATH"
done

export LANG=en_US.UTF-8
export LC_CTYPE=en_US.UTF-8

export EDITOR='vi'
export VISUAL='nvim'

VISUAL and EDITOR, will give priority to VISUAL and will use EDITOR only when it is not available.

Performance

This is several orders of magnitude faster than oh-my-zsh, which is slower than hell.

1
2
3
4
5
6
for i in $(seq 1 5); do time /bin/zsh -i -c exit; done
/bin/zsh -i -c exit  0.11s user 0.07s system 86% cpu 0.211 total
/bin/zsh -i -c exit  0.12s user 0.05s system 93% cpu 0.188 total
/bin/zsh -i -c exit  0.11s user 0.06s system 91% cpu 0.187 total
/bin/zsh -i -c exit  0.12s user 0.06s system 89% cpu 0.199 total
/bin/zsh -i -c exit  0.12s user 0.06s system 84% cpu 0.219 total

What configuration do I use

The pictures in this article don’t say much, I’ve selected some visually appealing plugins for the pictures, in fact, my personal configuration is very lean. The most practical configurations don’t look anything high-end in the pictures. The practical configuration that really works looks pretty much the same as the default

Good prompt.

  • current path
  • git branch (should be optional, as a software developer, this is necessary)
  • The result of the last command execution $?
  • If it’s a server, it’s better to show the hostname and user
  • personal preference, preference for two lines
  • Try not to have special fonts and special characters (ascii characters)

I use the default configuration of zimfw on my main machine MPB, plus a little bit of self-defined functions, and the default configuration of grml on all other machines.

If you are interested in my configuration you can come here: a-wing/dotfiles#zsh