Wednesday, June 10, 2009

Using command_not_found_handle to VERY quick access hosts via ssh.

Some time ago, I had noticed that my .bash_history contains a lot of similar records like 'ssh 192.168.0.10', 'ssh 192.168.0.24' etc. So I thought that this is not the True Unix Way and attempted to change this situation.

First of all, I generated a list of bash aliases with entries like the following:

...
alias 123='ssh user@192.168.0.123'
alias 125='ssh user@192.168.0.125'
...


But this solution looked too ugly. Another solution was to add lots of ssh_config entries as described here; it was ugly too.

Another thing that made these two hacks useless was that I needed to connect to hosts in other networks, like 192.168.1.x, 192.168.20.x etc, and it wasn't possible to add all of them to .bash_aliases or ~/.ssh/config.

So I invented the wheel.

In Debian (and Ubuntu too) there is a valuable addendum to bash: function command_not_found_handle that accepts the name of non-existing command and decides what to do: call another program instead this command or something else. By default, it searches the command name in apt package cache and advices user to install the appropriate package.

I wrote my own function and placed it to ~/.bashrc:
command_not_found_handle () {
if [[ ! "$1" ]] ; then
return 127
fi

n="$1"

if echo $n| perl -ne 'exit(/^([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/ ? 0:1)' ; then
ip=192.168.20.$n
elif echo $n| perl -ne '
exit (/^([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.
([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])
$/x ? 0:1)' ; then
ip=192.168.$n
elif echo $n| perl -ne '
exit (/^([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.
([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.
([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.
([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])
$/x ? 0:1)' ; then
ip=$n
else
return 127
fi

ssh $ip
}


How does it work. The function analyzes its argument, and if this argument is a number from 0 to 255, it calls "ssh 192.168.0.xx" (where xx is the argument passed to this function); if the argument passes contains two dot-separated numbers like xx.xx (each is from 0 to 255), it calls ssh 192.168.xx.xx. If the argument validates as a correct IP-address, "ssh ip" is called. In any other cases, exit code 127 is returned and bash handles "command not found" error by itself.

So, I write only the last number of the IP address and it automatically being translated to the full IP:
bvk@host ~$ 15
# becomes ssh 192.168.0.15
bvk@host ~$ 29
# becomes ssh 192.168.0.29
bvk@host ~$ 10.51
# becomes ssh 192.168.10.51
bvk@host ~$ 172.19.4.10
# becomes ssh 172.19.4.10

Also, I wrote some entries to ~/.ssh/config to specify different parameters for different hosts:

Host 192.168.20.251
User special_user1

Host 192.168.20.252
User special_user2

Host 192.168.20.254
User special_user3

Host *
User ordinary_user


This allows me to connect to the hosts 192.168.20.251, 192.168.20.252 and 192.168.20.253 as respective users, and to all other hosts as ordinary_user.

P.S. It's often impossible to give names to all the hosts in our networks, because hosts in our test environment go up and down frequently. So we have to connect to them via their IPs.