The previous section might have led you to ask, What about if I actually do want the contents of my variable to undergo whitespace splitting and glob expansion?
You may be tempted to do this to store a set of options for repeated use in later commands, for example:
# Not recommended curlopts='--location --cookie-jar ~/cookies --cookie ~/cookies' curl $curlopts --head -- http://www.example.org/ curl $curlopts -- http://www.example.net/
If we double-quoted the $curlopts expansions here, the options would be treated together as one argument, and wouldn't work correctly. However, this still seems to be a useful property of variables—so how can we use it safely?
The answer is that this is an ideal application of the positional parameters in the POSIX shell, or (preferably) array variables in Bash:
# POSIX (if necessary)
(
set -- --location --cookie-jar ~/cookies --cookie ~/cookies
curl "$@" --head -- http://www.example.org/
curl "$@" -- http://www.example.net/
)
# Bash (better)
curlopts=(--location --cookie-jar ~/cookies --cookie ~/cookies)
curl "${curlopts[@]}" --head -- http://www.example.org/
curl "${curlopts[@]}" -- http://www.example.net/
The first form uses a subshell, surrounding the commands that require the curl options in parentheses, so that the set command that sets the positional parameters for reuse only applies within that block. This works, but it can be awkward to manage several such parameter stacks. If Bash is available, using arrays as in the second form makes the task much easier, and require such forced scoping.
You can build and store whole command strings this way, including the command name itself, executing it by simply expanding the whole array. Here's an example of building all the options and arguments for a grep command line:
#!/bin/bash
# Example starting values
extended=0
fixed=1
patterns=(foo bar)
extra_files=()
# Start with the grep command line grepcmdline=(grep)
# If the extended or fixed flags are set, add -F or -E if ((extended)) ; then grepcmdline+=(-E) elif ((fixed)) ; then grepcmdline+=(-F) fi
# Add each pattern to check for pattern in "${patterns[@]}" ; do grepcmdline+=(-e "$pattern") done
# Add option terminator grepcmdline+=(--)
# Add files to check grepcmdline+=(* "${extra_files[@]}")
# Run command! "${grepcmdline[@]}"
This can be very useful for wrapper scripts, designed to simplify the use of a complicated command for one specific application, such as searching a fixed set of files for a set of patterns specified by the user.