I am trying to read the arguments provided to a bash shell script like:
sh test.sh -s "xyz" -e staging --files file1 --tags tags
Below is my code:
options=$(getopt --long service:,environment:,help::,files::,tags:: -o s:e:h::f::t:: -- "$@")
eval set -- "$options"
for opt; do
echo "$opt :: $2"
case "$opt" in
-h|--help)
echo "help"
exit 0
;;
-s|--service)
service_name="$2"
echo "Option chosen - service --> ${service_name}"
shift
;;
-e|--env)
environment_name="$2"
echo "Option chosen - environment --> ${environment_name}"
shift
;;
-t|--tags)
tag_names="$2"
echo "Option chosen - tags -> $tag_names"
shift
;;
-f|--files)
filenames="$2"
echo "Option chosen - files -> ${2}"
shift
;;
--)
shift
break;;
\?)
echo "Error: Invalid option"
exit 0
;;
esac
shift
done
However, this is the result I am getting:
-s :: xyz
Option chosen - service --> xyz
xyz :: staging
-e :: --files
Option chosen - environment --> --files
staging :: --tags
--files ::
Option chosen - files ->
:: file1
--tags :: tags
Option chosen - tags -> tags
::
-- ::
I was expecting something like:
-s :: xyz
Option chosen - service --> xyz
-e :: staging
Option chosen - environment --> staging
--files :: file1
Option chosen - files -> file1
--tags :: tags
Option chosen - tags -> tags
As you can see, the "environment" and "files" arguments are not correct.
I have tried with "shift 2" as well but still unable to get the correct results. Any idea what I am doing wrong?
First, most of the problem seems to be your doubled colons.
$: set -- -s "xyz" -e staging --files file1 --tags tags
$: echo "$@"
-s xyz -e staging --files file1 --tags tags
$: getopt --long service:,environment:,help::,files::,tags:: -o s:e:h::f::t:: -- "$@"
-s 'xyz' -e 'staging' --files '' --tags '' -- 'file1' 'tags'
$: getopt --long service:,environment:,help:,files:,tags: -o s:e:h:f:t: -- "$@"
-s 'xyz' -e 'staging' --files 'file1' --tags 'tags' --
Doubled colons mean a value is optional, but you have to bear in mind how that will be implemented. How will the engine know whether a following token is the argument to an option, or a different argument? Does it make any sense for --files
to not have an argument?
c.f. this example of the model you are almost using.
You might wants getopts
(with an s
) instead. There's a good example of that here.
In general, avoid optional arguments; if you need optional arguments, provide a default value, and allow a supplied option to override that value... but if the user types in the option flag, it should either require an argument, or not allow one. Consistent clarity of design is better than bugged flexibility. With a little more thought and code, you can get the best of both. Using the option should generally either be boolean, or a flag that there is a following argument to collect.