Tuomas Siipola Articles Projects

Distinguish local and remote terminals

If you're a heavy user of the command-line, you may have run the wrong command in the wrong terminal window once or twice by accident. This can, of course, have unexpected consequences especially when connected to a production server.

The common utilities like terminal emulators, shells and SSH clients don't really provide anything to prevent these kinds of mistakes. At best, you get a prompt with different username and hostname but telling this apart from your local prompt can be difficult at a glance.

Is there a better way to quickly identify the correct window? In this article we're going to use colors indicate the remote terminal session:

Screenshot of terminals with different background colors

The only terminal emulator I'm aware of that does something similar out of the box is King's Cross. It changes the title bar background color when using SSH or the root user. This article's approach is more configurable and will work with a number of different terminal emulators.

Changing background color

You can usually configure terminal emulator's color scheme to your liking by using a GUI or modifying some configuration file. In addition, many terminal emulators support escape sequences to change colors on the fly. For example, running the following command should change your terminal's background color to a delightful red:

$ printf "\033]11;rgb:ff/00/00\033\\"

If the new color hurts your eyes, reset the background to its original color using:

$ printf "\033]111\033\\"

It's possible that these commands did not work for you. If this is the case, you should:

Automation

Now that we hopefully have the commands to change the background color, we just need to find a way to run them automatically when using a command like ssh. There are a couple of ways to accomplish this:

The best approach, in my opinion, is to create a wrapper for ssh. This is a flexible solution that should work in any situation. Here's the first attempt:

#!/bin/sh
printf "\033]11;rgb:55/11/11\033\\"
ssh "$@" # Pass arguments to the real SSH executable
printf "\033]111\033\\"

This script works in normal circumstances. However, if the script is interrupted (e.g. with Ctrl+C), the background color is not reset. To fix this, let's add a trap to always reset the background color after exit:

#!/bin/sh
trap 'printf "\033]111\033\\"' EXIT INT
printf "\033]11;rgb:55/11/11\033\\"
ssh "$@"

There's still one corner case we need to handle: using ssh with pipelines and redirects. This can be a convenient way of transferring data:

$ ssh server 'mysqldump db_name' > backup-file.sql

In this case, we do not want any escape sequences to mess up our backups or other data. With [ -t 1 ] we can detect whether file descriptor 1, i.e. the standard output, is attached to a terminal. This gives us the final script:

#!/bin/sh
if [ -t 1 ]; then
    trap 'printf "\033]111\033\\"' EXIT INT
    printf "\033]11;rgb:55/11/11\033\\"
fi
ssh "$@"

To use the wrapper, store the script in a file like ~/bin/myssh and run chmod +x ~/bin/myssh to make it executable. Then, run alias ssh=~/bin/myssh to replace ssh command with the wrapper. Now try connecting to a server using the regular ssh server and you should see the background change. Finally, add the alias to your .bashrc (or equivalent file for your shell) to enable the wrapper permanently.

Tweaking

Although we have a working solution, there are still many things we can tweak. First and foremost, you should find a background color that works well with your current color scheme.

Secondly, the same background color is used for all sessions. It may be helpful, for instance, to differentiate development, staging and production servers from each other. We can change the background color based on the name:

# Match substring in arguments like "-p 1234 server1-prod uname -a"
case "$@" in
    *-prod* | *myserver.com* )         printf "\033]11;rgb:55/11/11\033\\" ;;
    *-stg*  | *staging.myserver.com* ) printf "\033]11;rgb:11/55/11\033\\" ;;
    *-dev*  | *localhost* )            printf "\033]11;rgb:11/11/55\033\\" ;;
    * )                                printf "\033]11;rgb:55/11/55\033\\" ;;
esac

The text may be hard to read if only the background color is changed. Conveniently, there are additional escape sequences that can be used to change the whole color scheme on the fly. For example, to use the excellent Solarized Light color scheme, run the following commands:

printf "\033]10;rgb:65/7b/83\033\\"   # set foreground color
printf "\033]11;rgb:fd/f6/e3\033\\"   # set background color
printf "\033]12;rgb:58/6e/75\033\\"   # set text cursor color
printf "\033]13;rgb:58/6e/75\033\\"   # set mouse foreground color
printf "\033]14;rgb:93/a1/a1\033\\"   # set mouse background color
printf "\033]4;0;rgb:ee/e8/d5\033\\"  # set black
printf "\033]4;1;rgb:dc/32/2f\033\\"  # set red
printf "\033]4;2;rgb:85/99/00\033\\"  # set green
printf "\033]4;3;rgb:b5/89/00\033\\"  # set yellow
printf "\033]4;4;rgb:26/8b/d2\033\\"  # set blue
printf "\033]4;5;rgb:d3/36/82\033\\"  # set magenta
printf "\033]4;6;rgb:2a/a1/98\033\\"  # set cyan
printf "\033]4;7;rgb:07/36/42\033\\"  # set white
printf "\033]4;9;rgb:cb/4b/16\033\\"  # set bright black
printf "\033]4;8;rgb:fd/f6/e3\033\\"  # set bright red
printf "\033]4;10;rgb:93/a1/a1\033\\" # set bright green
printf "\033]4;11;rgb:83/94/96\033\\" # set bright yellow
printf "\033]4;12;rgb:65/7b/83\033\\" # set bright blue
printf "\033]4;13;rgb:6c/71/c4\033\\" # set bright magenta
printf "\033]4;14;rgb:58/6e/75\033\\" # set bright cyan
printf "\033]4;15;rgb:00/2b/36\033\\" # set bright white

Run the following to restore the original color scheme:

printf "\033]110\033\\" # reset foreground color
printf "\033]111\033\\" # reset background color
printf "\033]112\033\\" # reset text cursor color
printf "\033]113\033\\" # reset mouse foreground color
printf "\033]114\033\\" # reset mouse background color
printf "\033]104\033\\" # reset other colors

Changing and managing color schemes using raw escape sequences may become unwieldy. Instead, you can use utilities like theme.sh, pywal and xtermcontrol to make things easier.

Understanding escape sequences

How do these escape sequences work? Terminals basically display text but with special sequences we can send commands to do more interesting things, such as controlling output formatting or building complete TUIs. Let's break down the familiar escape sequence "\033]11;rgb:ff/00/00\033\\":

With this in mind, the other escape sequences presented in this article should not look too cryptic anymore. These escape sequences among others are documented thoroughly in XTerm Control Sequences.