0

I'm using a variation of the BashFAQ manual loop and I want to convert it to a function and assign the arguments to local variable but I can't figure out the correct syntax. Here's what I have:

function parseArguments() {
    local arguments=( "$@" )

    while :; do
        case ${1:-} in
            -d|--debug)
                set -o xtrace
                # [...more stuff...]
                ;;
            -p|--prompt)
                IsPromptEnabled=true
                ;;
            --)
                shift
                break
                ;;
            -?*)
                error ${LINENO} "\"${1:-}\" is an unknown option" 1
                ;;
            *)
                break
        esac

        shift
    done
}

parseArguments "$@"

This works fine as is until I try to replace $1 in the loop with the value from arguments. ${arguments[0]} and every other variation I can think of fails, I'd like to understand why (and figure out the solution).

1
  • You use shift, and this command affects (modifies) $1. Hence if you just replace $1 by a different variable, you break the semantics of your program. Commented Jun 29, 2021 at 6:41

2 Answers 2

1

You should loop over the array

function parseArguments() {
    local arguments=( "$@" )

    for a in "${arguments[@]}"
    do
        case ${a:-} in
            -d|--debug)
                set -o xtrace
                # [...more stuff...]
                ;;
            -p|--prompt)
                IsPromptEnabled=true
                ;;
            --)
                # shift
                break
                ;;
            -?*)
                error ${LINENO} "\"${a:-}\" is an unknown option" 1
                ;;
            *)
                break
        esac

        shift
    done
}

parseArguments "$@"
Sign up to request clarification or add additional context in comments.

1 Comment

Looping over the arguments array and also using shift may cause confusion. If you're going to use the array, I'd recommend removing the shift commands.
1

It's because you set the arguments array to the initial list of arguments.

For example, if you call parseArguments -d -p, then you start with $1 set to "-d" and $2 set to "-p". So arguments gets set to ("-d" "-p") as expected.

On the first time through the loop, it recognizes ${1:-} as "-p", sets IsPromptEnabled=true, and shifts that argument away.

So at the end of the first time through the loop, $1 is "-d" and $2 is unset. But arguments hasn't changed, it's still set to ("-d" "-p"). The shift doesn't update it, and it never gets reassigned, so it's always going to have the full list of arguments.

Even after "-d" gets recognized, acted on, and shifted away (making the actual argument list empty), arguments will still be set to ("-d" "-p").

If you want arguments to hold the non-option arguments, you should move the assignment to it after the option-parsing loop, so it gets set after all the options have been shifted away.

5 Comments

Your answer helped me understand what is happening @GordonDavisson but I'm not sure how to alter the function in a while loop based on that. Diego's solution worked but I can see your point regarding shift being less clear, is there an alternative way you would write it? Also, pardon the break from SO convention but thank you for your answer, I really appreciated your description!
@ChristinWhite It really depends on why you want to use an array instead of just $1 etc, because my first preference would be to just skip the array altogether and use what you have in the question. If you want an array of the non-option arguments, I'd assign that after the loop. If it's something else... I'd need to know what that something else is.
For the current purpose I'm primarily trying to write as clean and clear of a function as I can. I do need to add some options that take an argument though and I don't think Diego's solution will work for that, at least not as flexibly as just using positional parameters. I'm sure GreyCat wrote the original that way for a reason, learning why was the ultimate goal.
@ChristinWhite Yeah, looping over the arguments won't work if some options take arguments themselves; what you could do is iterate over indexes with something like for ((i=0; i<${#arguments[@]}; i++)); do..., examine "${arguments[i]}" in the loop, if the option itself takes an argument it'll be "${arguments[i+1]}" and you'd use ((i++)) to skip past it. You can do it this way, it's just more work.
I’ll give that a try to learn but I think you’re right, it’s probably superfluous extra work. I really appreciate you taking the time to help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.