I would like to write a script that requires -c and -f where each requires an option.
When I run my script below I get some unexpected errors:
$ ./user.sh -c
./user.sh: option requires an argument -- c
Usage: user.sh -c username -f filename
-c username
-f SSH public key
$ ./user.sh -c gg
Error: You have not given a filename.
In the first case, I would have liked it said I am missing the option for -c and in the second case I would have liked it said I am missing -f.
Question
How do I make such error handing, and what am I doing wrong?
user.sh
#!/bin/bash
usage () {
echo "Usage: user.sh -c username -f filename"
echo " -c username"
echo " -f SSH public key"
echo ""
}
if ! [ "$*" ]; then
usage
exit 1
fi
while getopts "c:f:" opt; do
case $opt in
c) user=$OPTARG;;
f) filename=$OPTARG;;
\?)
echo
usage
exit 1;;
*) echo "Internal error: Unknown option.";;
esac
done
if ! [ $filename ]; then
echo "Error: You have not given a filename."
exit 1
fi
if ! [ $user ]; then
echo "Error: You have not given an username."
exit 1
fi
The
c:says ‘the-coption must be followed by a username’.The error message says ‘the
-coption was not followed by a username’.Granted, it didn’t mention ‘username’ but that’s because it doesn’t know what it is that follows the option
-c.The
getoptsbuilt-in cannot handle mandatory options; you have to code that for yourself by checking that the mandatory options were in fact passed. It also doesn’t worry if the same option is specified twice; your code has to deal with that if it matters. (It’s easy to let the last specified value take effect.)Modern style is to avoid option letters before mandatory arguments. I’m not wholly in favour of the change; it means that the ordering of the arguments becomes critical in a way that using option letters to indicate what follows does not. Without option letters, you’d write:
./user.sh username filename, but with option letters, you can write either of these and expect it to work:Note that the onus is on you to worry about extra arguments too. You’ll typically use:
to remove the processed arguments, and you can then do:
And variations on that theme. Note that the error report is sent to standard error, not to standard output — the
>&2redirection sends standard output (file descriptor 1) to standard error (file descriptor 2) instead.To avoid ambiguity, I’d code your usage function a little differently:
The inner braces do I/O redirection en masse, without starting a subshell. That can be useful when you need to send a number of echo commands to the same place. I’ve also presented the detail information a little differently, so that a user isn’t confused into thinking that ‘SSH public key’ is three arguments to follow the
-f. If there were any pure-option flags, they’d be followed by blanks: