在 FreeBSD sh 中将当前目录显示为终端标题
同时打开多个终端时,如果能直接从标题栏判断每个终端所在的目录,会方便 很多。
例如当前目录为:
/home/hodong/projects/nimfsoft.com
终端标题可以显示为:
~/projects/nimfsoft.com
如果当前目录不在主目录下,则显示完整路径:
/usr/local/src
本文以 FreeBSD 的 /bin/sh 和 ~/.shrc 为例。
修改终端标题的原理
兼容 xterm 的终端模拟器可以通过 OSC(Operating System Command)转义序列 修改终端标题。
printf '\033]0;%s\007' '新的终端标题'
各部分含义如下:
\033 ESC 字符
]0; 设置图标名称和窗口标题
%s 要显示的字符串
\007 使用 BEL 结束 OSC 序列
因此,可以直接将当前目录用作标题:
printf '\033]0;%s\007' "$PWD"
是否支持该功能取决于终端模拟器及其配置。
将主目录缩写为 ~
如果 $PWD 等于 $HOME,或者位于 $HOME 之下,就将主目录前缀替换为
~。
set_terminal_title() {
if [ -n "${HOME-}" ]; then
case "$PWD" in
"$HOME")
_terminal_title_path='~'
;;
"$HOME"/*)
_terminal_title_path="~${PWD#"$HOME"}"
;;
*)
_terminal_title_path=$PWD
;;
esac
else
_terminal_title_path=$PWD
fi
printf '\033]0;%s\007' "$_terminal_title_path"
unset _terminal_title_path
}
当 HOME 未设置时,${HOME-} 会展开为空字符串,从而避免把空值当作有效的
主目录前缀。
当主目录已正确设置时,使用 case 语句对当前路径进行分类。
case "$PWD" in
"$HOME")
_terminal_title_path='~'
;;
"$HOME"/*)
_terminal_title_path="~${PWD#"$HOME"}"
;;
*)
_terminal_title_path=$PWD
;;
esac
如果当前目录与主目录完全相同,标题为 ~。
/home/hodong -> ~
如果当前目录位于主目录之下,则删除 $HOME 部分,并在剩余路径前加上
~。
/home/hodong/projects/nimfsoft.com
-> ~/projects/nimfsoft.com
${PWD#"$HOME"} 会从 $PWD 开头删除与 $HOME 匹配的最短前缀。
例如,在以下环境中:
HOME=/home/hodong
PWD=/home/hodong/projects/nimfsoft.com
其结果为:
/projects/nimfsoft.com
在前面加上 ~ 后,最终标题为:
~/projects/nimfsoft.com
单独使用 "$HOME"/* 模式,可以避免将 /home/hodong2/projects 错误地
视为 /home/hodong 的子目录。
/home/hodong2/projects
-> /home/hodong2/projects
_terminal_title_path 是临时变量。shell 函数中的变量默认会保留在当前
shell 中,因此使用完后通过 unset 删除,以降低命名冲突的可能性。
在执行 cd 后更新标题
只在 shell 启动时设置一次标题还不够。工作目录变化后,也需要重新设置。
下面的函数先调用原始的 shell 内建 cd,并且只在切换目录成功后更新标题:
cd() {
command cd "$@" || return $?
set_terminal_title
}
如果在函数内部直接执行 cd "$@",会再次调用同名函数并形成递归。
cd "$@"
使用 command cd "$@" 会绕过同名函数,调用 shell 内建的 cd。
command cd "$@"
"$@" 会原样保留传给 cd 的所有参数:
cd
cd -
cd /usr/local/src
如果目录切换失败,则返回原来的失败状态,并保持标题不变。
command cd "$@" || return $?
因此,即使尝试进入一个不存在的目录,当前目录和终端标题也都会保持 不变。
最终 .shrc 补丁
将下面的补丁应用到 ~/.shrc:
--- a/.shrc 2026-07-15 19:28:53.358286000 +0900
+++ b/.shrc 2026-07-15 19:47:56.461994000 +0900
@@ -46,6 +46,37 @@
bind ^[[5~ ed-move-to-beg
bind ^[[6~ ed-move-to-end
+# Update terminal title to the current working directory.
+set_terminal_title() {
+ if [ -n "${HOME-}" ]; then
+ case "$PWD" in
+ "$HOME")
+ _terminal_title_path='~'
+ ;;
+ "$HOME"/*)
+ _terminal_title_path="~${PWD#"$HOME"}"
+ ;;
+ *)
+ _terminal_title_path=$PWD
+ ;;
+ esac
+ else
+ _terminal_title_path=$PWD
+ fi
+
+ printf '\033]0;%s\007' "$_terminal_title_path"
+ unset _terminal_title_path
+}
+
+# Change directory and update the terminal title.
+cd() {
+ command cd "$@" || return $?
+ set_terminal_title
+}
+
+# Set the initial terminal title.
+set_terminal_title
+
# set prompt: ``username@hostname:directory $ ''
PS1="\u@\h:\w \\$ "
最初调用一次 set_terminal_title,是为了在读取 .shrc 时立即反映当前
目录。
set_terminal_title
之后,每次 cd 成功时都会再次更新标题。
重新加载配置:
. ~/.shrc
进入项目目录:
cd ~/projects/nimfsoft.com
标题将显示为:
~/projects/nimfsoft.com
进入主目录之外的路径时,则显示完整路径:
cd /usr/local/src
/usr/local/src
为什么不在 PS1 中调用函数
一种看似可行的写法是:
PS1='$(set_terminal_title)\u@\h:\w \$ '
但是在本文使用的 FreeBSD sh 环境中,该写法没有按预期工作。提示符显示
异常,终端标题也没有更新。
因此保留原有的提示符:
PS1="\u@\h:\w \\$ "
标题更新不依赖提示符的重新求值行为,而是在 cd 成功之后立即完成。
注意事项
该配置要求终端模拟器支持 OSC 标题序列,并允许应用程序修改标题。
本文假设目录名是普通文本。如果目录名可能包含 ESC、BEL 等终端控制字符, 则在将路径插入 OSC 序列之前应先进行过滤。
标题会在通过这里定义的 cd 函数切换目录时更新。shell 启动时则由显式的
初始调用设置标题。
总结
最终的显示规则如下:
/home/hodong -> ~
/home/hodong/projects -> ~/projects
/home/hodong/projects/nimfsoft.com -> ~/projects/nimfsoft.com
/home/hodong2/projects -> /home/hodong2/projects
/usr/local/src -> /usr/local/src
这个小设置可以在同时处理多个项目时,更容易区分不同的终端窗口。