Customizing Zsh
This article is automatically translated by LLM, so the translation may be inaccurate or incomplete. If you find any mistake, please let me know.
You can find the original article here .
Zsh is a well-known shell, and thanks to Oh My Zsh, it allows users to easily have a beautiful and convenient shell.
I originally used it too, but I found its performance a bit slow, and its customization wasn't as strong as I wanted. So, I researched how to achieve some custom functions by writing my own .zshrc
and decided to share my approach.
To see what my shell looks like when I write articles, you can check: https://asciinema.org/a/7vvdQ5xpUNJu3FAWdLvUIDsDc
This is the .zshrc
I had when writing this article. You can check it out if you're interested. This dotfiles
repo also includes other dotfiles I use on WSL.
Zinit and Third-Party Plugins
Although I moved away from Oh My Zsh, there are still some features from Oh My Zsh that I wanted to continue using. So, I looked for a way to use only parts of its functionality. After some research, I discovered that Zsh has plugin managers like Antigen, and Zinit is one of them.
Zinit is a powerful plugin manager with many features, but it is also quite complex and has a learning curve. It's recommended to start by reading the Introduction on the official website. It also has a turbo mode that delays loading some features to improve Zsh startup performance, making it ready to use immediately.
I used it to load some Oh My Zsh features and other useful plugins, and for the prompt, I used Powerlevel10k, also loaded with Zinit. Finally, I used it to automatically download and load some handy tools like jq
and rg
.
# ZInit
source ~/.zinit/bin/zinit.zsh
zinit snippet OMZL::history.zsh # Blocking 載入 Oh My Zsh 歷史的功能
zinit wait lucid for \ # Turbo mode 載入其他的一些 Oh My Zsh 插件
OMZL::key-bindings.zsh \
OMZP::sudo
zinit wait lucid light-mode for \
zsh-users/zsh-history-substring-search \ # 按下 "up" 時會自動針對目前輸入的指令搜尋
atinit"zicompinit; zicdreplay" \
zdharma/fast-syntax-highlighting \ # Syntax Highlighting
atload"_zsh_autosuggest_start" \
zsh-users/zsh-autosuggestions \ # 自動從歷史中建議
blockf atpull"zinit creinstall -q ." \
zsh-users/zsh-completions # 各種常用的指令 Completion
zinit ice depth=1
zinit light romkatv/powerlevel10k # Powerlevel10k
zinit lucid for \
as"program" pick"bin/n" tj/n # 類似 nvm 的 node.js 管理器
zinit lucid from"gh-r" as"program" for \
pick"jq-*" mv"jq-* -> jq" stedolan/jq \ # 處裡 json
pick"ripgrep-*-linux-*" extract mv"*/rg -> rg" BurntSushi/ripgrep \ # grep 加強版
pick"exa-linux-*" extract mv"exa-* -> exa" ogham/exa \ # ls 加強版
pick"bat-linux-*" extract mv"*/bat -> bat" @sharkdp/bat # cat 加強版
WSL Related Settings
I mainly use this in a WSL2 environment, so there are some settings related to WSL.
# WSL specific
if [[ -v WSL_DISTRO_NAME ]] then
alias ex=/mnt/c/Windows/explorer.exe # 可以用 ex <directory> 來打開檔案總管
# Copy .ssh
upd_ssh(){ # 從 Windows 的 .ssh 中複製檔案到 ~/.ssh
rm -rf ~/.ssh
/bin/cp -rf "/mnt/c/Users/$(whoami)/.ssh" ~/.ssh
chmod 600 ~/.ssh/*
}
# Windows Path handling for performance (見下文)
WIN_PATH=$(echo $PATH | tr ':' '\n' | grep '/mnt/c' | tr '\n' ':' | sed 's/.$//')
export PATH=$(echo $PATH | tr ':' '\n' | grep -v '/mnt/c' | tr '\n' ':' | sed 's/.$//')
zinit wait'3' lucid atinit'export PATH="$PATH:$WIN_PATH"' nocd for /dev/null
fi
Performance Issues of Zsh in WSL2
By default, WSL automatically adds Windows PATH
to WSL PATH
, which should generally be fine. However, Zsh automatically searches your PATH
for autocomplete, and accessing NTFS in WSL2 is slow, leading to startup performance issues. Refer to WSL#4256.
One solution is to disable adding Windows PATH
as shown below, but this makes it inconvenient to access some Windows features.
# /etc/wsl.conf
[interop]
appendWindowsPath = false
So, I wondered if I could use Zinit's delay feature to delay the PATH
, and it indeed achieved my goal. The result is the three lines above:
WIN_PATH=$(echo $PATH | tr ':' '\n' | grep '/mnt/c' | tr '\n' ':' | sed 's/.$//')
export PATH=$(echo $PATH | tr ':' '\n' | grep -v '/mnt/c' | tr '\n' ':' | sed 's/.$//')
zinit wait'3' lucid atinit'export PATH="$PATH:$WIN_PATH"' nocd for /dev/null
SSH
Agent
# Keychain
if (( $+commands[keychain] )) && [[ -a ~/.ssh/id_rsa ]] then
eval `keychain --quiet --eval --agents ssh id_rsa`
fi
This part is just a few lines above, but it uses keychain to retain the agent's session, so you need to install it first. On Debian-based systems, you can install it with the following command:
sudo apt install keychain
Autocomplete
Zsh's SSH autocomplete by default comes from many places, including domain names in /etc/hosts
, but not from ~/.ssh/config
...
So, I wanted it to autocomplete only the servers in my ~/.ssh/config
, so I did some research and came up with the following snippet.
# Fix ssh autocomplete
zstyle ':completion:*:ssh:argument-1:*' tag-order hosts
h=()
if [[ -r ~/.ssh/config ]]; then # 讀取 config 然後把 Host 弄出來
h=($h ${${${(@M)${(f)"$(cat ~/.ssh/config)"}:#Host *}#Host }:#*[*?]*})
fi
if [[ $#h -gt 0 ]]; then
zstyle ':completion:*:ssh:*' hosts $h
zstyle ':completion:*:slogin:*' hosts $h
fi