9
\$\begingroup\$

I'm an intensive user of AWS EC2 instances, many instances are launched, stopped, repurposed, etc.

To connect to any instance using SSH I must keep track of their IPs.

The bash script I wrote (following the question I asked on SO) uses aws-cli to do the heavy lifting for me, leaving me to remember only the logical names I've given to my instances.

Here is the main code, including the auto-completion code:

# connect to machine
function sash {
 if [ -z 1ドル ]; then
 echo "Please enter machine name"
 return 1
 fi
 local instance ip pem
 instance=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=1ドル" "Name=instance-state-name,Values=running" --query 'Reservations[*].Instances[].[KeyName,PublicIpAddress]' --output text)
 if [ -z "${instance}" ]; then
 echo Could not find an instance named 1ドル
 return 1
 else
 ip=$(echo $instance | awk '{print 2ドル}')
 pem=$(echo $instance | awk '{print 1ドル}')
 echo "Connecting to 1ドル ($ip)"
 ssh -i ~/.aws/$pem.pem ubuntu@$ip
 fi
}
function clear_sash {
 unset -v _sash_instances
}
# completion command
function _sash {
 if [ -z "${_sash_instances}" ]; then
 _sash_instances="$( aws ec2 describe-tags --filter Name=key,Values=Name Name=resource-type,Values=instance --query Tags[].Value --output text )"
 fi
 local curw
 COMPREPLY=()
 curw=${COMP_WORDS[COMP_CWORD]}
 COMPREPLY=($(compgen -W "${_sash_instances}" -- $curw))
 return 0
}
complete -F _sash sash

The main function (sash) takes the first parameter, and queries aws ec2 for a machine with a 'Name' tag with that value, extracts its public ip and pem file, and calls the proper ssh command.

The auto-completion command (_sash) enumerates the names of all EC2 machines, and keeps them in a cache in the scope of the shell.

A small helper command (clear_sash) is used to clear the cache for the auto-complete.

Since this is the first function I've written in bash, I would love to hear some opinions on the code, style, caching decisions, etc.

asked Feb 16, 2014 at 8:21
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

It's mostly fine, but I would suggest some minor improvements.

Give a proper name to 1ドル early on, for example:

host=1ドル
if [ -z $host ]; then
 echo "Please enter machine name"
 return 1
fi

You use it in several places later and it will make the code more readable.


Instead of this:

if [ -z "${instance}" ]; then

you can use [[ ... ]] instead and drop the double quotes:

if [[ -z $instance ]]; then

This is a bit ugly:

ip=$(echo $instance | awk '{print 2ドル}')
pem=$(echo $instance | awk '{print 1ドル}')

It's ugly because you're spawning two awk processes for the same input. You could use a single read instead:

read pem ip junk <<< $instance

Or perhaps slightly cleaner to read into an array:

read -a arr <<< $instance
ip=${arr[1]}
pem=${arr[0]}

The array solution is especially good if the indexes of ip and pem are dynamic, for example if they come from variables:

read -a arr <<< $instance
# given: ip_idx=2 and pem_idx=1
ip=${arr[$ip_idx - 1]}
pem=${arr[$pem_idx - 1]}

(It would be cleaner to have the *_idx 0-based, but I used this example for the sake of illustrating simple arithmetics in array indexes.)

For the record, here's my earlier uglier awk solution:

set -- $(awk '{print 1,ドル 2ドル}' <<< $instance)
pem=1ドル
ip=2ドル

This is not as good as read, because it spawns an awk process, and it overwrites the positional parameters 1ドル, 2ドル, ... Other variants using $pem_idx and $ip_idx:

set -- $(awk "{print \$$pem_idx, \$$ip_idx}" <<< $instance)
# or:
set -- $(awk -v ip_idx=$ip_idx -v pem_idx=$pem_idx '{print $(pem_idx), $(ip_idx)}' <<< $instance)

In this code:

local curw
COMPREPLY=()
curw=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=($(compgen -W "${_sash_instances}" -- $curw))

The first COMPREPLY=() is unnecessary because you overwrite it soon after anyway.

Also, I think it's better to write local on the same line as the assignment:

local curw=${COMP_WORDS[COMP_CWORD]}

Actually, since the line fits within 80 characters, I'm not sure I would use the local curw at all:

COMPREPLY=($(compgen -W "${_sash_instances}" -- ${COMP_WORDS[COMP_CWORD]}))

The final return 0 is unnecessary if the last operation is successful. If the last operation is not successful, you probably want to let the function return failure anyway.

answered Apr 23, 2014 at 21:00
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.