1 #
2 # Interesting interpretation of constants.
3 #
4 # "Constants with a leading 0 are interpreted as octal numbers. A leading ‘0x’
5 # or ‘0X’ denotes hexadecimal. Otherwise, numbers take the form [base#]n, where
6 # the optional base is a decimal number between 2 and 64 representing the
7 # arithmetic base, and n is a number in that base. If base# is omitted, then
8 # base 10 is used. When specifying n, the digits greater than 9 are represented
9 # by the lowercase letters, the uppercase letters, ‘@’, and ‘_’, in that order.
10 # If base is less than or equal to 36, lowercase and uppercase letters may be
11 # used interchangeably to represent numbers between 10 and 35. "
12 #
13 # NOTE $(( 8#9 )) can fail, and this can be done at parse time...
14
15 #### Side Effect in Array Indexing
16 a=(4 5 6)
17 echo "${a[b=2]} b=$b"
18 ## stdout: 6 b=2
19 ## OK zsh stdout: 5 b=2
20 ## N-I dash stdout-json: ""
21 ## N-I dash status: 2
22
23 #### Add one to var
24 i=1
25 echo $(($i+1))
26 ## stdout: 2
27
28 #### $ is optional
29 i=1
30 echo $((i+1))
31 ## stdout: 2
32
33 #### SimpleVarSub within arith
34 j=0
35 echo $(($j + 42))
36 ## stdout: 42
37
38 #### BracedVarSub within ArithSub
39 echo $((${j:-5} + 1))
40 ## stdout: 6
41
42 #### Arith word part
43 foo=1; echo $((foo+1))bar$(($foo+1))
44 ## stdout: 2bar2
45
46 #### Arith sub with word parts
47 # Making 13 from two different kinds of sub. Geez.
48 echo $((1 + $(echo 1)${undefined:-3}))
49 ## stdout: 14
50
51 #### Constant with quotes like '1'
52 # NOTE: Compare with [[. That is a COMMAND level expression, while this is a
53 # WORD level expression.
54 echo $(('1' + 2))
55 ## status: 0
56 ## N-I bash/zsh status: 1
57 ## N-I dash status: 2
58
59 #### Arith sub within arith sub
60 # This is unnecessary but works in all shells.
61 echo $((1 + $((2 + 3)) + 4))
62 ## stdout: 10
63
64 #### Backticks within arith sub
65 # This is unnecessary but works in all shells.
66 echo $((`echo 1` + 2))
67 ## stdout: 3
68
69 #### Invalid string to int
70 # bash, mksh, and zsh all treat strings that don't look like numbers as zero.
71 shopt -u strict_arith || true
72 s=foo
73 echo $((s+5))
74 ## OK dash stdout-json: ""
75 ## OK dash status: 2
76 ## OK bash/mksh/zsh/osh stdout: 5
77 ## OK bash/mksh/zsh/osh status: 0
78
79 #### Invalid string to int with strict_arith
80 shopt -s strict_arith || true
81 s=foo
82 echo $s
83 echo $((s+5))
84 echo 'should not get here'
85 ## status: 1
86 ## STDOUT:
87 foo
88 ## END
89 ## OK dash status: 2
90 ## N-I bash/mksh/zsh STDOUT:
91 foo
92 5
93 should not get here
94 ## END
95 ## N-I bash/mksh/zsh status: 0
96
97 #### Newline in the middle of expression
98 echo $((1
99 + 2))
100 ## stdout: 3
101
102 #### Ternary operator
103 a=1
104 b=2
105 echo $((a>b?5:10))
106 ## stdout: 10
107
108 #### Preincrement
109 a=4
110 echo $((++a))
111 echo $a
112 ## stdout-json: "5\n5\n"
113 ## N-I dash status: 0
114 ## N-I dash stdout-json: "4\n4\n"
115
116 #### Postincrement
117 a=4
118 echo $((a++))
119 echo $a
120 ## stdout-json: "4\n5\n"
121 ## N-I dash status: 2
122 ## N-I dash stdout-json: ""
123
124 #### Increment undefined variables
125 shopt -u strict_arith || true
126 (( undef1++ ))
127 (( ++undef2 ))
128 echo "[$undef1][$undef2]"
129 ## stdout: [1][1]
130 ## N-I dash stdout: [][]
131
132 #### Increment and decrement array elements
133 shopt -u strict_arith || true
134 a=(5 6 7 8)
135 (( a[0]++, ++a[1], a[2]--, --a[3] ))
136 (( undef[0]++, ++undef[1], undef[2]--, --undef[3] ))
137 echo "${a[@]}" - "${undef[@]}"
138 ## stdout: 6 7 6 7 - 1 1 -1 -1
139 ## N-I dash stdout-json: ""
140 ## N-I dash status: 2
141 ## BUG zsh stdout: 5 6 7 8 -
142
143 #### Increment undefined variables with nounset
144 set -o nounset
145 (( undef1++ ))
146 (( ++undef2 ))
147 echo "[$undef1][$undef2]"
148 ## stdout-json: ""
149 ## status: 1
150 ## OK dash status: 2
151 ## BUG mksh/zsh status: 0
152 ## BUG mksh/zsh stdout-json: "[1][1]\n"
153
154 #### Comma operator (borrowed from C)
155 a=1
156 b=2
157 echo $((a,(b+1)))
158 ## stdout: 3
159 ## N-I dash status: 2
160 ## N-I dash stdout-json: ""
161
162 #### Augmented assignment
163 a=4
164 echo $((a+=1))
165 echo $a
166 ## stdout-json: "5\n5\n"
167
168 #### Comparison Ops
169 echo $(( 1 == 1 ))
170 echo $(( 1 != 1 ))
171 echo $(( 1 < 1 ))
172 echo $(( 1 <= 1 ))
173 echo $(( 1 > 1 ))
174 echo $(( 1 >= 1 ))
175 ## stdout-json: "1\n0\n0\n1\n0\n1\n"
176
177 #### Logical Ops
178 echo $((1 || 2))
179 echo $((1 && 2))
180 echo $((!(1 || 2)))
181 ## stdout-json: "1\n1\n0\n"
182
183 #### Logical Ops Short Circuit
184 x=11
185 (( 1 || (x = 22) ))
186 echo $x
187 (( 0 || (x = 33) ))
188 echo $x
189 (( 0 && (x = 44) ))
190 echo $x
191 (( 1 && (x = 55) ))
192 echo $x
193 ## stdout-json: "11\n33\n33\n55\n"
194 ## N-I dash stdout-json: "11\n11\n11\n11\n"
195
196 #### Bitwise ops
197 echo $((1|2))
198 echo $((1&2))
199 echo $((1^2))
200 echo $((~(1|2)))
201 ## stdout-json: "3\n0\n3\n-4\n"
202
203 #### Unary minus and plus
204 a=1
205 b=3
206 echo $((- a + + b))
207 ## stdout-json: "2\n"
208
209 #### No floating point
210 echo $((1 + 2.3))
211 ## status: 2
212 ## OK bash/mksh status: 1
213 ## BUG zsh status: 0
214
215 #### Array indexing in arith
216 # zsh does 1-based indexing!
217 array=(1 2 3 4)
218 echo $((array[1] + array[2]*3))
219 ## stdout: 11
220 ## OK zsh stdout: 7
221 ## N-I dash status: 2
222 ## N-I dash stdout-json: ""
223
224 #### Constants in base 36
225 echo $((36#a))-$((36#z))
226 ## stdout: 10-35
227 ## N-I dash stdout-json: ""
228 ## N-I dash status: 2
229
230 #### Constants in bases 2 to 64
231 # This is a truly bizarre syntax. Oh it comes from zsh... which allows 36.
232 echo $((64#a))-$((64#z)), $((64#A))-$((64#Z)), $((64#@)), $(( 64#_ ))
233 ## stdout: 10-35, 36-61, 62, 63
234 ## N-I dash stdout-json: ""
235 ## N-I dash status: 2
236 ## N-I mksh/zsh stdout-json: ""
237 ## N-I mksh/zsh status: 1
238
239 #### Multiple digit constants with base N
240 echo $((10#0123)), $((16#1b))
241 ## stdout: 123, 27
242 ## N-I dash stdout-json: ""
243 ## N-I dash status: 2
244
245 #### Dynamic base constants
246 base=16
247 echo $(( ${base}#a ))
248 ## stdout: 10
249 ## N-I dash stdout-json: ""
250 ## N-I dash status: 2
251
252 #### Octal constant
253 echo $(( 011 ))
254 ## stdout: 9
255 ## N-I mksh/zsh stdout: 11
256
257 #### Dynamic octal constant
258 zero=0
259 echo $(( ${zero}11 ))
260 ## stdout: 9
261 ## N-I mksh/zsh stdout: 11
262
263 #### Dynamic hex constants
264 zero=0
265 echo $(( ${zero}xAB ))
266 ## stdout: 171
267
268 #### Dynamic var names - result of runtime parse/eval
269 foo=5
270 x=oo
271 echo $(( foo + f$x + 1 ))
272 ## stdout: 11
273 ## OK osh stdout: 6
274
275 #### Bizarre recursive name evaluation - result of runtime parse/eval
276 foo=5
277 bar=foo
278 spam=bar
279 eggs=spam
280 echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
281 ## stdout: 6 6 6 6
282 ## OK osh stdout: 6 1 1 1
283 ## N-I dash stdout-json: ""
284 ## N-I dash status: 2
285
286 #### nounset with arithmetic
287 set -o nounset
288 x=$(( y + 5 ))
289 echo "should not get here: x=${x:-<unset>}"
290 ## stdout-json: ""
291 ## status: 1
292 ## BUG dash/mksh/zsh stdout: should not get here: x=5
293 ## BUG dash/mksh/zsh status: 0
294
295 #### Integer Overflow
296 set -o nounset
297 echo $(( 999999 * 999999 * 999999 * 999999 ))
298 ## stdout: 999996000005999996000001
299 ## BUG dash/bash/zsh stdout: -1996229794797103359
300 ## BUG mksh stdout: -15640831
301
302 #### Invalid LValue
303 a=9
304 (( (a + 2) = 3 ))
305 echo $a
306 ## status: 2
307 ## stdout-json: ""
308 ## OK bash/mksh/zsh stdout: 9
309 ## OK bash/mksh/zsh status: 0
310 # dash doesn't implement assignment
311 ## N-I dash status: 2
312 ## N-I dash stdout-json: ""
313
314 #### Invalid LValue that looks like array
315 (( 1[2] = 3 ))
316 echo "status=$?"
317 ## status: 2
318 ## stdout-json: ""
319 ## OK bash stdout: status=1
320 ## OK bash status: 0
321 ## OK mksh/zsh stdout: status=2
322 ## OK mksh/zsh status: 0
323 ## N-I dash stdout: status=127
324 ## N-I dash status: 0
325
326 #### Invalid LValue: two sets of brackets
327 (( a[1][2] = 3 ))
328 echo "status=$?"
329 # shells treat this as a NON-fatal error
330 ## status: 2
331 ## stdout-json: ""
332 ## OK bash stdout: status=1
333 ## OK mksh/zsh stdout: status=2
334 ## OK bash/mksh/zsh status: 0
335 # dash doesn't implement assignment
336 ## N-I dash stdout: status=127
337 ## N-I dash status: 0
338
339 #### Operator Precedence
340 echo $(( 1 + 2*3 - 8/2 ))
341 ## stdout: 3
342
343 #### Exponentiation with **
344 echo $(( 3 ** 0 ))
345 echo $(( 3 ** 1 ))
346 echo $(( 3 ** 2 ))
347 ## STDOUT:
348 1
349 3
350 9
351 ## END
352 ## N-I dash stdout-json: ""
353 ## N-I dash status: 2
354 ## N-I mksh stdout-json: ""
355 ## N-I mksh status: 1
356
357 #### Exponentiation operator has buggy precedence
358 # NOTE: All shells agree on this, but R and Python give -9, which is more
359 # mathematically correct.
360 echo $(( -3 ** 2 ))
361 ## osh stdout: 9
362 ## N-I dash stdout-json: ""
363 ## N-I dash status: 2
364 ## N-I mksh stdout-json: ""
365 ## N-I mksh status: 1
366
367 #### Negative exponent
368 # bash explicitly disallows negative exponents!
369 echo $(( 2**-1 * 5 ))
370 ## stdout-json: ""
371 ## status: 1
372 ## OK zsh stdout: 2.5
373 ## OK zsh status: 0
374 ## N-I dash stdout-json: ""
375 ## N-I dash status: 2
376
377 #### Comment not allowed in the middle of multiline arithmetic
378 echo $((
379 1 +
380 2 + \
381 3
382 ))
383 echo $((
384 1 + 2 # not a comment
385 ))
386 (( a = 3 + 4 # comment
387 ))
388 echo [$a]
389 ## status: 1
390 ## STDOUT:
391 6
392 ## END
393 ## OK dash/osh status: 2
394 ## OK bash STDOUT:
395 6
396 []
397 ## END
398 ## OK bash status: 0
399
400 #### Can't add integer to indexed array
401 declare -a array=(1 2 3)
402 echo $((array + 5))
403 ## status: 1
404 ## stdout-json: ""
405 ## BUG bash status: 0
406 ## BUG bash STDOUT:
407 6
408 ## END
409 ## N-I dash status: 2
410
411 #### Can't add integer to associative array
412 typeset -A assoc
413 assoc[0]=42
414 echo $((assoc + 5))
415 ## status: 1
416 ## stdout-json: ""
417 ## BUG bash/mksh/zsh status: 0
418 ## BUG bash/mksh/zsh stdout: 47
419 ## BUG dash status: 0
420 ## BUG dash stdout: 5
421
422 #### Double subscript
423 a=(1 2 3)
424 echo $(( a[1] ))
425 echo $(( a[1][1] ))
426 ## status: 1
427 ## OK osh status: 2
428 ## STDOUT:
429 2
430 ## END
431 ## N-I dash status: 2
432 ## N-I dash stdout-json: ""
433 ## OK zsh STDOUT:
434 1
435 ## END
436
437 #### result of ArithSub is array
438 a=(4 5 6)
439 echo declared
440 b=$(( a ))
441 echo $b
442 ## status: 1
443 ## STDOUT:
444 declared
445 ## END
446 ## BUG bash/mksh status: 0
447 ## BUG bash/mksh STDOUT:
448 declared
449 4
450 ## END
451 ## N-I dash status: 2
452 ## N-I dash stdout-json: ""
453
454 #### result of ArithSub is assoc array
455 declare -A A=(['foo']=bar ['spam']=eggs)
456 echo declared
457 b=$(( A ))
458 echo $b
459 ## status: 1
460 ## STDOUT:
461 declared
462 ## END
463 ## N-I mksh stdout-json: ""
464 ## BUG bash/zsh status: 0
465 ## BUG bash/zsh STDOUT:
466 declared
467 0
468 ## END
469 ## N-I dash status: 2
470 ## N-I dash stdout-json: ""
471
472 #### comma operator
473 a=(4 5 6)
474
475 # zsh and osh can't evaluate the array like that
476 # which is consistent with their behavior on $(( a ))
477
478 echo $(( a, last = a[2], 42 ))
479 echo last=$last
480
481 ## status: 1
482 ## stdout-json: ""
483
484 ## N-I dash status: 2
485
486 ## OK bash/mksh status: 0
487 ## OK bash/mksh STDOUT:
488 42
489 last=6
490 ## END
491
492 #### assignment with dynamic var name
493 shopt -s parse_dynamic_arith
494 foo=bar
495 echo $(( x$foo = 42 ))
496 echo xbar=$xbar
497 ## STDOUT:
498 42
499 xbar=42
500 ## END
501
502 #### array assignment with dynamic array name
503 shopt -s parse_dynamic_arith
504 foo=bar
505 echo $(( x$foo[5] = 42 ))
506 echo 'xbar[5]='${xbar[5]}
507 ## STDOUT:
508 42
509 xbar[5]=42
510 ## END
511 ## BUG zsh STDOUT:
512 42
513 xbar[5]=
514 ## END
515 ## N-I dash status: 2
516 ## N-I dash stdout-json: ""
517
518 #### unary assignment with dynamic var name
519 shopt -s parse_dynamic_arith
520 foo=bar
521 xbar=42
522 echo $(( x$foo++ ))
523 echo xbar=$xbar
524 ## STDOUT:
525 42
526 xbar=43
527 ## END
528 ## BUG dash status: 2
529 ## BUG dash stdout-json: ""
530
531 #### unary array assignment with dynamic var name
532 shopt -s parse_dynamic_arith
533 foo=bar
534 xbar[5]=42
535 echo $(( x$foo[5]++ ))
536 echo 'xbar[5]='${xbar[5]}
537 ## STDOUT:
538 42
539 xbar[5]=43
540 ## END
541 ## BUG zsh STDOUT:
542 0
543 xbar[5]=42
544 ## END
545 ## N-I dash status: 2
546 ## N-I dash stdout-json: ""
547
548 #### shopt -s eval_unsafe_arith
549 shopt -s eval_unsafe_arith
550 e=1+2
551 echo $(( e + 3 ))
552 [[ e -eq 3 ]] && echo true
553 [ e -eq 3 ]
554 echo status=$?
555 ## STDOUT:
556 6
557 true
558 status=2
559 ## END
560 ## BUG mksh STDOUT:
561 6
562 true
563 status=0
564 ## END
565 ## N-I dash status: 2
566 ## N-I dash stdout-json: ""
567
568 #### eval_unsafe_arith on empty string
569 shopt -s eval_unsafe_arith
570 a=''
571 echo $(( a ))
572
573 a2=' '
574 echo $(( a2 ))
575 ## STDOUT:
576 0
577 0
578 ## END
579
580 #### nested ternary (bug fix)
581 echo $((1?2?3:4:5))
582 ## STDOUT:
583 3
584 ## END
585
586 #### 1 ? a=1 : b=2 ( bug fix)
587 echo $((1 ? a=1 : 42 ))
588 echo a=$a
589
590 # this does NOT work
591 #echo $((1 ? a=1 : b=2 ))
592
593 ## STDOUT:
594 1
595 a=1
596 ## END
597 ## BUG zsh stdout-json: ""
598 ## BUG zsh status: 1
599
600 #### Invalid constant
601
602 echo $((a + x42))
603 echo status=$?
604
605 # weird asymmetry -- the above is a syntax error, but this isn't
606 $SH -c 'echo $((a + 42x))'
607 echo status=$?
608
609 # regression
610 shopt -s eval_unsafe_arith
611 echo $((a + 42x))
612 echo status=$?
613 ## status: 1
614 ## STDOUT:
615 0
616 status=0
617 status=1
618 ## END
619 ## OK dash status: 2
620 ## OK dash STDOUT:
621 0
622 status=0
623 status=2
624 ## END
625 ## BUG bash status: 0
626 ## BUG bash STDOUT:
627 0
628 status=0
629 status=1
630 status=1
631 ## END