ajxs
ajxs

Reputation: 3618

Strange behavior with parameter expansion in program arguments

I'm trying to conditionally pass an argument to a bash script only if it has been set in the calling script and I've noticed some odd behavior. I'm using parameter expansion to facilitate this, outputting an option only if the corresponding variable is set. The aim is to pass an argument from a 'parent' script to a 'child' script.

Consider the following example:

The calling script:

#!/bin/bash
# 1.sh

ONE="TEST_ONE"
TWO="TEST_TWO"

./2.sh \
    --one "${ONE}" \
    "${TWO:+"--two ${TWO}"}" \
    --other

and the called script:

#!/bin/bash
# 2.sh

while [[ $# -gt 0 ]]; do
    key="${1}"

    case $key in
        -o|--one)
        ONE="${2}"
        echo "ONE: ${ONE}"
        shift
        shift
        ;;
        -t|--two)
        TWO="${2}"
        echo "TWO: ${TWO}"
        shift
        shift
        ;;
        -f|--other)
        OTHER=1
        echo "OTHER: ${OTHER}"
        shift
        ;;
        *)
        echo "UNRECOGNISED: ${1}"
        shift
        ;;
    esac
done

output:

ONE: TEST_ONE
UNRECOGNISED: --two TEST_TWO
OTHER: 1

Observe the behavior of the option '--two', which will be unrecognised. It looks like it is being expanded correctly, but is not recognised as being two distinct strings. Can anyone explain why this is happening? I've seen it written in one source that it will not work with positional parameter arguments, but I'm still not understanding why this behaves as it does.

Upvotes: 2

Views: 66

Answers (1)

Inian
Inian

Reputation: 85550

It is because when you pass $2 as a result of parameter expansion from 1.sh you are quoting it in a way that --two TEST_TWO is evaluated as one single argument, so that the number of arguments in 2.sh result in 4 instead of 5

But that said, using your $2 as ${TWO:+--two ${TWO}} would solve the problem, but that would word-split the content of $2 if it contains spaces. You need to use arrays.

As a much more recommended and fail-proof approach use arrays as below on 1.sh as

argsList=(--one "${ONE}" ${TWO:+--two "${TWO}"} --other)

and pass it along as

./2.sh "${argsList[@]}"

or if you are familiar with how quoting rules work (how and when to quote to prevent word-splitting from happening) use it directly on the command line as below. This would ensure that the contents variables ONE and TWO are preserved even if they have spaces.

./2.sh \
    --one "${ONE}" \
    ${TWO:+--two "${TWO}"} \
    --other

As a few recommended guidelines

  • Always use lower-case variable names for user defined variables to not confuse them with the environment variables maintained by the shell itself.
  • Use getopts() for more robust argument flags parsing

Upvotes: 3

Related Questions