1233 文字
3 分
日本語

~/.zshrc を置かない dotfiles:ZDOTDIR と chezmoi

長く使った ~/.zshrc を見ると、自分で書いた行とインストーラーが追加した行を区別できなくなっていた。

インストールスクリプトは簡単に初期化処理を追記するが、ツールを消してもその行は残る。ファイルが一応動いているので触りづらくなり、さらに追記だけが増えていく。

今はホームディレクトリに ~/.zshenv だけを残し、ほかの zsh 設定を ~/.config/zsh/ に置いて chezmoi で管理している。最初の記事で書いたレイヤー分けを dotfiles に適用した形だ。

ZDOTDIR で設定を移動する#

macOS の interactive login shell は、おおむね次の順で設定を読む。

/etc/zshenv → ~/.zshenv (always, every shell)
/etc/zprofile → ~/.zprofile (login shells)
/etc/zshrc → ~/.zshrc (interactive shells)
/etc/zlogin → ~/.zlogin (login shells)

~/.zshenv はすべての zsh で最初に読まれる。ここで ZDOTDIR を設定すると、.zprofile.zshrc.zlogin の探索先が $HOME から $ZDOTDIR に変わる。

Terminal window
# ~/.zshenv — the only zsh file allowed to live in $HOME
export ZDOTDIR="${XDG_CONFIG_HOME:-$HOME/.config}/zsh"
ZDOTDIR 使用前後のホームディレクトリ Before ~/ .zshenv.zshrc .zprofile.bash_profile .npmrc.gitconfig .python_history.zsh_history .cargo/ .rustup/ … (増え続ける) After (ZDOTDIR) ~/ .zshenv → ZDOTDIR を設定 ~/.config/zsh/ .zshrc00-env.zsh 20-aliases.zsh30-functions.zsh 35-tools.zsh …

これは zsh が正式にサポートする仕組みだ。VS Code の integrated terminal も ~/.zshenv を読む。古いツールの一部は ~/.zshrc を決め打ちしているが、私の環境では問題になることは少なかった。

長い一ファイルを作らない#

移動先で再び巨大な .zshrc を作らないよう、.zshrc 自体は番号順に fragment を読むだけにした。

Terminal window
# ~/.config/zsh/.zshrc — loads every fragment in numeric order
for _file in "${ZDOTDIR}"/conf.d/*.zsh(N); do
source "$_file"
done
unset _file

(N) は、対象がないときにエラーではなく空へ展開する zsh の glob qualifier。

~/.config/zsh/conf.d/
├── 00-env.zsh # exported env vars, PATH base
├── 10-completion.zsh # compinit and completion styles
├── 20-aliases.zsh # short renames of existing commands
├── 30-functions.zsh # shell functions that do real work
├── 35-tools.zsh # eval-hooks for external CLIs (mise, zoxide, …)
├── 40-lang.zsh # language/runtime-specific setup
├── 50-plugins.zsh # zsh-ecosystem plugins
└── 90-local.zsh # machine-specific, not tracked in git

環境変数、関数、外部 CLI が生成する hook、言語設定、プラグインの順に分ける。prompt が runtime 情報を使う場合は mise の activation より後に読み、syntax highlighting は line editor を wrap するので最後に置く。

PATH を重複させない#

~/.zshenv は子 shell でも実行されるため、

Terminal window
export PATH="$HOME/.local/bin:$PATH"

をそのまま置くと tmux、subshell、exec zsh のたびに同じパスが増える。

Terminal window
# Prepend to PATH only if not already present.
path_prepend() {
case ":$PATH:" in
*":$1:"*) ;; # already there — do nothing
*) PATH="$1:$PATH" ;;
esac
}
path_prepend "$HOME/.local/bin"

PATH の両端にもコロンを付けることで、先頭と末尾の要素も完全一致で確認できる。この関数は 00-env.zsh に置く。

chezmoi の source と target#

Git リポジトリを $HOME に直接 symlink する代わりに chezmoi を使う。source directory からホームディレクトリへレンダリングし、source 側のファイル名で属性も宣言する。

  • dot_:先頭にドットを付ける。
  • private_:結果を 0600 にする。
  • executable_:実行 bit を付ける。
  • exact_:宣言されていないディレクトリ内容を削除する。
  • run_onchange_:内容が変わったときにスクリプトを実行する。アプリの記事では Brewfile の変更時に使う。
chezmoi source repo → rendered into $HOME
├── dot_zshenv → ~/.zshenv
├── dot_config/
│ └── zsh/
│ ├── dot_zshrc → ~/.config/zsh/.zshrc
│ └── conf.d/
│ ├── 00-env.zsh → ~/.config/zsh/conf.d/00-env.zsh
│ └── 20-aliases.zsh → ~/.config/zsh/conf.d/20-aliases.zsh
└── dot_local/
└── bin/
└── executable_zhealth → ~/.local/bin/zhealth (chmod +x)

zhealth で勝手に増えた設定を探す#

構成後、$HOME にある zsh ファイルは ~/.zshenv だけになる。~/.zshrc が復活したら、インストーラーが書いた可能性が高い。

Terminal window
# 30-functions.zsh — flag stray zsh files in $HOME
zhealth() {
local stray=(~/.zshrc(N) ~/.zprofile(N) ~/.zlogin(N) ~/.zshrc.*(N))
if (( ${#stray} )); then
print -u2 "zhealth: unexpected zsh files in \$HOME:"
printf ' %s\n' "${stray[@]}" >&2
return 1
fi
print "zhealth: \$HOME is clean — only ~/.zshenv expected"
}

新しいツールを入れたあとに実行すれば、数か月後にシェルの挙動から原因を推測せずに済む。メンテナンスの記事でも同じ health check の考え方を使う。

追跡しないもの#

~/.zsh_history は設定ではなく状態で、内容も private。.zcompdump* は再生成できる cache。credential や token も通常の dotfiles リポジトリには置かない。

.chezmoiignore
.config/zsh/.zcompdump*
.zsh_history

マシン固有の proxy や local alias は 90-local.zsh に置く。最後に読み込むので上書きできるが、Git では管理しない。

結果として $HOME には入口が一つ、設定本体は順序付きの小さな fragment、期待状態は chezmoi、勝手に増えたファイルの確認は zhealth という形になった。

~/.zshrc を置かない dotfiles:ZDOTDIR と chezmoi
https://www.shiinayane.com/ja/posts/dotfiles/
著者
YANKAI WANG
公開日
2026-05-30
ライセンス
CC BY-NC-SA 4.0