5

In a bash script, I call another programme, but I want to configure that programme with a command line option. The following works:

AREA_ARG=""
if __SOME_SETTING__ ; then
  AREA_ARG=" --area us,ca "
fi

process_data -i /some/path $AREA_ARG

i.e. bash either executes process_data -i /some/path, or process_data -i /some/path --area us,ca .

However shellcheck complains!

$ shellcheck test.sh 

In test.sh line 7:
process_data -i /some/path $AREA_ARG
                           ^-------^ SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
process_data -i /some/path "$AREA_ARG"

For more information:
  https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...

I understand the principle, but I want/need the variable to split on the space so that process_data gets 2 arguments.

What's the Proper Way™ to do this in bash?

4

4 Answers 4

13

Use arrays

Here is your code re-written using arrays. Also to be a working example (ls in place of your command), and using correct case for variables (coding standard says that capitalised names are reserved for system).

#!/bin/bash

area_args=()
if true ; then
  area_args=(-l -a)
fi

ls "${area_args[@]}"
4
  • Worth clarifying that although bash, ksh and (I think) zsh support arrays, sh (and dash) don't?
    – roaima
    May 18 at 9:56
  • 2
    @roaima, ...well, sh supports exactly one array in each context, the argument list. To use it here might be something like: set -- as an equivalent to area_args=( ), and set -- -l -a vs areas_args=( -l -a ), then ls "$@" to actually pass them through. May 18 at 18:44
  • @roaima, zsh supported arrays long before bash, that array=(foo bar) is actually from zsh. csh (the first in the late 70s), tcsh, rc, es, yash, fish all support arrays / lists. Shells that don't are the exception. Arrays / lists should be the primary data type in shells (and is in many) as the main thing a shell is meant to do is run commands with their list of arguments. The main reason why POSIX didn't specify arrays for sh is (I believe) because it's based on ksh, and the ksh array design (which bash copied unfortunately) is by far the worst. May 18 at 19:43
  • @Stéphane thank you for the info. I'm not (yet) a zsh user so my comment could only be definitive about the shells I know. The question is tagged bash so there's nothing wrong in expecting an array type to be present; I thought it was worth clarifying which common shells do/don't support it
    – roaima
    May 18 at 22:02
6

For your specific use case, there's a much easier answer:

unset -v area
if __SOME_SETTING__; then
  area="ca,us"
fi

process_data -i /some/path ${area+ --area "$area" }

If for some reason you can't use arrays, and you need more control than the above practice offers, you might consider using a function wrapper.

withOptionalArea() {
  if __SOME_SETTING__; then
    "$@" --area us,ca
  else
    "$@"
  fi
}

withOptionalArea process_data -i /some/path

...but this should only be necessary in exceptional circumstances.


Really, use an array.

2

Many argument parsing libraries let you use an = sign to join a long option name and its value. (This includes Gnu libc's parsers and Python's standard argparse library.) If you're calling such a program, and you have no need for more generality, you can use:

AREA_ARG=--area=us,ca
2

The Proper Way for storing arbitrary commands or arguments in a variable would be to use an array, see How can we run a command stored in a variable?

But you can just tell shellcheck you like it just the way it is, as long as you're sure it's ok (i.e. you don't have whitespace that should stay intact, or glob characters that could cause issues, and didn't change IFS to something that would trash this):

AREA_ARG=""
if __SOME_SETTING__ ; then
  AREA_ARG=" --area us,ca "
fi


# shellcheck disable=SC2086 # split on purpose
process_data -i /some/path $AREA_ARG

See https://www.shellcheck.net/wiki/Directive

1
  • 1
    You also need to make sure that at the time process_data -i /some/path $AREA_ARG is run, $IFS contains the space character and none of the characters of $AREA_ARG that you don't want it to be split on. You may want to do something like (IFS=' '; set -o noglob; exec process_data -i /some/path $AREA_ARG) as an approximation of what you'd do with languages with proper splitting operators (like zsh's ${(s[ ])AREA_ARG}). May 19 at 5:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.