Shell 配置是 dotfile 管理裡最基礎也最常失控的一層。.zshrc.bashrc 通常是開發者第一個開始客製的檔案,也是最容易長成數百行無結構巨檔的對象。

Zsh vs Bash 的配置檔載入順序

理解配置檔的載入順序是結構化拆分的前提。不知道哪個檔案在什麼時機被讀取,就無法判斷設定該放在哪。

Bash 的載入順序

Bash 區分 login shell 和 non-login shell,兩者讀取的檔案不同:

  • Login shell(SSH 進來、bash --login):讀 ~/.bash_profile(如果不存在,依序嘗試 ~/.bash_login~/.profile
  • Non-login interactive shell(開一個新終端機視窗):讀 ~/.bashrc
  • 常見做法:在 ~/.bash_profile 裡 source ~/.bashrc,確保設定不管怎麼進來都一致

Zsh 的載入順序

Zsh 的載入鏈比 Bash 更細緻:

  1. ~/.zshenv — 每次都讀(login、non-login、script 都會),放環境變數
  2. ~/.zprofile — 只有 login shell 讀,對應 Bash 的 ~/.bash_profile
  3. ~/.zshrc — interactive shell 讀,放 alias、function、prompt、plugin
  4. ~/.zlogin — login shell 在 .zshrc 之後讀(少用)
  5. ~/.zlogout — logout 時讀(少用)

實務上 90% 的設定都進 .zshrc,環境變數(PATHEDITOR)放 .zshenv

結構化拆分:從單一巨檔到模組化

一個典型的失控 .zshrc 長這樣:PATH 設定、alias、function、plugin 載入、prompt 配置、各種工具的 eval/source 全混在一起,改一個東西要在五百行裡找位置。

模組化的目標是依職責拆檔,.zshrc 本身只負責 source 這些模組:

 1# ~/.zshrc — 只做 source,不放具體設定
 2
 3# 環境變數(PATH 在 .zshenv,這裡放其他 interactive 專用的)
 4source "$HOME/.config/zsh/env.zsh"
 5
 6# Alias
 7source "$HOME/.config/zsh/aliases.zsh"
 8
 9# Function
10source "$HOME/.config/zsh/functions.zsh"
11
12# Plugin manager
13source "$HOME/.config/zsh/plugins.zsh"
14
15# Prompt / theme
16source "$HOME/.config/zsh/prompt.zsh"
17
18# 工具整合(fzf, nvm, pyenv, etc.)
19source "$HOME/.config/zsh/tools.zsh"
20
21# 機器專屬設定(不進 Git)
22[[ -f "$HOME/.config/zsh/local.zsh" ]] && source "$HOME/.config/zsh/local.zsh"

各模組的職責

aliases.zsh — 短指令對映

 1# 檔案操作
 2alias ll='ls -alF'
 3alias la='ls -A'
 4
 5# Git 常用
 6alias gs='git status'
 7alias gd='git diff'
 8alias gco='git checkout'
 9alias gp='git push'
10
11# 導航
12alias ..='cd ..'
13alias ...='cd ../..'

判讀準則:alias 適合「不帶參數的簡單替換」。如果需要參數處理或條件判斷,改用 function。

functions.zsh — 帶邏輯的常用操作

1# 建目錄並進入
2mkcd() {
3    mkdir -p "$1" && cd "$1"
4}
5
6# 在 Git repo 根目錄搜尋
7ggrep() {
8    git grep "$@" "$(git rev-parse --show-toplevel)"
9}

tools.zsh — 第三方工具的初始化

1# fzf
2[[ -f ~/.fzf.zsh ]] && source ~/.fzf.zsh
3
4# nvm
5export NVM_DIR="$HOME/.nvm"
6[[ -s "$NVM_DIR/nvm.sh" ]] && source "$NVM_DIR/nvm.sh"
7
8# pyenv
9command -v pyenv >/dev/null && eval "$(pyenv init -)"

每個工具的 init 前面加存在性檢查(command -v[[ -f ]]),避免在沒裝該工具的機器上報錯。

local.zsh — 機器專屬、不進 Git

1# 公司 VPN 設定
2export CORP_PROXY="http://proxy.corp:8080"
3
4# 只有這台機器需要的 PATH
5export PATH="$HOME/corp-tools/bin:$PATH"

在 dotfile repo 的 .gitignore 裡排除這個檔案。.zshrc 裡用 [[ -f ... ]] && source 確保不存在也不報錯。