Is bash doing "$@" expansion incorrectly inside ${var+...}? Is bash doing "$@" expansion incorrectly inside ${var+...}? bash bash

Is bash doing "$@" expansion incorrectly inside ${var+...}?


I tend not to trust spaces inside unquoted parameter expansion. I can't explain why it doesn't work, but I can give you a solution that does: move the space outside the parameter expansion. The set command doesn't mind trailing white space.

$ bash -c 'set bar; set foo ${1+"$@"}; echo "$# $*"'   # 5.0.2(1)2 foo bar$ dash -c 'set bar; set foo ${1+"$@"}; echo "$# $*"'   # 0.5.10.22 foo bar$ ash  -c 'set bar; set foo ${1+"$@"}; echo "$# $*"'   # /bin/sh on FreeBSD 7.32 foo bar$ ksh  -c 'set bar; set foo ${1+"$@"}; echo "$# $*"'   # 93u+ 2012-08-012 foo bar$ zsh  -c 'set bar; set foo ${1+"$@"}; echo "$# $*"'   # 5.72 foo bar

(I ran everything on the latest Debian Testing except ash)


Though this does appear to be a bug, I've run across some weird edge cases with "... $@" before, and this matches expected behavior, at least historically. " $@" (with hanging white space) is undefined behavior, because you're basically asking for all members of the $@ array to be treated as args for the shell parser plus one character of whitespace. It can be a bit difficult to wrap your head around, and some of the things you've said are misleading, so I figured I'd provide more info and a tip for achieving better behavior.

The way you're using "$@" & trying to interchange it with "$*" is going to cause you issues. There are significant differences between $* and $@ behavior that matches their intended usage. If you're using $@, it is because you want to do something like this:

wrapper_fxn() {  wrapped_cmd "$@"}

When you wrap $@ in quotes (and any other array for that matter), bash evaluates it as if each argument/member of the array is first wrapped in double quotes. However, there is a bit more magic involved, since it doesn't actually wrap it in double-quotes, but removes the double-quotes but indicates to change the parsing behavior at a later step.

The reason the special behavior of "$@" exists is to help deal with frustrating quoting & argv interference issues.

So, think of them like this:

  • $@ : multiple args / for propagating args while preserving quoting of whitespace
  • $* : single arg / for concatenating all strings in an array to be a single arg

My advice: don't do "$@ "; it is undefined behavior. As soon as you're quoting like "$@", you're trying to do argument-expansion, and to add another argument or array of arguments, just do it like so:

# Making sure to have quotes around each new argsomecmd "$newFirstArg" "$@" "${additionalArray[@]}" "$newLastArg"

... it will better communicate intention, and almost never yield unexpected behavior.

More in depth

With all that said, the strange edge behavior of "$@" has to do when you use it like: "... $@", as does " $@". Bash will merge one of the items of the array, depending on the position of ... with the extra characters.

When you add anything to the right or left of $@ in a quoted statement, e.g. "extra stuff $@" or "$@ extra stuff", what bash will do is just to add the extra stuff onto the first or last member of the array, respectively. The exception to this is if there is another array in the double-quoted statement, perhaps $@ duplicated like so: "$@ $@". It will actually merge the last item of the first array, with the first item of the last array. So, if $@ has three members, the final argv[] of "$@ $@" will have 5 args, not 6.

Here is a utility function to help poke at the behavior:

# example functionprint-args () {     local i=0;    for arg in "$@";    do        printf "[${i}]:%s\n" "\"$arg\"";        let i++;    done}# good example arg-set covering many of the important edge casesset "one \"one" two three# One case, modify it to see the various permutations of the edge casesprint-args " $@ $@ "#>[0]:" one "one"#>[1]:"two"#>[2]:"three one "one"#>[3]:"two"#>[4]:"three "