Command Not Found Handler to Quickly SSH to Hosts
I believe that part of being a good Systems Administrator is being lazy - that lazier you are, the more you’ve automated.
Out with the old
In the spirit of laziness, many years ago I wrote a little bash function to enable me to SSH to hosts simply by typing or pasting their hostname/IP address as though they were commands. Here’s the original function:
function updatehostsandaliases() {
unset HOSTFILE
HOSTFILE="~/.hostfile"
rotate='|/-\'
for host in `sed -e 's/[, ].*//' ~/.ssh/known_hosts ; cat ~/.hostfile`; do
# check if it already exists and skip
type ${host} > /dev/null 2>&1 && continue
alias ${host}="ssh ${host}"
# Remove domainnames
domain=${host%%.*}
if [ "${domain}" != "${host}" ]; then
if ! type ${domain} > /dev/null 2>&1; then
alias ${domain}="ssh ${host}"
fi
if ! type scp${domain} > /dev/null 2>&1; then
eval "function scp${host}() { scp \${1} ${host}:\${2:-/tmp}; }"
fi
fi
type scp${host} > /dev/null 2>&1 && continue
eval "function scp${host}() { scp \${1} ${host}:/tmp; }"
done && echo aliases rebuilt
}
The function defined aliases in the form of alias <hostname>="ssh <hostname>"
and this worked quite well save for one annoying shortcoming - it only worked for hosts I’d been to before (taken from ~/.ssh.known_hosts
) or which I had manually added to ~/.hostfile
. Any new hosts would simply give command not found
.
In with the new
In search of a more elegant solution, I stumbled across a Gist by Wijnand Modderman-Lenstra which makes use of bash’s and zsh’s command not found handlers.
Since bash version 4, if a function named command_not_found_handle
is defined, this function gets called for any instances of command not found
. The same goes for zsh except it’s suffixed handler instead of handle.
Wijnand’s solution is much nicer than my clunky old function as it works for any host, whether you’ve been to it before or not.
I’ve taken his idea and made some modifications, namely
- to only attempt SSH if there was only 1 argument (if there was more than one argument, you probably fat-fingered a command rather than wanted to SSH somewhere.)
- to check if the host is already in
~/.ssh/known_hosts
(that’s a good indication it’s safe to SSH to.) - if it isn’t an IP address but looks like a valid domain name, prompt for confirmation before performing a DNS lookup (in order to avoid leaking any potentially sensitive data in the lookup.)
- test if SSH is reachable with ssh-keygen first
- if all SSH tests fail, switch to using thefuck to work out what I meant
Important
If you use thefuck in a command not found handler, you ==must== enable confirmation or you may end up running something destructive like rm
without being prompted first! Set require_confirmation = True
in ~/.thefuck/settings.py
to turn confirmation on.
# Modifications based on https://gist.github.com/tehmaze/1080039
# Original license follows:
# _______
# ____________ _______ _\__ /_________ ___ _____
# | _ _ \ _ | ____\ _ / | |/ _ \
# | / / / / | | | /___/ _ | | / /
# |___/___/ /___/____|________|___ | |_| |___|_____/
# \__/ |___|
#
#
# Put this in your ~/.zshrc or ~/.bashrc:
# source ~/.zsh/ssh
#
# (c) 2011 Wijnand Modderman-Lenstra <[email protected]>
# MIT License
#
zmodload zsh/pcre
is_valid_ip() {
IP="$1"
python - "${IP}" <<EOF
import socket
import sys
# Check IPv6
if socket.has_ipv6:
try:
socket.inet_aton(sys.argv[1])
sys.exit(0)
except socket.error:
pass
# Check IPv4
try:
socket.inet_aton(sys.argv[1])
sys.exit(0)
except socket.error:
sys.exit(1)
EOF
return $?
}
try_connect_ssh() {
# If only one argument is supplied, try to
# connect there using ssh
if [[ $# -eq 1 ]]; then
# Have we SSHd here before or does it look like an IP
if (grep -q ${1} ~/.ssh/known_hosts) || (is_valid_ip "$1") ; then
SAFE=true
fi
# Haven't seen it before and it doesn't look like an IP
# Does it look like a valid domain name?
if [ -z "${SAFE}" -a "${1}" -pcre-match "(?=^.{4,253}$)(^(?:[a-zA-Z](?:(?:[a-zA-Z0-9\-]){0,61}[a-zA-Z])?\.)+[a-
zA-Z]{2,}$)" ]; then
# Prompt for confirmation so we don't leak data in DNS queries
read -k1 -q "CONFIRM?Do you want to try to SSH to ${1}?"
if [ ${CONFIRM} = "y" ] ; then
# If it ain't an IP, is it a hostname?
if (host -W 2 $1 2>/dev/null | grep -v NXDOMAIN >/dev/null) ; then
RESOLVES="$1"
fi
fi
fi
# It was either already known, an IP or a hostname, test for SSH and connect if present
if [ $? -eq 0 -a -n "${SAFE}" -o -n "${RESOLVES}" ]; then
ssh-keyscan -T 1 "$@" 2>/dev/null | grep "$@" >/dev/null 2>&1 && ssh "$@" && return 0 || return 1
fi
fi
# Bork out, we couldn't handle the command
return 1
}
case "${SHELL}" in
*/zsh)
command_not_found_handler() {
try_connect_ssh "$@" || TF_CMD=$(TF_ALIAS=fuck PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES=$(alias) thefuck "$@") && eval $TF_CMD && print -s $TF_CMD
return $?
}
;;
*/bash)
command_not_found_handle() {
try_connect_ssh "$@"
return $?
}
;;
esac