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:
(smaller) the arguments have non-intuitive letters (
ffor thePUBKEY,ifor theHOST_IP) because if I'm not mistaken,getoptsonly 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? π(bigger) currently the
-pargument (PORT) is mandatory, and the user used for the ssh connection is alwaysroot. I would like to modify the script so that, if the-pargument is not passed, then another argument-u(USER) must be passed, and the connection command becomesUSER_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?
1 Answer 1
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=1is only needed if yousourcethis script. If you're running it as a script, then the variable is unset before you get togetoptsI'd remove
exit 0at the bottom of the script: let it exit withssh-copy-id's exit status.Your code forgot
iin 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=somethingand 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
getoptssays: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_ipis empty (-z), set the value to a default value.
-
thanks for the answer! A few questions:
while getopts ":hi:p:f:u:". Shouldn't there be a:betweenhandi, i.e.,while getopts ":h:i:p:f:u:"? Also, you say thatWith 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 againDeltaIV– DeltaIV2022εΉ΄03ζ17ζ₯ 14:11:33 +00:00Commented Mar 17, 2022 at 14:11
You must log in to answer this question.
Explore related questions
See similar questions with these tags.