0

I often connect through ssh to Docker containers running on a remote server. Every time the container is stopped or removed, I need to copy my ssh key on the container again, in order to connect. For this reason, on my Mac laptop I put this simple bash script, whose goal is simply to add my ssh key to the Docker container to which I want to connect.

#!/bin/bash
# A POSIX variable
OPTIND=1 # Reset in case getopts has been used previously in the shell.
# Default parameter value:
PORT=...
PUBKEY=...
HOST_IP=...
# show_help function
show_help() {
 echo "Usage: $(basename "0ドル") [-h] [-p PORT] [-f PUBKEY] [-i HOST_IP]"
 echo
 echo " -p PORT add key to container running on port PORT (default: ...)"
 echo " -f PUBKEY add key file PUBKEY (default: ...)"
 echo " -i HOST_IP connect to host HOST_IP (default: ...)"
 echo
 return
}
while getopts ":h:p:f:i:" option; do
 case "$option" in
 \?)
 echo "invalid argument"
 show_help
 exit 1
 ;;
 h)
 show_help
 exit 0
 ;;
 p) PORT=$OPTARG
 ;;
 f) PUBKEY=$OPTARG
 ;;
 i) HOST_IP=$OPTARG
 ;;
 esac
done
shift $((OPTIND-1))
USER_AT_HOST="root@$HOST_IP"
PUBKEYPATH="$HOME/.ssh/$PUBKEY"
ssh-copy-id -i "$PUBKEYPATH" "$USER_AT_HOST" -p "$PORT"
exit 0

I have two problems:

  1. (smaller) the arguments have non-intuitive letters (f for the PUBKEY, i for the HOST_IP) because if I'm not mistaken, getopts only support single-letter arguments. Is there any way to circumvent this limitation? If not, could you suggest some more creative/self-explanatory letters for the arguments? πŸ™‚

  2. (bigger) currently the -p argument (PORT) is mandatory, and the user used for the ssh connection is always root. I would like to modify the script so that, if the -p argument is not passed, then another argument -u (USER) must be passed, and the connection command becomes

     USER_AT_HOST="$USER@$HOST_IP"
     PUBKEYPATH="$HOME/.ssh/$PUBKEY"
     ssh-copy-id -i "$PUBKEYPATH" "$USER_AT_HOST"
    

    instead than

     USER_AT_HOST="root@$HOST_IP"
     PUBKEYPATH="$HOME/.ssh/$PUBKEY"
     ssh-copy-id -i "$PUBKEYPATH" "$USER_AT_HOST" -p "$PORT"
    

How could I modify the script in order to obtain this result?

asked Mar 16, 2022 at 20:50

1 Answer 1

2

With this implementation, you don't specify default values up front.

The arguments to ssh-copy-id are built up in an array. Note the use of += to append to the array.

Other notes:

  • OPTIND=1 is only needed if you source this script. If you're running it as a script, then the variable is unset before you get to getopts

  • I'd remove exit 0 at the bottom of the script: let it exit with ssh-copy-id's exit status.

  • Your code forgot i in the opt string for getopts. Double check this.

  • ?* is a shell pattern (aka "wildcard") that matches at least 1 character.

  • Get out of the habit of using ALLCAPS variable names, leave those as reserved by the shell. One day you'll write PATH=something and then wonder why your script is broken.

  • an alternate way to set a default value is to use the : command

    : ${var:="default value"}
    

    But that's a bit opaque.


#!/bin/bash
# show_help function
show_help() {
 echo "Usage: $(basename "0ドル") [-h] [-p PORT|-u] [-f PUBKEY] [-i HOST_IP]"
 echo
 echo " -p PORT add key to container running on port PORT (default: ...)"
 echo " -u USER help string here..."
 echo " -f PUBKEY add key file PUBKEY (default: ...)"
 echo " -i HOST_IP connect to host HOST_IP (default: ...)"
 echo
 return
}
while getopts ":hi:p:f:u:" option; do
 case "$option" in
 p) port=$OPTARG ;;
 f) pubkey=$OPTARG ;;
 i) host_ip=$OPTARG ;;
 u) user=$OPTARG ;;
 h) show_help; exit 0 ;;
 ?) echo "invalid argument"; show_help; exit 1 ;;
 esac
done
shift $((OPTIND-1)) # not needed if there are no positional parameters
[[ -z $host_ip ]] && host_ip="default value"
[[ -z $pubkey ]] && pubkey="default value"
cmd_args=( -i "$HOME/.ssh/$pubkey" )
case "$port,$user" in
 ,) # empty port and empty user
 echo "If you don't specify a port, you need to specify a user" >&2
 exit 1
 ;;
 ,?*) # empty port but user is specified
 cmd_args+=( "$user@$host_ip" )
 ;;
 ?*,*) # port has been given. user doesn't matter
 cmd_args+=( -p "$port" "root@$host_ip" )
 ;;
esac
ssh-copy-id "${cmd_args[@]}"

To address your comments:

  • only options that take an argument use a colon. To demonstrate

    With a colon,

    $ set -- -h
    $ getopts :h: opt
    $ declare -p opt OPTARG OPTIND
    declare -- opt=":"
    declare -- OPTARG="h"
    declare -i OPTIND="2"
    

    As the manual for getopts says:

    If a required argument is not found [...] a colon (:) is placed in name and OPTARG is set to the option character found.

    Without a colon, option parsing proceeds as you expect

    $ unset opt OPTARG OPTIND
    $ set -- -h
    $ getopts :h opt
    $ declare -p opt OPTARG OPTIND
    declare -- opt="h"
    declare -- OPTARG
    declare -- OPTIND="2"
    
  • Regarding default values, do the option parsing first. Then, if the variables are not set by options, provide the default value.

    [[ -z $host_ip ]] && host_ip="default value"
    

    This means: if the value of host_ip is empty (-z), set the value to a default value.

answered Mar 16, 2022 at 21:49
1
  • thanks for the answer! A few questions: while getopts ":hi:p:f:u:". Shouldn't there be a : between h and i, i.e., while getopts ":h:i:p:f:u:"? Also, you say that With this implementation, you don't specify default values up front.. However, I find default values useful because I often use the script with the same parameters. Could you explain a bit more in detail why I can't use default parameters? Finally, could you please explain the meaning of these two lines [[ -z $host_ip ]] && host_ip="default value" [[ -z $pubkey ]] && pubkey="default value"? Thanks again Commented Mar 17, 2022 at 14:11

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.