How do I parse optional arguments in a bash script if no order is given?

71,056

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

Share:
71,056

Related videos on Youtube

ShanZhengYang
Author by

ShanZhengYang

Updated on September 18, 2022

Comments

  • ShanZhengYang
    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
      Admin over 7 years
    • Admin
      Admin over 7 years
      @orion With getopts, I would need to specify each argument combination? 3 & 4, 3 & 5, 3 & 4 & 5, etc. ?
    • Admin
      Admin over 7 years
      No, 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
      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 as program --flag1 VAL --flag2 VAL. If you ran run_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE, the program would run as program --flag1 VAL --flag2 VAL --optflag2 10. How can you get such behavior with getopts?
    • Admin
      Admin over 7 years
      Oh, 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 of getopts - you are giving options, not receiving them. I'll post the answer below.
    • Admin
      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
    Jasen over 7 years
    I usually do while (( $# )) instead of while :; and often quit with an error in the * case
  • Jasen
    Jasen over 7 years
    this answers the title but not the question.
  • John N
    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
    Jasen over 7 years
    no problem, it's still a good answer, just the question dodged it.
  • G-Man Says 'Reinstate Monica'
    G-Man Says 'Reinstate Monica' about 7 years
    No, 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'
    G-Man Says 'Reinstate Monica' about 7 years
    You 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
    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
    Raphael over 5 years
    NB: In case of set -o nounset the first solution will error out if no parameter is given. Fix: case ${1:-} in
  • demisx
    demisx over 4 years
    But, how do you set it to the value of each flag instead of "SET"?