5

I have bash script with set -o nounset option (and I want that!). Now, I want construct a command invocation, but I don't know number of arguments beforehand, so I want to use an array for that (example below). However, when ARRAY is an empty array, "${ARRAY[@]}" fails.

Question: how to @-expand array ("${ARRAY[@]}") so that the expansion does not fail when set -o nounset is on?

Example:

# Clone git repo. Use --reference if ${reference_local_repo} exist.
reference_local_repo=.....
test -d "${reference_local_repo}" \
    && reference=("--reference" "${reference_local_repo}") \
    || reference=()
git clone "${reference[@]}" http://address/of/the/repo

Of course, I could use the following instead:

# bad example
reference=''
test -d "${reference_local_repo}" && reference="--reference ${reference_local_repo}"

... but that wouldn't work if the path to local repo contained a whitespace.

As a workaround, instead of reference=() i use reference=("-c" "dummy.dummy=dummy"). That way I avoid empty array, and Bash does not complain. Alternatively, i can (rename the array variable and) have "clone" as the first array element. So I got this working, but I'd like to learn The Proper Way.

For the record, I'm using GNU bash, version 4.3.42(1)-release (x86_64-pc-linux-gnu).

Piotr Findeisen
  • 19,480
  • 2
  • 52
  • 82
  • 1
    Interesting. I wouldn't consider `${reference[@]}` to *be* an unset parameter, just because the array happens to be empty. `set -u; bar=; echo $bar` doesn't raise the same error. It might be worth reporting this as a bug. – chepner Mar 29 '16 at 14:43
  • There's a reason that `set -u` is frowned on in freenode's #bash -- like `set -e`, its definition leads to unintuitive behaviors. – Charles Duffy Mar 29 '16 at 14:45
  • @chepner Executing `bar=` sets the variable `bar` to something (Yes, a NUL) thus it is not un-set. No, that is not a bug. –  Mar 29 '16 at 22:15
  • I realize that; I'm just not sure that an empty array is any less "set" than an empty string. Although it does fit with the idea that something like `"${foo[@]}"` disappers, rather than expanding to the empty string, when `foo` is empty. – chepner Mar 29 '16 at 23:08
  • @chepner, indeed -- just as "$@" is either some words or nothing at all. But never an error. – Piotr Findeisen Mar 31 '16 at 07:21
  • `$@` is special, though, as it is mandated to exist by the POSIX spec. – chepner Mar 31 '16 at 11:51

3 Answers3

6

To answer your specific question: The very old and simple way to deal with this is:

${reference[@]+"${reference[@]}"}

If reference is unset, nothing is expanded.
If it is set, all its components are expanded.

Read the historical roots for this use:

Once upon 20 or so years ago, some broken minor variants of the Bourne Shell substituted an empty string "" for "$@" if there were no arguments,


Of course, in this specific case:

test -d "${reference_local_repo}" && abool="" || unset abool
git clone ${abool+--reference "$reference_local_repo"} http://address/of/the/repo

When abool is set to NUL ("") (or some other value if you so choose to use), it is set, and in the next line it expands to what is after the plus (yes, as exactly two parameters).

When abool is unset, it completely disappears in the next line expansion.


Maybe this is more verbose:

unset abool
if test -d "${reference_local_repo}"; then abool="ValidDir"; fi
git clone ${abool+--reference "$reference_local_repo"} http://address/of/the/repo
3

I don't understand why you're using an array here. You could just:

test -d "${reference_local_repo}" \
  && reference="${reference_local_repo}" \
  || reference=""
git clone ${reference:+--reference "$reference"} http://address/of/the/repo

Now there are no undefined variables, and no mucking about with arrays for what is actually a single value.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • I like this simple approach. To be sure: will `${reference:+--reference "$reference"}` expand to exactly two arguments, no matter what characters are inside the `$reference` variable? ... I will have problem accepting this as an answer. Although solves my problem, it doesn't answer my question. I'm a bit lost. – Piotr Findeisen Mar 29 '16 at 19:55
  • This is using effectively the same quoting as in your array-based implementation, so it ought to be behave equivalently. That is, if `reference=("--reference" "${reference_local_repo}")` also results in a two-element array, then this should always expand to exactly to arguments. – larsks Mar 29 '16 at 20:02
  • 1
    Thanks for answer & explanation. I +1, but ended up accepting @BinaryZebra's, as directly answering the question. – Piotr Findeisen Mar 30 '16 at 09:28
  • @PiotrFindeisen This solution, as larsks explains in his comment, does work, and uses a the same approach I used in my answer (though it's better applied to your particular case). This should be the accepted answer – Enrico Mar 30 '16 at 13:05
-1

You may use an auxiliar variable (or just redefine the same variable) to check if an array has anything:

foo=${your_array[@]:-}

and then:

git clone ... "${foo}" ...

This is compatible with the nounset flag. The :- expansion at the end of the variable (${your_array[@]:-}) will yield an empty string if $your_array is undefined.

Enrico
  • 766
  • 8
  • 19
  • I don't understand this solution. In `git clone "${refs}" …` what will be first argument to clone sub-command when there ain't any reference? – Piotr Findeisen Mar 29 '16 at 19:56
  • @PiotrFindeisen it will be empty, unless you specify a default value. You can do that like this: `refs=${reference[@]:-hello world}` (where "hello world" would be the default value). Of course you have to tweak it to fit your script, this is just an example. – Enrico Mar 30 '16 at 01:11
  • @PiotrFindeisen I updated the answer to make it more generic, I hope that helps. – Enrico Mar 30 '16 at 01:15
  • 1
    Sorry, but this doesn't work. `foo` ends up empty and i pass `''` to `git clone`, which yells `fatal: repository '' does not exist` at me. – Piotr Findeisen Mar 30 '16 at 09:30
  • @PiotrFindeisen I was concretely answering your question (read the title!): **How to @-expand possibly empty array in Bash when nounset is set?**. This doesn't work for you as it is (or as it was previous to the edit I did yesterday) because you have to adapt it to your particular implementation, but this _does_ work and does address the thing you remarked as your question. Please reconsider your downvote. – Enrico Mar 30 '16 at 13:02
  • Oh, indeed. I agree this uses @ to expand an array, but it also pretty much kills the purpose of @-expansion (as illustrated by the example in question). I edited the question title for clarity. Btw, I can't un-downvote unless you edit. – Piotr Findeisen Mar 31 '16 at 07:15