跳至主要内容

🖥️ SSH 遠端 Zsh 補全與提示異常修復:Terminfo 同步指南

在本地使用現代終端機(如 Ghostty)搭配精雕細琢的 Zsh 配置(Powerlevel10k、zsh-autosuggestions、fzf-tab、fast-syntax-highlighting)時,體驗極致流暢。然而,當我們將相同的一套 .zshrc 同步至遠端主機並透過 SSH 登入時,往往會遇到自動補全與命令提示顯示異常的問題。

本文記錄該問題的成因、各類解決方案、最推薦的解法,以及本機的實際驗證成果。


📌 問題說明

症狀表現

當遠端 SSH 主機使用與本地完全相同的 .zshrc 設定時,常出現以下非預期的視覺異常:

  • 游標錯位與飄移:按 Tab 觸發補全或使用退格鍵(Backspace)時,游標跳至錯誤位置或提前換行。
  • 文字殘影與疊字zsh-autosuggestions 的淡灰色補全文字未正常覆蓋或清除,造成指令字元與建議字元重疊。
  • 補全選單破版fzf-tab 彈窗或內建補全清單渲染亂碼、邊框破碎或無法即時清除。
  • 換行異常(Ghost characters):長指令抵達終端邊界時,未正常換行,而是從當前行首覆蓋輸入。

核心成因分析

這個問題的根本原因並非 .zshrc 語法錯誤,而是 SSH 連線時產生的終端定義斷層

  1. $TERM 與 Terminfo 缺失(最關鍵根因)

    • 現代終端模擬器(例如 Ghostty)在啟動時會設定自定義的 $TERM(如 xterm-ghostty)。
    • 透過 SSH 登入遠端時,SSH Client 會將本地的 $TERM 變數傳送給遠端主機。
    • 然而,遠端 Linux 伺服器的資料庫(/usr/share/terminfo)通常僅內建傳統的 xterm-256colorvt100 等定義,根本沒有 xterm-ghostty 的 terminfo 檔案
    • 遠端系統無法識別終端控制碼(ANSI Escape Sequences),使得 ZLE(Zsh Line Editor)無法正確計算游標座標、清除行末或重繪字元。
  2. 語系與字元寬度計算不一致(Locale & wcwidth)

    • Powerlevel10k 與補全工具大量使用 Unicode / Nerd Font 圖示。
    • 若遠端環境的 LANGLC_ALLPOSIXC,終端在計算圖示寬度(wcwidth)時會判定為單欄(1 column)而非雙欄(2 columns),導致游標定位計算失真。
  3. SSH 登入橫幅(MOTD)干擾

    • 伺服器登入時若輸出系統歡迎訊息或套件更新通知,會干擾 Powerlevel10k 的 Instant Prompt 機制,引發警示與畫面抖動。

🛠️ 可用解決方案

針對此問題,主要有三種應對思路:

方案 1:同步本地 Terminfo 至遠端主機(推薦首選)

直接將本地端現代終端的 terminfo 定義檔提取並編譯注入遠端使用者的 ~/.terminfo

infocmp -a | ssh <remote-host> "tic -x -"

方案 2:本地強制以 xterm-256color 進行 SSH 連線(通用降級)

避免將特殊的 $TERM 傳給遠端,強制在連線時偽裝為標準 256 色終端。

在本地 ~/.zshrc 加入別名:

alias ssh='TERM=xterm-256color ssh'

或於本地 ~/.ssh/config 搭配環境變數設定:

Host *
SendEnv LANG LC_*
SetEnv TERM=xterm-256color

(注意:SetEnv 需遠端 sshd_configAcceptEnv 支援)

方案 3:在 .zshrc 中動態識別 SSH Session 並降級配置(腳本適應)

若連線的遠端主機數量龐大或為臨時容器(無法逐一注入 terminfo),可透過環境變數偵測是否為 SSH 連線,自動執行安全防禦:

# 於 .zshrc 或 common.zshrc 內加入
if [[ -n "$SSH_CONNECTION" || -n "$SSH_CLIENT" || -n "$SSH_TTY" ]]; then
# 若遠端沒有該終端的 terminfo,自動退回標準 256 色
if ! infocmp "$TERM" &>/dev/null; then
export TERM=xterm-256color
fi

# 確保 UTF-8 語系
export LANG="${LANG:-en_US.UTF-8}"
export LC_ALL="${LC_ALL:-en_US.UTF-8}"

# 調整 autosuggestions 顏色與非同步模式
export ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=244"
export ZSH_AUTOSUGGEST_USE_ASYNC=1

# 簡化 fzf-tab 預覽,降低網路延遲下的渲染負擔
if zstyle -L ':fzf-tab:*' &>/dev/null; then
zstyle ':fzf-tab:complete:*:*' fzf-preview ''
fi

# 靜音 p10k instant prompt 警告
typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet
fi

💡 建議方案與原因

最佳選擇:方案 1(infocmp -a | ssh ... "tic -x -"

在評估各方案後,強烈建議優先採用「同步 Terminfo」

推薦原因

  1. 僅需執行一次,永久生效
    • 遠端系統會在 ~/.terminfo/ 產生編譯後的終端定義檔。只要家目錄保留,日後所有連線皆自動支援,完全無痛。
  2. 免改任何程式碼與設定檔
    • 本地端不需要加 alias ssh 搞包裝,遠端也不需要特別分歧維護另一份 .zshrc,實現真正「一套 dotfiles 到處跑」。
  3. 享受現代終端完整特性
    • 若降級為 xterm-256color,現代終端(如 Ghostty)的專屬功能(包含 TrueColor 真彩色、特殊修飾鍵組合、Kitty 鍵盤協定等)將無法完全發揮;同步 terminfo 則能完整保留所有功能。
  4. 無需 Root / Sudo 權限
    • tic -x - 編譯時會自動將結果寫入當前使用者的 ~/.terminfo/,不需要系統管理員權限,一般受限帳號或公司跳板機亦可直接使用。

🚀 本機實際成果

驗證環境

  • 本地端:macOS + Ghostty 終端機(TERM=xterm-ghostty
  • 遠端主機:Linux 伺服器,配置與本機同步之 Zsh dotfiles(Powerlevel10k + fzf-tab + zsh-autosuggestions)

執行步驟

在本地終端機針對遠端主機執行單行指令:

infocmp -a | ssh <remote-host> "tic -x -"

指令解構說明:

  • infocmp -a:自本地提取當前終端(xterm-ghostty)的完整 terminfo 描述文字,包含所有別名與延伸能力定義(-a)。
  • | ssh <remote-host>:透過 SSH 管道將定義串流傳送至遠端主機。
  • "tic -x -":調用遠端的 terminfo 編譯器(tic),參數 - 代表由標準輸入(stdin)讀取,-x 代表保留現代終端的延伸屬性(Extended capabilities),最終自動編譯寫入遠端的 ~/.terminfo/x/xterm-ghostty

成果驗證

執行完成後重新登入遠端主機,終端表現如下:

  1. 自動補全完全正常:按 Tab 呼叫補全與 fzf-tab 選單,位置精準對齊,不再產生錯位或殘留方塊。
  2. Autosuggestions 渲染完美:淡灰色提示字元清晰顯示,打字或按方向鍵補全時流暢無殘影。
  3. 文字退格無殘留:長指令行尾換行、Backspace 退格皆與本地終端完全一致,游標定位百分之百精準。
  4. 驗證耗時:全程僅需一秒鐘執行一次指令,立即解決所有顯示異常。