Hiển thị thư mục hiện tại trên tiêu đề terminal bằng FreeBSD sh

Khi mở nhiều terminal cùng lúc, việc nhận biết thư mục làm việc của từng cửa sổ ngay trên thanh tiêu đề sẽ rất tiện lợi.

Ví dụ, nếu thư mục hiện tại là:

/home/hodong/projects/nimfsoft.com

tiêu đề terminal có thể hiển thị:

~/projects/nimfsoft.com

Nếu thư mục hiện tại nằm ngoài thư mục home, đường dẫn đầy đủ sẽ được hiển thị:

/usr/local/src

Bài viết này sử dụng /bin/sh~/.shrc trên FreeBSD.

Cơ chế thay đổi tiêu đề terminal

Các trình giả lập terminal tương thích xterm có thể thay đổi tiêu đề bằng chuỗi thoát OSC, viết tắt của Operating System Command.

printf '\033]0;%s\007' 'Tiêu đề terminal mới'

Ý nghĩa của từng phần:

\033    Ký tự ESC
]0;     Đặt tên biểu tượng và tiêu đề cửa sổ
%s      Chuỗi cần hiển thị
\007    Kết thúc chuỗi OSC bằng BEL

Do đó, có thể dùng trực tiếp thư mục hiện tại:

printf '\033]0;%s\007' "$PWD"

Khả năng hỗ trợ phụ thuộc vào trình giả lập terminal và cấu hình của nó.

Rút gọn thư mục home thành ~

Khi $PWD bằng $HOME, hoặc nằm bên dưới $HOME, tiền tố thư mục home sẽ được thay bằng ~.

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-} được mở rộng thành chuỗi rỗng khi biến HOME chưa được đặt. Điều này ngăn một giá trị chưa được thiết lập bị xem như tiền tố thư mục home hợp lệ.

Khi thư mục home đã được thiết lập đúng, câu lệnh case phân loại đường dẫn hiện tại.

case "$PWD" in
    "$HOME")
        _terminal_title_path='~'
        ;;
    "$HOME"/*)
        _terminal_title_path="~${PWD#"$HOME"}"
        ;;
    *)
        _terminal_title_path=$PWD
        ;;
esac

Nếu thư mục hiện tại chính xác là thư mục home, tiêu đề sẽ là ~.

/home/hodong -> ~

Nếu thư mục hiện tại nằm bên dưới thư mục home, phần $HOME sẽ bị loại bỏ và ~ được đặt trước phần đường dẫn còn lại.

/home/hodong/projects/nimfsoft.com
-> ~/projects/nimfsoft.com

${PWD#"$HOME"} xóa tiền tố ngắn nhất khớp với $HOME khỏi đầu $PWD.

Ví dụ, với môi trường sau:

HOME=/home/hodong
PWD=/home/hodong/projects/nimfsoft.com

kết quả là:

/projects/nimfsoft.com

Thêm ~ vào đầu sẽ tạo thành tiêu đề cuối cùng:

~/projects/nimfsoft.com

Việc dùng riêng mẫu "$HOME"/* tránh nhận nhầm /home/hodong2/projects là thư mục con của /home/hodong.

/home/hodong2/projects
-> /home/hodong2/projects

_terminal_title_path là biến shell tạm thời. Biến được tạo trong hàm shell mặc định vẫn tồn tại trong shell hiện tại, vì vậy biến này được xóa bằng unset sau khi sử dụng để giảm khả năng xung đột tên.

Cập nhật tiêu đề sau khi chạy cd

Chỉ đặt tiêu đề một lần khi shell khởi động là chưa đủ. Tiêu đề cũng cần được cập nhật sau mỗi lần thư mục làm việc thay đổi.

Hàm sau gọi lệnh cd tích hợp ban đầu của shell, sau đó chỉ cập nhật tiêu đề khi việc chuyển thư mục thành công:

cd() {
    command cd "$@" || return $?
    set_terminal_title
}

Nếu gọi trực tiếp cd "$@" bên trong hàm, chính hàm này sẽ được gọi lại và gây đệ quy.

cd "$@"

command cd "$@" bỏ qua hàm cùng tên và gọi lệnh cd tích hợp của shell.

command cd "$@"

"$@" giữ nguyên toàn bộ đối số:

cd
cd -
cd /usr/local/src

Nếu việc chuyển thư mục thất bại, trạng thái lỗi được trả về và tiêu đề không thay đổi.

command cd "$@" || return $?

Do đó, khi thử chuyển đến một thư mục không tồn tại, cả thư mục hiện tại và tiêu đề terminal đều được giữ nguyên.

Bản vá .shrc hoàn chỉnh

Áp dụng bản vá sau vào ~/.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 \\$ "

Lần gọi set_terminal_title đầu tiên phản ánh thư mục làm việc tại thời điểm .shrc được nạp.

set_terminal_title

Sau đó, hàm bao cd sẽ cập nhật tiêu đề sau mỗi lần chuyển thư mục thành công.

Nạp lại cấu hình:

. ~/.shrc

Khi chuyển vào thư mục dự án:

cd ~/projects/nimfsoft.com

tiêu đề sẽ là:

~/projects/nimfsoft.com

Khi chuyển ra ngoài thư mục home, đường dẫn đầy đủ được hiển thị:

cd /usr/local/src
/usr/local/src

Vì sao không gọi hàm trong PS1

Có thể nghĩ đến cách viết:

PS1='$(set_terminal_title)\u@\h:\w \$ '

Tuy nhiên, trong môi trường FreeBSD sh được sử dụng cho bài viết này, cách đó không hoạt động như mong đợi. Dấu nhắc hiển thị sai và tiêu đề terminal không được cập nhật.

Vì vậy, dấu nhắc hiện có được giữ nguyên:

PS1="\u@\h:\w \\$ "

Thay vì phụ thuộc vào cách shell đánh giá lại dấu nhắc, cấu hình cập nhật tiêu đề ngay sau khi cd thành công.

Lưu ý

Cấu hình này yêu cầu trình giả lập terminal hỗ trợ chuỗi tiêu đề OSC và cho phép ứng dụng thay đổi tiêu đề.

Ví dụ giả định tên thư mục thông thường. Nếu tên thư mục có thể chứa ký tự điều khiển terminal như ESC hoặc BEL, đường dẫn cần được làm sạch trước khi đưa vào chuỗi OSC.

Tiêu đề được cập nhật khi thư mục thay đổi thông qua hàm cd được định nghĩa ở đây. Lần gọi ban đầu xử lý thư mục khởi đầu của shell.

Tóm tắt

Quy tắc hiển thị cuối cùng:

/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

Cấu hình nhỏ này giúp phân biệt các cửa sổ terminal dễ dàng hơn khi làm việc đồng thời trên nhiều dự án.