Scott Stevenson

Setting the terminal title in zsh

By default, zsh doesn’t set the terminal title, making it inefficient to know which application is running in each tab, or when a long-running application has exited, as each tab must be visited in turn.

Outside the shell, some terminal emulators provide functionality to set the title, typically formatting a string containing one or more of your username, hostname, current working directory, and any currently running application. Such terminal emulators include Kitty and Ghostty (Ghostty’s shell integration originated as a fork of Kitty’s).

However, even if you are using such a terminal emulator, the format is unlikely to be optimal for your circumstances. If you frequently connect to other hosts via SSH, including the hostname is valuable. However, if you rarely or never use SSH, including the hostname confers no distinct information and takes up space.

As such, whether your terminal emulator already sets the title or not, you can benefit from configuring a custom format. Several zsh features, especially prompt sequence expansion and hooks, compose to make this easy.

Almost all popular terminal emulators for Linux or macOS support xterm’s escape sequence for setting the title. This is formed of the escape character \e]0; (or \033 in octal), followed by the title contents, and terminated by the bell character \a. To test support in your current terminal emulator, run:

printf "\e]0;Hello\a"

A terminal emulator with a title of Hello

In addition to the printf built-in, which is defined by POSIX, zsh provides a print built-in, which is specific to zsh and offers extended functionality.

print has a -P flag, which enables expansion of the prompt sequences you may already be familiar with if you’ve customised your zsh prompt. A full listing of available prompt sequences is provided in the zsh documentation, but some frequently used examples are:

So, to set the title to <username>@<hostname>: <cwd> run:

print -Pn "\e]0;%n@%m: %~\a"

(the -n flag suppresses printing a trailing newline character).

A terminal emulator with a title including the username, hostname, and directory

However, if you now move to a different directory with cd, the title will show the wrong directory until you run the print command again. You can keep the title synchronised with the current state of the shell using zsh’s hooks.

Zsh’s hooks allow registering functions to be called each time a specific event occurs, such as after the working directory changes. The two most useful events for setting the title are precmd, triggered before each new prompt is drawn, and preexec, triggered before a command is executed.

The add-zsh-hook function allows adding a function to be triggered on a given event, and must first be loaded with:

autoload -Uz add-zsh-hook

You can then add a function which calls print to set the title each time a new prompt is drawn, by adding a hook to the precmd event:

add-zsh-hook precmd () {
  print -Pn "\e]0;%n@%m: %~\a"
}

In contrast to precmd hooks, preexec hooks are run after a command line has been entered, but before it is executed, and so allow the title to include the name of the currently running command. Functions registered for preexec are called with positional arguments, the second of which is the command line being executed. Therefore, you can use print within a preexec hook to set the title to the same value as in the precmd hook, but with the currently running command appended:

add-zsh-hook preexec () {
  print -Pn "\e]0;%n@%m: %~ — $2\a"
}

A terminal emulator with a title suffixed with the currently running command

When a command exits, a new prompt will be drawn, triggering the precmd hook again, which will set the title back to not include a command name.

To use these hooks, make sure to disable title setting in your terminal emulator; otherwise, your own hooks will be overridden. For example, set shell_integration no-title in the configuration file for Kitty, and shell-integration-features = no-title in the configuration file for Ghostty.

Bringing this all together, you can set the title to show the username, hostname, working directory, and current command (when one is running), and keep the title up to date, by adding the following to ~/.zshrc:

autoload -Uz add-zsh-hook

add-zsh-hook precmd () {
  print -Pn "\e]0;%n@%m: %~\a"
}

add-zsh-hook preexec () {
  print -Pn "\e]0;%n@%m: %~ — $2\a"
}

You can further customise this using conditional checks in the precmd or preexec hooks, allowing you to display information like the hostname only when connected to an SSH session, or show the username based on a specific condition, such as it being root. For example, to show the username and hostname only if you’re connected via SSH, branch on $SSH_CONNECTION being set:

if [[ -n "$SSH_CONNECTION" ]]; then
  print -Pn "\e]0;%n@%m: %~\a"
else
  print -Pn "\e]0;%~\a"
fi

Think about what other contextual information would be useful to you, and try it out!