1

I'm trying to write a shell script which should work under any POSIX shell, and I found what seems to be an odd quirk of dash. I'm wondering whether this is something I can rely on working under other POSIX compatible shells, or whether it is simply an unintentional feature of the dash shell.

In an arithmetic expansion a variable can be written either with or without the inital dollar sign (except in the case of the positional parameters, which must always be written with a dollar sign, $1, $2 etc).

But I've found that the behaviour of "$((X))" is different from "$(($X))" in that "$(($X))" seems to be expanded twice, rather than just once. I.e. if my variable X contains the name of another variable (X=Y), which in turn contains a numeric value (Y=1) then "$(($X))" will return 1, while "$((X))" will just produce an 'Illegal number: Y' error.

Can I rely on this behavior being the same in other POSIX shells?

The odd behavior

"$((X))" and "$(($X))" do not behave the same.

$ X=Y; Y=1
$ echo "$((X))"
dash: 1: Illegal number: Y
$ echo "$(($X))"
1

Expected behavior

Just for completeness I here include what with no indirection and two layers of indirection. With no indirection everyting works as expected:

$ X=1
$ echo "$((X))"
1
$ echo "$(($X))"
1

As expected, two layers of indirection does not work (though notice that the error messages are referring to different values).

$ X=Y; Y=Z; Z=1
$ echo "$((X))"
dash: 6: Illegal number: Y
$ echo "$(($X))"
dash: 7: Illegal number: Z

This question is somewhat related to this question about Arithmetic expansion and parameter expansion but it’s not the same since that's dealing with the behaviour of bash and more advanced shells, but I'm wondering about expected POSIX behavior.

2
4

Since your question is POSIX-generic, and related questions appear to concern more advanced shells, I’ll answer this separately.

The behaviour you’re seeing comes from two different expansions, parameter expansion and arithmetic expansion, which POSIX specifies in that order. With

$ X=Y; Y=1

your first example only uses arithmetic expansion:

$ echo "$((X))"

tries to interpret X’s value as an arithmetic expression, and fails, because “Y” isn’t a number.

Your second example uses both:

$ echo "$(($X))"

is expanded (parameter expansion) to

$ echo "((Y))"

and arithmetic expansion then uses Y’s value, 1.

See also Understand two examples using indirect expansion for variable expansion in arithmetic expressions and Bash: Arithmetic expansion, parameter expansion, and the comma operator, among others.

2
  • Ah, so it comes down to the order of the different types of expansions! (But of course it does! I feel silly not to have thought of this before I asked my question.) I for some reason got it into my head that $ expansion would be a thing, but the expansions of $(...), $((...)) and variables actually occur in different steps.
    – zrajm
    May 27 '20 at 19:09
  • The first referene under "see also" is illgeal since neither 1+1 nor b++ are integer constants but free text. If you use $((var)), then var needs to contain an integer number. A shell that allows this is questionable and a script that uses it is non-conforming.
    – schily
    May 28 '20 at 7:02

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