Choose a Terminal Safely with sensible-terminal

Debian's sensible-terminal wrapper picks an emulator for you, but the variables it trusts run through a shell, so a careless value is a real risk. You will finish with a repeatable way to open one, pass it a title or command, and diagnose a missing emulator.

The examples match sensible-utils 0.0.22 installed on this machine. Allow about ten minutes. You need a Debian or Debian-derived shell with sensible-terminal installed. The checks below are ordinary user commands. Opening a graphical terminal needs a working graphical session, but the selection and failure tests do not change system configuration.

1. Confirm the installed wrapper

Check which executable will run and record its package version:

$ command -v sensible-terminal
/usr/bin/sensible-terminal
$ dpkg-query -W -f='${Package} ${Version}\n' sensible-utils
sensible-utils 0.0.22

The wrapper is a shell script, not a terminal emulator itself. It chooses a candidate and runs it with the arguments you supply. This distinction matters when a desktop session has no usable emulator, or when a variable points back to the wrapper.

Checkpoint: if command -v prints nothing, install sensible-utils through your normal package-management process. Do not add a shell alias or copy a replacement script into a system directory just to make this check pass.

2. Understand the selection order

The installed script tries candidates in this order:

  1. TERMINAL_EMULATOR, if it is set.
  2. SENSIBLE_TERMINAL_EMULATOR, if it is set.
  3. A desktop-specific command named sensible-terminal-desktop, when XDG_CURRENT_DESKTOP is set.
  4. x-terminal-emulator.

The first two variables contain command strings. The wrapper passes your arguments to the string through sh -c, so shell syntax in those variables is meaningful. Treat them as configuration, not as a safe place for untrusted input. Do not set either variable globally to text copied from an untrusted document.

If a candidate exits with status 126 or 127, the wrapper continues to the next candidate. Other exit statuses are returned immediately. That lets a missing command fall through, while a real launch failure remains visible.

3. Test argument passing without opening a window

Use a temporary environment override and printf as a harmless stand-in for a terminal emulator. This does not write a file or alter your shell configuration:

$ TERMINAL_EMULATOR='/usr/bin/printf emulator-argument:%s\n' sensible-terminal 'demo-title'
emulator-argument:demo-title

The command string is evaluated by sh -c. The word TERMINAL_EMULATOR after the command string becomes the shell's $0; your supplied arguments begin at $1. The wrapper's quoting preserves an argument containing spaces when it invokes the candidate.

Checkpoint: repeat the test with two arguments if your caller needs them:

$ TERMINAL_EMULATOR='/usr/bin/printf arg:%s\n' sensible-terminal 'first value' 'second value'
arg:first value
arg:second value

This is a test of wrapper argument handling, not proof that a graphical terminal accepts those arguments. A particular emulator may interpret -T, --wait or -e differently. Check that emulator's own manual before putting those options in a script.

4. Use the documented options

The wrapper accepts an optional title, a wait request, and a command to execute in the selected emulator:

sensible-terminal [-T title] [--wait] [-e cmd...]

Use one clear command boundary, and quote paths or arguments containing spaces:

$ sensible-terminal -T 'Build log' -e /usr/bin/sh -c 'printf "%s\n" "terminal is ready"; exec /bin/sh'

This example will normally open a terminal and leave an interactive shell after printing its message. It requires a graphical session and a configured emulator, so do not use it as the first diagnostic on a remote or headless machine. If the emulator treats -e differently, follow that emulator's documentation.

There is no persistent change to undo here. The title, wait flag and command are arguments for this invocation only. To stop the child terminal, close that terminal in the ordinary way.

5. Verify the fallback and failure path

You can test the special fall-through rule without allowing the desktop fallback to open a window. Make the first candidate exit 127, which means unavailable, and make the second candidate exit 1, which is a real failure returned immediately:

$ TERMINAL_EMULATOR='/bin/sh -c "exit 127"' SENSIBLE_TERMINAL_EMULATOR=/bin/false sensible-terminal
$ printf 'exit status: %s\n' "$?"
exit status: 1

The first candidate was skipped and the second candidate supplied the non-zero result. This is a controlled check of the wrapper's selection logic. A missing command is a setup problem, not a reason to run the wrapper with sudo.

When all candidates are unavailable, the wrapper prints Couldn't find a terminal emulator! and a suggestion to set TERMINAL_EMULATOR, then returns 1. The exact point at which that happens depends on your desktop-specific command and whether x-terminal-emulator is installed. On a graphical machine, do not deliberately disable every candidate merely to reproduce the message: the fallback may still try to open a window.

6. Avoid recursion and unsafe configuration

The script detects when a variable resolves back to sensible-terminal and skips that candidate. This prevents a direct loop such as:

$ TERMINAL_EMULATOR=sensible-terminal SENSIBLE_TERMINAL_EMULATOR=/bin/false sensible-terminal
$ printf 'exit status: %s\n' "$?"
exit status: 1

That protection does not make arbitrary command strings safe. A value such as my-terminal --option is interpreted by a shell, and command substitutions or redirections in the value can run with your account's permissions. Keep the variable under your control, set it for one command while testing, and inspect a persistent setting before removing it:

$ printf 'TERMINAL_EMULATOR=%s\n' "${TERMINAL_EMULATOR-unset}"
$ printf 'SENSIBLE_TERMINAL_EMULATOR=%s\n' "${SENSIBLE_TERMINAL_EMULATOR-unset}"
$ printf 'XDG_CURRENT_DESKTOP=%s\n' "${XDG_CURRENT_DESKTOP-unset}"

Recovery: if you added a temporary export in the current shell, undo it with unset TERMINAL_EMULATOR SENSIBLE_TERMINAL_EMULATOR. If it came from a shell startup file or desktop session, remove the line from that configuration and start a new session. Do not delete a whole configuration file to remove one variable.

Done means