Search code examples
bashcommand-linescriptingargumentsgetopts

How to getopts after $1 in bash


I want to run the script as ./script speed -a some_value -b some_value also ./script accuracy -a some_value -b some_value

What I tried is

while [ -n "$1" ]; do 

    case "$1" in

    speed)  

        for i in "${@:2}"
        do while getopts "a:b:" opt; do
            case "${opt}" in 
                a) list=$OPTARG
                    echo $list
                    ;;
                b) list2=$OPTARG
                    echo $list2
                    ;;
            esac
           done     
        done
        echo "speed option passed" 
        break ;;

    accuracy) echo "similar to above function"
              break ;;


    *) echo "Option $1 not recognized" ;; # In case you typed a different option other than a,b,c

    esac

    shift

done

getting output as when ran ./script speed -a some_value

this is something
speed option passed

I don't know if this is possible or not or is there any way to do something like this?


Solution

  • I don't think you want the outer loop (while [ -n "$1" ]; do), unless you want to be able to process multiple subcommands in a single run. That is, do you want this:

    ./script speed -a some_value -b some_value accuracy -a some_value -b some_value
    

    To be roughly equivalent to this:

    ./script speed -a some_value -b some_value
    ./script accuracy -a some_value -b some_value
    

    If not, remove that loop because you'll only be processing one subcommand per run. If you do want to process more than one subcommand per run, then you need to take some extra steps to remove or skip over the arguments relating to one subcommand before running the next one.

    You do want to remove the for i in "${@:2}" loop -- that just doesn't mix with the way getopts works. What you do need to do is skip over the subcommand name before processing the options. You could either use shift to remove the subcommand name, something like this:

    case "$1" in
        speed)
            shift    # Remove the first argument ("speed")
            while getopts "a:b:" opt; do
                ...
    

    If you're going to allow multiple subcommands, add shift $((OPTIND-1)) sfter the getopts loop, to get it ready for the next subcommand.

    Or you could modify OPTIND to tell getopts that it's already processed the first argument and it can go to work on the second:

    case "$1" in
        speed)
            OPTIND=2    # Tell getopts to start processing at arg #2
            while getopts "a:b:" opt; do
                ...
    

    If you're going to handle multiple subcommands with this method... well, it's a bit more complicated and I think I'll duck the question.

    Yet another option is to put the code for each subcommand in a function, and call it with all but the first argument:

    speed_subcommand() {
        local OPTIND
        while getopts "a:b:" opt; do
            ...
    }
    
    case "$1" in
        speed)
            speed_subcommand "${@:2}" ;;
        accuracy)
            accuracy_subcommand "${@:2}" ;;
        ...
    

    This method doesn't really mix with handling multiple subcommands per run.