4

I am working on a very simple bash script and I'm having a problem with understating why deprecated $[] is working flawlessly, while $(()) seems to break the whole thing.

The code I'm referring to is:

for i in {1..10};
do 
    printf %4d $[{1..10}*i]
    echo
done

In this version I am having no issues, yet I wouldn't like to use deprecated bash elements, that's why I wanted to switch to $(()).

Unfortunately, as soon as I change my code to:

printf %4d $(({1..10}*i))

I receive an error:

./script_bash.sh: line 8: {1..10}*i: syntax error: argument expected (error token is "{1..10}*i")

I'd be thankful for some help with this one...

phuclv
  • 37,963
  • 15
  • 156
  • 475
Jakub
  • 187
  • 1
  • 1
  • 9
  • `$(())` is an arithmetic expression. It doesn't do regular wildcard or brace expansion in there. – Barmar Oct 14 '16 at 01:40

4 Answers4

5

Setting the way back machine for 1990.

Bash implemented the $[] syntax per POSIX P1003.2d9 (circa 1990), which was a draft of the released P1003.2-1992. In the two years between draft and standard, POSIX had instead settled on the ksh88 $(()) syntax and behaviors. Chet Ramey (bash maintainer) had this to say, back in 2012:

Bash... implemented $[...] because there was no other syntax at the time, and to gain some operational experience with arithmetic expansion in the shell. Bash-1.14... lists both forms of arithmetic expansion, but by the time bash-2.0 was released in 1995, the manual referred only to the $((...)) form.

This suggests to me that the $[] form was experimental, and it had certain behaviors (like brace expansion) that were specified into oblivion when POSIX adopted the $(()) syntax. Those experimental behaviors were left in, since there were already scripts in the wild relying on them (remember more than 2 years had elapsed).

Chet makes clear in that same thread that the $[] form is obsolete, but not deprecated:

Now, it's hardly any problem to keep dragging the $[...] syntax along. It takes only a few dozen bytes of code. I have no plans to remove it.

The current POSIX standard, C.2.6 Word Expansions > Arithmetic Expansion mentions the syntax (emphasis mine):

In early proposals, a form $[expression] was used. It was functionally equivalent to the "$(())" of the current text, but objections were lodged that the 1988 KornShell had already implemented "$(())" and there was no compelling reason to invent yet another syntax. Furthermore, the "$[]" syntax had a minor incompatibility involving the patterns in case statements.

So the as-implemented behavior in bash isn't quite to specification, but since there are no plans to remove it, I see no reason to forgo its benefits if it neatly solves your problem. However, as pointed out by @Barmar's comment, it'd be A Good Idea to comment the code and link it here so future developers know what the heck you mean!

bishop
  • 37,830
  • 11
  • 104
  • 139
  • 1
    While it's not explicitly deprecated (is there actually a list of deprecated features?), the fact that it's not even mentioned in the manual is a strong indication to avoid it. Even if it never actually goes away, other programmers will not understand it. I've been programming shell scripts for 3 decades and I never saw this before, I'm sure I'm not alone. Compare with the other features that are on the bash-hackers page you linked to, they're still in the manual. – Barmar Oct 14 '16 at 18:31
  • @Barmar It's mentioned in the current POSIX specification, and [purportedly](https://lists.gnu.org/archive/html/bug-bash/2012-04/msg00033.html) some distros patch the bash man page to mention it. Nonetheless, I've elaborated on my usage "recommendation". – bishop Oct 14 '16 at 18:50
1

$(()) is for arithmetic expressions, and brace expansion isn't done in arithmetic.

Make an array with a loop:

for i in {1..10}
do
    vals=()
    for j in {1..10}
    do
        vals+=($((i*j)))
    done
    printf "%4d" ${vals[@]}
done
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    But, on the flip side, why is brace expansion done in `$[]`? – bishop Oct 14 '16 at 01:50
  • This is the first I've ever heard of `$[]`. I can't find it in the bash manual. – Barmar Oct 14 '16 at 02:06
  • It's a deprecated pseudo-equivalent of `$((`, see http://wiki.bash-hackers.org/scripting/obsolete – bishop Oct 14 '16 at 03:23
  • So, I guess *technically* your first sentence isn't true, since ostensibly `$[]` is also for "arithmetic expressions." – bishop Oct 14 '16 at 03:25
  • But I guess at the time they created it, they didn't consider arithmetic expansion to be a completely different context with its own syntax. – Barmar Oct 14 '16 at 16:04
  • Indeed. Did some digging and looks like `$[]` was a POSIX draft syntax before settling on the ksh `$(())` syntax. Additional details in my answer. – bishop Oct 14 '16 at 17:29
1

printf %4d $(({1..10}*i))

does not work because of the order in which parameters are expanded in bash. The brace expansion ({}) is done earlier than arithmetic expansion ($(())) by bash. Your code would definitely work if it were other way around.

From man bash:

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and pathname expansion.

heemayl
  • 39,294
  • 7
  • 70
  • 76
1

Ran across some bash like this. This question asks for explanation, rather than solution, but here would be a $(())-way of expressing this.

for i in {1..10}; do
  printf %4d $(eval echo '$(('{1..10}'*i))')
  echo
done

Brace expansion is inhibited inside an arithmetic expansion like it is for parameter expansion.

(bash manual)

To avoid conflicts with parameter expansion, the string ‘${’ is not considered eligible for brace expansion, and inhibits brace expansion until the closing ‘}’.

Brian Chrisman
  • 3,482
  • 1
  • 15
  • 16