Distinguish local and remote terminals
- Published
- Last updated
- Revisions
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:
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:
- Request this feature from the authors of the terminal emulator or, if possible, contribute the support yourself
- Investigate if there are other ways of programmatically changing the color scheme in the terminal emulator of your choice
- Switch to a terminal emulator that supports these escape sequences, such as Alacritty or any VTE-based terminal emulator
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:
- SSH has an option called
LocalCommand
which can be used to change the background color after connecting to a server. Unfortunately, there isn't the opposite option that could be used to reset the background color after disconnecting. - The background change command can be put in
.bashrc
file on the server. Similarly, the background reset can be done in.bash_logout
. The downside of this approach is that we need to do it for each server.
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\\"
:
\033]
are two characters that mark the start of Operating System Command (OSC). Specifically,\033
is the number 27 in octal, i.e.ESC
or escape character in ASCII, and]
is just the right square bracket it looks like.\033
is a special syntax supported byprintf
command.11
is the numeric identifier for command that changes the background color of the terminal.;
separates arguments.rgb:ff/00/00
is the first and only argument. The format is based onXParseColor
function from XLib. More familiar#ff0000
syntax is also supported but its use is not encouraged.\033\\
isESC
followed by the backslash\
. This two character sequence is known as the string terminator (ST) and here it marks the end of OSC.\\
is the way of escaping the backslash character in a double-quoted string.
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.