自訂 Zsh

Zsh 是個蠻有名的 Shell,因為有 Oh My Zsh 的緣故可以讓人很輕鬆的有個漂亮、方便好用的 Shell。

我原本也是使用這個,不過我覺得它的效能有點慢,自訂性其實也沒那麼強,所以就研究看看了怎麼透過自己寫自己的 .zshrc 去達成一些自訂功能,然後分享一下我的作法。

要看我寫文章時的 Shell 長怎樣的可以看: https://asciinema.org/a/7vvdQ5xpUNJu3FAWdLvUIDsDc

這個是我寫文章當下的 .zshrc,想看的可以到這邊看。而這個 dotfiles 的 repo 也有包含其他我用在 WSL 上的一些 dotfiles。

Zinit 與第三方插件

我雖然是從 Oh My Zsh 轉走,不過還是有些 Oh My Zsh 的功能我還想繼續用,所以打算找個方法可以只使用部分的功能。然後經過一些研究就發現說原來 Zsh 還有 Plugin Manager 之類的東西,如 Antigen 之類的,而 Zinit 就是其中之一。

Zinit 是一款功能多、強大的一個 Plugin Manager,但使用起來也很複雜,有一定的學習門檻。建議先從官網的 Introduction 開始看過一遍會比較好。
還有它有個 turbo mode 的部分也相當重要,會延遲加載一些功能來提升開啟 Zsh 的效能,以達成馬上就能用的效果。

我使用了它來載入了一些 Oh My Zsh 的一些功能以及一些其他的好用插件,而 Prompt 則是使用了 Powerlevel10k,一樣使用了 Zinit 來載入的。
最後我還用它來自動下載並載入一些方便的工具,如 jq, rg 之類的。

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
# 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 相關設定

我使用這個的地方主要是在一個 WSL2 的環境中使用的,所以會有些和 WSL 有關的設定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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

關於 Zsh 在 WSL2 中的效能問題

WSL 預設是會自動把 Windows 的 PATH 中加入 WSL 的 PATH 的,在一般情況下應該要不成問題。但是 Zsh 會自動為了 Autocomplete 去你的 PATH 中搜尋,而 WSL2 中存取 NTFS 的速度又很慢就導致了啟動的效能問題。參考 WSL#4256

解決方法之一是直接把加入 Windows PATH 的功能關閉如下,但這個又會讓你沒辦法方便的存取一些 Windows 的功能。

1
2
3
# /etc/wsl.conf
[interop]
appendWindowsPath = false

所以我就想能不能透過 Zinit 的延遲功能來幫我把 PATH 給延遲掉,然後也真的能達成我想要的目標。結果就是上面的那三行:

1
2
3
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

1
2
3
4
# Keychain
if (( $+commands[keychain] )) && [[ -a ~/.ssh/id_rsa ]] then
eval `keychain --quiet --eval --agents ssh id_rsa`
fi

這部分其實就上面短短的幾行,不過會使用到 keychain 來保留 agent 的 session 所以需要先安裝。在類 Debian 的系統上用下面的指令就能安裝了:

1
sudo apt install keychain

Autocomplete

Zsh 的 SSH 自動補全預設會來自很多的地方,包括 /etc/hosts 裡面的域名都會自動補全,但 ~/.ssh/config 卻不會…

所以我想讓它只 autocomplete 我 ~/.ssh/config 裡面的伺服器就好,所以就稍微研究了一下弄出了下面這一段。

1
2
3
4
5
6
7
8
9
10
# 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