I had been happily using the zshrc I set up in Reviewing my Zsh and NeoVim configuration as a base, but I went over the configuration again with the help of generative AI (ChatGPT-5.2).
What I improved
- Made the broken predicate functions correct (stopped using return $(…))
- Left the locale alone (LC_ALL in particular is easy to get wrong)
- Made completion work from the very first shell (avoided the landmine of zinit lazy loading combined with the compinit order)
- Isolated the heavy listings into ll/la so that everyday ls does not get slow
Background: with .zshrc, convenience and destruction are a hair apart
When it comes to improving a zshrc, there are more landmines of the “writing it the wrong way quietly breaks things” kind than there is work on appearance or adding aliases. These were the four places I got stuck this time:
- return $(…) in _has/_try
- locale (overwriting LC_ALL)
- the order of zinit lazy loading and compinit
- aliases that make the ls equivalent too heavy
1) _has/_try: return $(…) is an accident that turns output into an exit status
What return returns is a status from 0 to 255. If you feed the standard output of $(…) into it, it becomes an empty string or a string and breaks (it drifts away from the intent).
The fix is simple: just run the command and return its exit status.
_has() {
whence -w -- "$1" >/dev/null 2>&1
}
_try() {
eval "$*" >/dev/null 2>&1
}Key points:
- Always quote “$1” / “$*” (it prevents breakage from spaces and special characters)
- Because _try is eval, where it can be used is limited (this time I leaned to the safe side without breaking the existing design)
2) locale: LC_ALL overwrites everything, so do not touch it in .zshrc
Once you export LC_ALL, all the categories such as LC_CTYPE and LC_MESSAGES are forced. As a result Japanese turns into mojibake, command output changes, and you get behaviour you did not expect.
This time I made it a policy not to set LC_ALL inside .zshrc, and only to put UTF-8 in when LANG is empty.
if [[ -z ${LANG-} ]]; then
export LANG=ja_JP.UTF-8
fi
# NOTE:
# Avoid setting LC_ALL here. It overrides all LC_* categories and can easily
# cause mojibake. If you need C locale for a single command, do:
# LC_ALL=C <cmd>Operational tips:
- For a use like “I want to sort in C only”, it is safer to write LC_ALL=C cmd for that command alone.
3) Completion: the order of zinit lazy loading and compinit causes “it only fails the first time”
If you lazy load a completion source (for example zsh-completions) and run compinit first, completion can end up being iffy on the first run only.
This time I loaded the completion related things first without delaying them, and made it lighter with compinit -C (cache).
zinit light zsh-users/zsh-completions
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
zstyle ':completion:*:default' menu select=1
autoload -Uz compinit && compinit -C4) alias: isolate the heavy ls equivalents into ll/la
Piling eza options on (icons/git/time-style/all/long) feels good, but when a directory is large it is plainly slow.
So, going with “the heavy ones go to an explicit command”, I grew ll and la.
which eza > /dev/null && alias ll='eza --icons --git --time-style relative -al'
which eza > /dev/null && alias la='eza --icons --git --time-style relative -a'A landmine I crushed along the way: smart quotes kill an alias
Slipping smart quotes in, as in alias dc=‘docker-compose’, is the kind of thing that makes just that one line ineffective. After unifying on the ASCII ‘, I made it a fallback that prefers docker v2.
if _has docker; then
if _try "docker compose version"; then
alias dc='docker compose'
elif _has docker-compose; then
alias dc='docker-compose'
fi
fiVerification: at least this much
- Syntax check (no side effects)
zsh -n ~/.zshrc- Applying it (evaluate in a new environment)
exec zsh -lNote: when mojibake shows up, it can be that an old shell or terminal was holding on to the old environment, so restart the terminal if necessary.
Summary
- With .zshrc, putting not breaking ahead of convenience is what ends up feeling good to use
- Localise LC_ALL, mind the order for completion, and let the heavy listings escape to ll/la
- return $(…) is out of the question (output is not the exit status)
References
- Zsh builtins / compinit: man zshbuiltins, man zshcompsys
- Zinit: https://github.com/zdharma-continuum/zinit