记录从零搭建服务器 zsh 环境的过程,包含踩过的坑。配置已整理成 dotfiles 仓库,新服务器一键部署。
环境:Ubuntu 24.04 / Debian 12,zsh + Zim 框架
最终工具栈:
| 工具 | 作用 |
|---|---|
| Zim | zsh 框架,比 oh-my-zsh 轻,启动快 |
| Powerlevel10k | 提示符主题 |
| eza | 带图标的 ls 替代,支持 git 状态列 |
| zoxide | 智能目录跳转,记住历史,z 关键词 直达 |
| fzf | 模糊查找,Ctrl+R 历史搜索、Ctrl+T 文件选择 |
| bat | 带语法高亮的 cat(Debian/Ubuntu 上命令名为 batcat) |
先做这一步,否则 eza 图标会显示成方块。
eza 的图标由本地终端渲染,服务器不需要装字体。SSH 连服务器时,字符渲染在本地做,改的是本地终端的字体配置。
下载 0xProto Nerd Font:nerdfonts.com
装完在终端设置里切换字体。Windows Terminal 注意:每个 profile 可以单独设字体,改的要是你 SSH 用的那个 profile,不是默认 profile。
.zshenv比 /etc/zsh/zshrc 更早加载,用来解决 compinit 重复初始化的问题(见下方坑点记录)。
zshskip_global_compinit=1
.zimrcbash# 基础模块
zmodule environment
zmodule input
zmodule termtitle
zmodule utility
# Git
zmodule git
zmodule wfxr/forgit # fzf 加持的 git 交互工具
# 主题
zmodule duration-info
zmodule git-info
zmodule romkatv/powerlevel10k
# 补全(必须在普通模块之后)
zmodule zsh-users/zsh-completions --fpath src
zmodule completion
# 必须最后加载
zmodule zsh-users/zsh-syntax-highlighting
zmodule zsh-users/zsh-history-substring-search
zmodule zsh-users/zsh-autosuggestions
.zshrcbash# =========================
# ZSH CONFIG
# =========================
# p10k instant prompt(必须最顶部)
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
# Zim 初始化
ZIM_HOME=${ZDOTDIR:-$HOME}/.zim
source ${ZIM_HOME}/init.zsh
# 基础环境变量
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export EDITOR=vim
export VISUAL=vim
# 历史记录
HISTSIZE=50000
SAVEHIST=50000
HISTFILE=~/.zsh_history
setopt APPEND_HISTORY
setopt SHARE_HISTORY # 跨会话共享
setopt HIST_IGNORE_ALL_DUPS # 去重
setopt HIST_REDUCE_BLANKS
setopt HIST_IGNORE_SPACE # 空格开头不入历史(用于敏感命令)
# 补全
setopt AUTO_MENU
setopt COMPLETE_IN_WORD
# 目录
setopt AUTO_CD
# p10k 主题
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh
# ----------------------------
# Aliases
# ----------------------------
# eza
alias ls='eza --icons --group-directories-first'
alias ll='eza -alF --icons --group-directories-first --git --header --time-style=relative'
alias la='eza -A --icons --group-directories-first'
alias l='eza --icons --group-directories-first'
alias lt='eza --tree --icons --level=2'
alias lt3='eza --tree --icons --level=3'
# bat
alias cat='batcat --paging=never'
alias bat='batcat'
# 目录跳转
alias ..='cd ..'
alias ...='cd ../..'
# 危险操作加确认(服务器上手滑代价大)
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
# grep
alias grep='grep --color=auto'
# Git
alias gs='git status'
alias ga='git add'
alias gc='git commit'
alias gp='git push'
alias gl='git log --oneline --graph --decorate'
# Docker
alias dps='docker ps'
alias dpa='docker ps -a'
alias di='docker images'
alias dlog='docker logs -f'
alias dex='docker exec -it'
# Systemd
alias sctl='systemctl'
alias sstart='systemctl start'
alias sstop='systemctl stop'
alias sstat='systemctl status'
alias srestart='systemctl restart'
# Network
alias ports='ss -tulnp'
alias myip='curl -s --max-time 5 ifconfig.me'
# Process
alias psg='ps aux | grep'
# ----------------------------
# 性能
# ----------------------------
export KEYTIMEOUT=1
# ----------------------------
# 函数
# ----------------------------
# mkdir + cd
mkcd() { mkdir -p "$1" && cd "$1" }
# 通用解压
extract() {
if [ ! -f "$1" ]; then echo "'$1' is not a valid file"; return 1; fi
case "$1" in
*.tar.bz2) tar xjf "$1" ;;
*.tar.gz) tar xzf "$1" ;;
*.tar.xz) tar xJf "$1" ;;
*.tar) tar xf "$1" ;;
*.bz2) bunzip2 "$1" ;;
*.gz) gunzip "$1" ;;
*.zip) unzip "$1" ;;
*.7z) 7z x "$1" ;;
*.rar) unrar x "$1" ;;
*) echo "Unknown format: $1" ;;
esac
}
# ----------------------------
# 工具初始化(放文件末尾)
# ----------------------------
eval "$(zoxide init zsh)"
source <(fzf --zsh)
表现
warning: completion was already initialized before completion module. Will call compinit again.
同时 p10k 弹 instant prompt 警告——p10k 只是信使,检测到启动期间有输出就报警,真正的根因是上面那行。
根因
Ubuntu/Debian 的 /etc/zsh/zshrc 在系统层面调了一次 compinit,Zim 的 completion 模块又调一次,重复了。
定位方法:
bashzsh -xic exit 2>&1 | grep "compinit" | head
输出里能看到 +/etc/zsh/zshrc:111> compinit,确认是系统配置先调的。
解法
创建 ~/.zshenv,写入:
zshskip_global_compinit=1
.zshenv 比 /etc/zsh/zshrc 更早加载。Debian/Ubuntu 的系统 zshrc 在调 compinit 前会检查这个变量,值为 1 就跳过。Zim 的 completion 模块成为唯一一次初始化,警告消失。
验证系统是否支持该变量:
bashgrep -n "skip_global_compinit" /etc/zsh/zshrc
有输出即支持。
表现
/home/csr/.zimrc:4: command not found: ^M fatal: unable to access 'https://github.com/zimfw/environment?.git/': URL rejected: Malformed input to a URL function
^M 是 Windows 的回车符 \r。zmodule environment 变成了 zmodule environment\r,git clone 的 URL 里带了非法字符。
根因
在 Windows 上用编辑器(VS Code 默认)保存文件时,换行符是 CRLF(\r\n),Linux/zsh 需要 LF(\n)。
三层解法
CRLF 切成 LF 再保存.gitattributes,强制所有文件 LF* text=auto eol=lf *.sh text eol=lf .zshrc text eol=lf .zimrc text eol=lf .zshenv text eol=lf
提交时加一步规范化:
bashgit add --renormalize .
deploy() 函数里用 sed 在落盘前清洗bashsed 's/\r$//' "$src" > "$dst"
最后一道兜底,即使仓库里偶尔带了 \r,落到目标机器也是干净的。
| bash 执行表现
You must use zsh to run install.zsh curl: (23) Failure writing output to destination
根因
Zim 的安装脚本是 .zsh 文件,必须用 zsh 执行。| bash 会把脚本内容塞给 bash,bash 检测到不是 zsh 就退出,curl 那端管道断了所以报写入失败。
解法
先下载到临时文件,再明确用 zsh 执行:
bashcurl -fsSL https://raw.githubusercontent.com/zimfw/install/master/install.zsh \
-o /tmp/zim-install.zsh
zsh /tmp/zim-install.zsh
rm -f /tmp/zim-install.zsh
配置托管在 GitHub:github.com/CasearF/dotfiles
新服务器部署:
bashgit clone https://github.com/CasearF/dotfiles.git ~/dotfiles
cd ~/dotfiles && chmod +x install.sh && ./install.sh
exec zsh
install.sh 自动完成:zsh/git/curl/unzip 检测安装 → eza/zoxide/fzf/bat 安装 → Zim 安装 → 旧配置备份(.bak-时间戳)→ 配置部署(自动清 CRLF)→ Zim 模块安装 → 设 zsh 为默认 shell。
bashexec zsh
ll # eza:图标 + 目录排前 + git 列
z some-dir # zoxide:模糊跳转
Ctrl+R # fzf:历史模糊搜索
bat ~/.zshrc # bat:语法高亮
启动无 compinit 警告,五项全绿即完工。
本文作者:Casear
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!