Date: Tue, 18 Nov 2025 16:51:56 -0500
From: Chet Ramey <[email protected]>
Message-ID: <[email protected]>
| In general, yes. In this particular case, the inconsistency resulted from
OK.
| Once that inconsistency gets fixed, we can talk about what the `right'
| behavior should be.
There's no real "right" here, those ${var/...} etc operators are
all extensions, and any use of $@ or $* with any of the operators
is also unspecified, so everyone gets to do what they want in
these cases.
| > I also hesitate to imagine how
| > echo $(( $@ ))
| Well, everyone (including the NetBSD sh), manages to display `11' when
| given these two scripts.
Sure, as I said, all shells do it that way, I am just unable to find
even a suggestion in the standard that this way is how it it should be
done.
In the standard, if we had:
| set -- a + b
Then it seems to me from reading it (once more!) that 3 words
should be generated for $(( $@ )) as:
$(( a
+
b ))
That would be completely useless, but nor is it important that
it actually be defined, as one can simply use $* instead of $@
in this kind of construction to get a properly defined and sane
result.
| so I'm not going to worry too much about it.
I think the only thing that could do with fixing is the standard.
| The behavior is pretty varied.
Not so much, just two (actual) variants for your test, when
actually tested properly (not including a quite different form
of unspecified behaviour and assuming a particular implementation)
| Given these four commands:
|
| set a b c; IFS=: o=${@-x}; printf '<%s>\n' "$o"
| set a b c; IFS=: o="${@-x}"; printf '<%s>\n' "$o"
| set a b c; IFS=: o=${@?x}; printf '<%s>\n' "$o"
| set a b c; IFS=: o="${@?x}"; printf '<%s>\n' "$o"
|
| There's the bash/ksh93/mksh/SVR4.2 sh camp, which uses spaces (except mksh
| errors on the `?' expansion); the dash/yash/zsh camp, which consistenly
| uses `:';
Yes.
| and the BSD sh camp, which uses spaces in the first command and
| `:' in the rest.
No, it doesn't. You'd see the problem if you reordered the commands,
or just repeat the first one again later.
I enhanced your test a bit...
set -- a b c; IFS=:; o=$@ s=$*; printf '!Q= <%s> <%s>\n' "$o" "$s"
set -- a b c; IFS=:; o="$@" s="$*"; printf ' Q= <%s> <%s>\n' "$o" "$s"
set -- a b c; IFS=:; o=${@-x} s=${*-x}; printf '!Q- <%s> <%s>\n' "$o" "$s"
set -- a b c; IFS=:; o="${@-x}" s="${*-x}"; printf ' Q- <%s> <%s>\n' "$o" "$s"
set -- a b c; IFS=:; o=${@?x} s=${*?x}; printf '!Q? <%s> <%s>\n' "$o" "$s"
set -- a b c; IFS=:; o="${@?x}" s="${*?x}"; printf ' Q? <%s> <%s>\n' "$o" "$s"
set -- a b c; IFS=:; o=${@+$@} s=${*+$*}; printf '!Q+ <%s> <%s>\n' "$o" "$s"
set -- a b c; IFS=:; o="${@+$@}" s="${*+$*}"; printf ' Q+ <%s> <%s>\n' "$o" "$s"
set -- a b c; IFS=:; o=${@+"$@"} s=${*+"$*"}; printf '+Q+ <%s> <%s>\n' "$o" "$s"
That fixes the problem in your test, and adds the remaining (sane)
standard expansions. (That is, there's no test of the absurdly
insane, and expressly prohibited, ${@=whatever}).
I see three variations in this one, bash/ksh93/mksh (ignoring its error
case for the ? variation, which is a little bizarre, but as all of this
is unspecified, that variation is OK as well).
!Q= <a b c> <a:b:c>
!Q= <a b c> <a:b:c>
!Q- <a b c> <a:b:c>
Q- <a b c> <a:b:c>
!Q? <a b c> <a:b:c>
Q? <a b c> <a:b:c>
!Q+ <a b c> <a:b:c>
Q+ <a b c> <a:b:c>
+Q+ <a b c> <a:b:c>
Then there's dash/yash/zsh/FBSD/NBSD (and various versions of pdksh):
!Q= <a:b:c> <a:b:c>
!Q= <a:b:c> <a:b:c>
!Q- <a:b:c> <a:b:c>
Q- <a:b:c> <a:b:c>
!Q? <a:b:c> <a:b:c>
Q? <a:b:c> <a:b:c>
!Q+ <a:b:c> <a:b:c>
Q+ <a:b:c> <a:b:c>
+Q+ <a:b:c> <a:b:c>
And last, there's poor old bosh
!Q= <a b c> <a b c>
!Q= <a b c> <a:b:c>
!Q- <a b c> <a b c>
Q- <a b c> <a:b:c>
!Q? <a b c> <a b c>
Q? <a b c> <a:b:c>
!Q+ <a b c> <a b c>
Q+ <a b c> <a:b:c>
+Q+ <a b c> <a:b:c>
which is simply broken ... the expansions using the - ? and + operators
are all unspecified, but that first expansion of $* (the !Q= line) is not
(it is one of the few in this test which isn't) and using spaces there is
incorrect (in the second result, for $s), it should be colons, as every
other (mainstream anyway) shell does it.
Again, other than the $* expansions without operators, all of this
is unspecified, so there's no "wrong" here - but I still believe that
treating $@ as if it were $* in all of the unspecified cases is better
(including in the case of simple unquoted $@ when field splitting happens -
where it is defined to behave identically to $* - the wording in the
standard is identical). Quite aside from what makes sense, it makes the
code simpler. There is exactly one "special" case, quoted $@ in places
where field splitting happens - in every other case, simply treat $@
exactly the same as $* and there's a whole lot less code to write.
The other (IMO) reasonable result would be to make the unspecified $@
expansions be errors, and force people to write $* in the cases where
"multiple words" (which is the only point of using $@) makes no sense at all.
kre
ps: I have no idea why you decided to repeat the "set" and IFS=
parts of the test, rather than just doing them once, then doing the
expansions, if you had done it that way, the confusion wrt what the
BSD shells produce wouldn't have occurred.