OSC 3008,全名是 Hierarchical Context Signalling,是 UAPI Group 規範中的 UAPI.15。它定義了一組新的終端控制序列,讓程式可以把目前終端裡的「上下文層級」告訴終端模擬器。
簡單說,它想解決的是一個現代 Linux 終端裡越來越常見的問題:你到底在哪一層?
比如你可能從本機 SSH 到伺服器,再進入容器,隨後用 run0 或類似工具提權執行命令。對使用者來說,這是一個連續的終端視窗;但對終端模擬器來說,如果沒有額外信號,它很難準確知道目前輸出來自本機、遠端、容器,還是提權後的命令。
OSC 3008 就是為這類場景設計的。UAPI 官方規範說明,它允許終端模擬器追蹤螢幕上目前內容的上下文層級。systemd 也已經提供了對應實作,腳本名通常是 80-systemd-osc-context.sh。
官方規範:
- UAPI.15 OSC 3008: Hierarchical Context Signalling:https://uapi-group.org/specifications/specs/osc_context/
- systemd
80-systemd-osc-context.sh:https://github.com/systemd/systemd/blob/main/profile.d/80-systemd-osc-context.sh
為什麼需要 OSC 3008
傳統終端只負責顯示程式輸出,最多透過標題、顏色、提示字元來輔助區分環境。問題是,現代終端會話經常是多層嵌套的:
|
|
如果只靠 PS1 提示字元,很容易遇到幾個問題:
- 提示字元只能表示目前 shell,很難完整表達嵌套層級。
- 不同 shell、不同發行版、不同使用者配置會互相覆蓋。
- 終端模擬器無法可靠知道哪些輸出屬於哪個上下文。
- 某些控制序列在不相容終端裡會直接變成亂碼。
OSC 3008 提供了一種更結構化的方式:由實際進入上下文的程式送出 start 序列,由退出上下文的程式送出 end 序列。終端模擬器解析後,就能知道目前輸出屬於哪個上下文節點。
它傳遞哪些資訊
OSC 3008 本質上是一組 OSC 轉義序列。規範裡定義了兩類核心命令:
|
|
start 表示一個上下文開始、更新或重新成為目前上下文;end 表示一個上下文結束。
每個上下文可以帶一些元資料欄位。常見欄位包括:
type=:上下文類型,例如shell、command、remote、container、vm、elevate。user=:送出序列的 UNIX 使用者名稱。hostname=:主機名稱。machineid=:來自/etc/machine-id的機器 ID。bootid=:來自/proc/sys/kernel/random/boot_id的啟動 ID。pid=:送出序列的程序 PID。comm=:程序名稱。cwd=:目前工作目錄,主要用於shell或command。cmdline=:被互動式呼叫的命令列。container=、vm=:容器或虛擬機名稱。targetuser=、targethost=:目標使用者或遠端目標主機。
這些欄位不是為了給使用者直接看,而是給終端模擬器解析。支援得好的終端會「吃掉」這些控制序列,並用它們改善介面體驗。
能帶來什麼好處
如果終端模擬器支援 OSC 3008,它可以做不少更聰明的事情。
第一,標記不同上下文的輸出。比如容器內輸出、提權命令輸出、遠端 SSH 輸出,可以有不同的背景、邊框或提示。
第二,顯示層級麵包屑。終端可以知道目前大致處於:
|
|
這比單靠視窗標題或 PS1 更穩定。
第三,輔助視窗標題、標籤頁和右鍵選單。終端可以根據目前上下文更新標籤頁標題,也可以在某段輸出上提供「在同一目錄打開 shell」之類操作。
第四,減少誤操作。比如目前已經進入生產環境容器並切到 root,終端可以做更醒目的提示,降低在錯誤環境執行危險命令的機率。
systemd 是怎麼接入的
systemd 的實作主要在 80-systemd-osc-context.sh 裡。這個腳本會透過 profile 機制載入,並在 Bash 互動環境裡設定相關函數和提示字元鉤子。
常見路徑可能是:
|
|
不同發行版的實際位置可能不同。systemd 原始碼中的註解說明,這個檔案會透過 systemd-tmpfiles 啟用,把它連結到 /etc/profile.d/。
在 Bash 裡,它會使用類似這些函數名:
|
|
同時還會影響 PROMPT_COMMAND 和 PS0。其中 PS0 會在 Bash 讀取命令後、執行命令前展開,這也是為什麼有些不相容終端會在每次執行命令前看到一串很長的 3008;start=... 文字。
為什麼有些終端會顯示亂碼
正常情況下,終端模擬器應該把 OSC 3008 當作控制序列解析掉,不直接顯示。
但如果終端不認識 OSC 3008,或者中間層把控制字元過濾、轉義、打散了,就可能把原始內容顯示出來。常見場景包括:
- 舊版終端模擬器。
- 尚未適配 OSC 3008 的 SSH 客戶端。
- Web 堡壘機或瀏覽器終端,例如某些 Apache Guacamole 環境。
- Emacs
term.el、串口終端、minicom等對 OSC 支援有限的環境。 - 多層轉發後控制序列被中間層破壞。
這時你可能會看到類似下面的內容:
|
|
或者看到包含 machineid=、bootid=、pid=、comm=、cwd= 的長字串。它們不是普通程式輸出,而是本來應該由終端處理的上下文信號。
怎麼判斷是不是 OSC 3008
可以先看三個線索。
第一,亂碼裡是否有 3008;start= 或 3008;end=。
第二,亂碼是否經常出現在命令執行前後,尤其是每次按 Enter 執行命令時都出現。
第三,目前系統是否載入了 systemd 的 OSC 函數:
|
|
如果能看到函數定義,說明目前 shell 裡確實載入了相關邏輯。
還可以檢查 profile 檔案:
|
|
如果這個路徑存在,並且你的終端正好不支援 OSC 3008,亂碼大機率就來自這裡。
臨時規避辦法
如果只是目前會話看著難受,可以先在 shell 裡把相關函數覆蓋掉,並清空 PS0:
|
|
如果想只在 SSH 會話中自動處理,可以放到 ~/.bashrc:
|
|
這種方式影響範圍比較小,不會動系統級 profile 檔案,也方便隨時刪除。
系統級禁用方法
如果你確認這台機器的終端環境普遍不相容 OSC 3008,可以考慮系統級禁用。但這一步要謹慎,因為它會影響所有使用者和所有登入會話。
systemd 腳本註解裡給出的思路是:移除 /etc/profile.d/80-systemd-osc-context.sh 符號連結,並 mask 對應的 tmpfiles 片段,避免之後被重新建立。
可以參考這樣的命令:
|
|
注意,這比只改 ~/.bashrc 更重。建議先用臨時方案確認問題確實由 OSC 3008 引起,再決定是否做系統級禁用。
如果只是個人 SSH 客戶端不相容,不建議直接改系統級配置。優先改自己的 ~/.bashrc,或者在 SSH 客戶端裡設定登入後執行清理命令。
應該升級終端還是禁用 OSC 3008
如果你使用的是現代終端,並且它已經支援 OSC 3008,最好保留這個能力。它未來可能會讓遠端 shell、容器、虛擬機和提權命令的上下文展示更清楚。
如果你的實際環境是堡壘機、串口、老舊終端或 Web SSH,而且短期內沒法升級客戶端,那麼遮蔽它更實際。畢竟對大多數維運場景來說,乾淨可讀的終端輸出比上下文增強更重要。
可以按這個順序處理:
- 能升級終端模擬器,就先升級。
- 不能升級,但只影響自己,就在
~/.bashrc或客戶端登入後命令裡遮蔽。 - 全機器都受影響,再考慮移除
/etc/profile.d/80-systemd-osc-context.sh連結並 mask tmpfiles 片段。
SSH 中處理 OSC 3008 亂碼的實作方案
用 SSH 連接 Ubuntu、Kubuntu 或其他 systemd 較新的 Linux 系統時,有時會在命令提示字元附近看到一串奇怪的控制字元,常見關鍵字包括 OSC 3008、systemd context、__systemd_osc_context_*。它通常不影響命令執行,但會污染終端顯示,複製日誌時也容易夾帶亂碼。
這個問題不一定只和某一個 SSH 客戶端有關。WindTerm、某些嵌入式終端、舊版終端模擬器,或者對 OSC 序列支援不完整的客戶端,都可能把本該被終端解讀的控制序列直接顯示出來。
根因通常是:遠端系統在 Bash 環境裡載入了 systemd 的 OSC 上下文鉤子,而目前 SSH 客戶端沒有正確處理這些序列。處理思路也很直接:確認這些鉤子函數存在後,把它們覆蓋成空函數,並清空 PS0。
下面給兩個辦法。第一個寫在遠端 ~/.bashrc,適合想一勞永逸處理 SSH 會話的伺服器;第二個寫在 SSH 客戶端的「登入後執行命令」裡,適合只想對某個客戶端或某個會話生效的場景。
方案一:在 .bashrc 裡按 SSH 會話攔截
這個方案的邏輯是:只要偵測到目前是 SSH 遠端會話,並且系統裡確實存在 systemd 注入的 OSC 鉤子函數,就把相關函數覆蓋成空函數,同時清空 PS0。
把下面這段加到遠端伺服器的 ~/.bashrc 末尾:
|
|
儲存後重新登入 SSH,或者在目前 shell 裡執行:
|
|
如果亂碼來自 systemd 的 OSC 鉤子,重新進入 shell 後通常就會消失。
如果還想相容特定客戶端
有些客戶端會設定自己的環境變數,例如 WindTerm 可能有 TERM_PROGRAM=WindTerm。如果你想保留這個判斷,也可以寫成更寬一點的版本:
|
|
普通 SSH 場景下,單純判斷 SSH_CLIENT、SSH_TTY、SSH_CONNECTION 已經夠用。加上 TERM_PROGRAM 只是為了覆蓋某些客戶端自己的識別方式。
為什麼這個寫法相對安全
這段配置有兩層限制。
第一層是會話判斷:
|
|
這些變數通常只在 SSH 登入會話裡出現,所以它主要影響遠端登入,不會隨便改動本機桌面終端。
第二層是函數存在性判斷:
|
|
這是一道保險。只有目前 shell 已經載入了 __systemd_osc_context_precmdline 這個函數,才會繼續覆蓋相關函數。如果系統沒有這套 systemd OSC 邏輯,這段配置不會做多餘動作。
真正被覆蓋的是這幾個函數:
|
|
它們的作用是讓相關鉤子不再輸出內容。PS0 是 Bash 在讀取命令後、執行命令前展開的提示字元變數,某些 OSC 序列正是透過它或類似機制插進去的,所以這裡也一起清空。
方案二:放到 SSH 客戶端的登入後命令裡
如果你不想改遠端伺服器的 ~/.bashrc,可以把修復動作放到 SSH 客戶端的「登入後執行命令」裡。很多終端或 SSH 客戶端都有類似功能,名字可能叫:
Command executed after authenticationPost-login commandRemote command after login登入後執行命令
填入下面這一行:
|
|
如果客戶端要求自動執行後換到新的提示行,可以在末尾按它的語法追加換行。例如 WindTerm 的 Command executed after authentication 裡,可以使用:
|
|
這裡的兩個 \n 用來讓客戶端執行清理命令後再進入新的提示行。這個寫法適合 WindTerm 這類支援認證後自動執行命令的客戶端,在 Kubuntu 24.04 環境裡測試可用。
這種方案的好處是影響範圍小:只有使用該客戶端、該會話連接時才會執行清理命令,伺服器上的 shell 配置不需要改。
兩種方案怎麼選
如果這台伺服器主要是你自己用,或者你希望所有 SSH 客戶端連進來都不再看到這類亂碼,推薦用方案一。寫進 ~/.bashrc 後,換 WindTerm、Windows Terminal、Tabby、Xshell 或其他 SSH 客戶端,都能統一處理。
如果伺服器是多人共用,或者你只想讓某個客戶端規避這個問題,推薦方案二。它不改遠端配置,也不會影響其他人的 shell 環境。
也可以先用方案二驗證問題確實能解決,再決定要不要把方案一寫進伺服器的 ~/.bashrc。
注意事項
這兩個方案只是遮蔽 systemd 的 OSC 輸出鉤子,不會修改 systemd 服務,也不會影響 SSH 登入本身。
不過,如果你正在使用支援 OSC 3008 的現代終端,並且依賴它顯示命令上下文、工作目錄或系統狀態,那麼遮蔽後這些增強提示可能會消失。普通 SSH 維運、開發機登入、伺服器管理場景通常不受影響。
另外,不建議一上來就把這類函數覆蓋寫到全域 /etc/bash.bashrc。除非你確認整台機器所有使用者都需要這個行為,否則個人環境優先放 ~/.bashrc,客戶端定向修復優先放 SSH 客戶端的登入後命令。
簡短結論
OSC 3008 不是病毒,也不是普通程式亂輸出。它是 UAPI.15 定義的終端上下文信令,目標是讓終端理解 shell、SSH、容器、虛擬機、提權命令之間的層級關係。
真正的問題通常出在相容性:支援它的終端會自動解析,不支援的終端就可能把控制序列當成亂碼顯示。
如果你看到 3008;start=、machineid=、bootid=、cwd= 之類內容,先判斷是否由 systemd 的 80-systemd-osc-context.sh 注入。確認後,再按影響範圍選擇臨時遮蔽、個人 .bashrc 遮蔽,或系統級禁用。