Terminal Color Scripting


SSH Over the Rainbow

I tend to multitask far more than necessary, and will often ssh into multiple remote servers at once. I also tend to clear my scrollback regularly, since I like my workspaces neat and tidy.

Unfortunately, that tends to lead to this problem:


Which tends to lead to a long night of restoring backups. You do have backups, right?

(Not interested in the explanation? Check out the gist instead!)

What's the Problem?

Obviously, dd if=/dev/random of=/dev/hda0 is not exactly a likely catastrophe. However, it's still annoying when you're comparing log files from production and dev to figure out a bug, and it's momentarily confusing when you try to ssh from a server into another server. And of course, the catastrophic will eventually happen, given enough trials.

The core of the problem is the mental context switch. Terminal windows are great at difficult context switches - by default, you have an 80x24 terminal window in a fixed width font, the systems you're accessing remotely likely have very similar prompts, and there's an astonishing array of 256 colors to choose from, so we naturally only use black and white. So, when it's time to switch back to the 2, 20, or n terminals you have open, you don't only have to remember what you were doing - you now have to remember which terminal is which.

Solving the Problem

Here's my solution:


Apple's Terminal.app actually has some powerful scripting capability - if you can tolerate AppleScript's syntax for long enough. Using a simple script, we're able to swap the themes of Terminal.app on the fly:

1
2
3
tell application "Terminal"
  set current settings of selected tab of window 1 to first settings set whose name is "Pro"
end tell

The above will only change the Terminal theme to Pro, and making a version of that script for each theme is a bit silly:

1
2
3
on run argv
  tell application "Terminal" to set current settings of selected tab of window 1 to (first settings set whose name is (item 1 of argv))
end run

When we run osascript terminal_color.scpt "Theme", it will change the theme of the active Terminal tab / window as expected.

Now, on to the shell script:

1
2
osascript ~/.bash/terminal_color.scpt "Remote"
/usr/bin/ssh "$@"

The use of $@ is very important here - I've seen plenty of scripts use $1 instead. $@ passes all positional parameters intact without any in script interpretation or expansion, so doing things like ./ssh-script $SERVER will work as expected.

There are two problems with this approach. I want the shell to turn back to a normal theme when I've exited the SSH session; I have servers that it's totally ok to muck about and wreck all the data, those should be a different color. We can accomplish the first with the following:

1
2
3
4
5
6
7
8
function onExit {
  osascript ~/.bash/terminal_color.scpt "Pro"
}

trap onExit EXIT

osascript ~/.bash/terminal_color.scpt "Remote"
/usr/bin/ssh "$@"

Traps in bash are quite cool, and the syntax is pretty simple: trap COMMAND SIGNALS; but a full exploration of traps would be another article entirely. Essentially, we're telling bash to call our function whenever any command in the script EXITs, which allows us to set the theme back to the default.

However, we still need to set safe servers to a different theme:

1
2
3
4
5
6
7
8
9
safe_server=`echo "$@" | grep -i -m=1 -f ~/.safe_servers | wc -l`

if [ $safe_server -gt 0 ]; then
  osascript ~/.bash/terminal_color.scpt "SafeRemote"
else
  osascript ~/.bash/terminal_color.scpt "Remote"
fi

/usr/bin/ssh "$@"

A blog on cool uses of grep could easily occupy me for a decade. So instead, I use grep in a decidedly mundane fashion: -i sets case insensitivity, -m=1 matches only a single line (no need to find all the matches for this case), and -f simply sets the file input. Because I prefer whitelists to blacklists, I assume any server that's not specifically designated as a safe server will blow up and cause a nuclear winter; reversing the logic should be fairly trivial if you'd rather make a list of production servers.

That brings us to these as the final scripts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function onExit {
  osascript ~/.bash/terminal_color.scpt "Pro"
}

trap onExit EXIT

safe_server=`echo "$@" | grep -i --max-count=1 -f ~/.safe_servers | wc -l`

if [ $safe_server -gt 0 ]; then
  osascript ~/.bash/terminal_color.scpt "SafeRemote"
else
  osascript ~/.bash/terminal_color.scpt "Remote"
fi

/usr/bin/ssh "$@"
1
2
3
on run argv
  tell application "Terminal" to set current settings of selected tab of window 1 to (first settings set whose name is (item 1 of argv))
end run

"But I use iTerm2!" you exclaim!

1
2
3
on run argv
tell application "iTerm" to set background color of current session of current terminal to (item 1 of argv)
end run

Full instructions are available via a gist on GitHub, so feel free to try it out and fork to your heart's content!

comments powered by Disqus