How do I parse optional arguments in a bash script if no order is given?
Solution 1
This article shows two different ways - shift
and getopts
(and discusses the advantages and disadvantages of the two approaches).
With shift
your script looks at $1
, decides what action to take, and then executes shift
, moving $2
to $1
, $3
to $2
, etc.
For example:
while :; do
case $1 in
-a|--flag1) flag1="SET"
;;
-b|--flag2) flag2="SET"
;;
-c|--optflag1) optflag1="SET"
;;
-d|--optflag2) optflag2="SET"
;;
-e|--optflag3) optflag3="SET"
;;
*) break
esac
shift
done
With getopts
you define the (short) options in the while
expression:
while getopts abcde opt; do
case $opt in
a) flag1="SET"
;;
b) flag2="SET"
;;
c) optflag1="SET"
;;
d) optflag2="SET"
;;
e) optflag3="SET"
;;
esac
done
Obviously, these are just code-snippets, and I've left out validation - checking that the mandatory args flag1 and flag2 are set, etc.
Which approach you use is to some extent a matter of taste - how portable you want your script to be, whether you can live with short (POSIX) options only or whether you want long (GNU) options, etc.
Solution 2
use an array.
#!/bin/bash
args=( --flag1 "$1" --flag2 "$2" )
[ "x$3" = xFALSE ] || args+=( --optflag1 "$3" )
[ "x$4" = xFALSE ] || args+=( --optflag2 "$4" )
[ "x$5" = xFALSE ] || args+=( --optflag3 "$5" )
[ "x$6" = xFALSE ] || args+=( --optflag4 "$6" )
[ "x$7" = xFALSE ] || args+=( --optflag5 "$7" )
program_name "${args[@]}"
this will handle arguments with spaces in them correctly.
[edit] I was using the roughly eqivalent syntax args=( "${args[@]}" --optflag1 "$3" )
but G-Man suggested a better way.
Solution 3
In a shell script, arguments are "$1", "$2", "$3", etc. The number of arguments is $#.
If your script doesn't recognize options you can leave out option detection and treat all arguments as operands.
To recognize options use the getopts builtin
Related videos on Youtube
ShanZhengYang
Updated on September 18, 2022Comments
-
ShanZhengYang almost 2 years
I'm confused how to include optional arguments/flags when writing a bash script for the following program:
The program requires two arguments:
run_program --flag1 <value> --flag2 <value>
However, there are several optional flags:
run_program --flag1 <value> --flag2 <value> --optflag1 <value> --optflag2 <value> --optflag3 <value> --optflag4 <value> --optflag5 <value>
I would like to run the bash script such that it takes user arguments. If users only input two arguments in order, then it would be:
#!/bin/sh run_program --flag1 $1 --flag2 $2
But what if any of the optional arguments are included? I would think it would be
if [ --optflag1 "$3" ]; then run_program --flag1 $1 --flag2 $2 --optflag1 $3 fi
But what if $4 is given but not $3?
-
Admin over 7 yearsDuplicate of stackoverflow.com/questions/16483119/…
-
Admin over 7 years@orion With
getopts
, I would need to specify each argument combination? 3 & 4, 3 & 5, 3 & 4 & 5, etc. ? -
Admin over 7 yearsNo, you just set them if you get them, otherwise it reports it wasn't found, so basically you just "get" each option, in whatever order, and specify them in any order, if at all. But just read the bash man page, it's all there.
-
Admin over 7 years@orion I'm sorry, but I still don't quite understand
getopts
. Let's say I force users to run the script with all arguments:run_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSE
which runs the program asprogram --flag1 VAL --flag2 VAL
. If you ranrun_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE
, the program would run asprogram --flag1 VAL --flag2 VAL --optflag2 10
. How can you get such behavior withgetopts
? -
Admin over 7 yearsOh, I didn't understand what you want. I thought you want to receive the arguments the same way your
run_program
does (which would make sense). What you are doing is the opposite ofgetopts
- you are giving options, not receiving them. I'll post the answer below. -
Admin over 7 years@ShanZhengYang, it sounds like you want your program to read the user's mind to know which arguments go with which option flags just based on the argument position—but then you also want to allow arguments to be omitted. You can't have both.
-
-
Jasen over 7 yearsI usually do
while (( $# ))
instead ofwhile :;
and often quit with an error in the*
case -
Jasen over 7 yearsthis answers the title but not the question.
-
John N over 7 years@Jasen I looked at this last night and couldn't for the life of me see why. This morning it's much clearer (and I've seen orion's answer now as well). I'll delete this answer later today (I wanted to acknowledge your comment first, and this seemed the easiest way to do it).
-
Jasen over 7 yearsno problem, it's still a good answer, just the question dodged it.
-
G-Man Says 'Reinstate Monica' about 7 yearsNo, you don’t want the shell to split the parameters into words. You should always quote all references to shell variables unless you have a good reason not to, and you’re sure you know what you’re doing. See Security implications of failing to quote a variable in bash/POSIX shells, in case you aren’t familiar with it, and scroll down to my answer, where I address precisely this problem (with the same technique Jasen uses).
-
G-Man Says 'Reinstate Monica' about 7 yearsYou can streamline this a little by saying
args+=( --optflag1 "$3" )
. You might want to see my answer to our reference work, Security implications of failing to quote a variable in bash/POSIX shells, where I discuss this technique (of building a command line in an array by conditionally appending optional arguments). -
Jasen about 7 years@G-Man Thanks, I had not seen that in the documentation, understanding
[@]
I had a sufficient tool to solve my problem. -
Raphael over 5 yearsNB: In case of
set -o nounset
the first solution will error out if no parameter is given. Fix:case ${1:-} in
-
demisx over 4 yearsBut, how do you set it to the value of each flag instead of "SET"?