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
274 #### Recursive name evaluation is a result of runtime parse/eval
275 foo=5
276 bar=foo
277 spam=bar
278 eggs=spam
279 echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
280 ## stdout: 6 6 6 6
281 ## N-I dash stdout-json: ""
282 ## N-I dash status: 2
283
284 #### nounset with arithmetic
285 set -o nounset
286 x=$(( y + 5 ))
287 echo "should not get here: x=${x:-<unset>}"
288 ## stdout-json: ""
289 ## status: 1
290 ## BUG dash/mksh/zsh stdout: should not get here: x=5
291 ## BUG dash/mksh/zsh status: 0
292
293 #### Integer Overflow
294 set -o nounset
295 echo $(( 999999 * 999999 * 999999 * 999999 ))
296 ## stdout: 999996000005999996000001
297 ## BUG dash/bash/zsh stdout: -1996229794797103359
298 ## BUG mksh stdout: -15640831
299
300 #### Invalid LValue
301 a=9
302 (( (a + 2) = 3 ))
303 echo $a
304 ## status: 2
305 ## stdout-json: ""
306 ## OK bash/mksh/zsh stdout: 9
307 ## OK bash/mksh/zsh status: 0
308 # dash doesn't implement assignment
309 ## N-I dash status: 2
310 ## N-I dash stdout-json: ""
311
312 #### Invalid LValue that looks like array
313 (( 1[2] = 3 ))
314 echo "status=$?"
315 ## status: 1
316 ## stdout-json: ""
317
318 ## OK bash stdout: status=1
319 ## OK bash status: 0
320
321 ## OK mksh/zsh stdout: status=2
322 ## OK mksh/zsh status: 0
323
324 ## N-I dash stdout: status=127
325 ## N-I dash status: 0
326
327 #### Invalid LValue: two sets of brackets
328 (( a[1][2] = 3 ))
329 echo "status=$?"
330 # shells treat this as a NON-fatal error
331 ## status: 2
332 ## stdout-json: ""
333 ## OK bash stdout: status=1
334 ## OK mksh/zsh stdout: status=2
335 ## OK bash/mksh/zsh status: 0
336 # dash doesn't implement assignment
337 ## N-I dash stdout: status=127
338 ## N-I dash status: 0
339
340 #### Operator Precedence
341 echo $(( 1 + 2*3 - 8/2 ))
342 ## stdout: 3
343
344 #### Exponentiation with **
345 echo $(( 3 ** 0 ))
346 echo $(( 3 ** 1 ))
347 echo $(( 3 ** 2 ))
348 ## STDOUT:
349 1
350 3
351 9
352 ## END
353 ## N-I dash stdout-json: ""
354 ## N-I dash status: 2
355 ## N-I mksh stdout-json: ""
356 ## N-I mksh status: 1
357
358 #### Exponentiation operator has buggy precedence
359 # NOTE: All shells agree on this, but R and Python give -9, which is more
360 # mathematically correct.
361 echo $(( -3 ** 2 ))
362 ## stdout: 9
363 ## N-I dash stdout-json: ""
364 ## N-I dash status: 2
365 ## N-I mksh stdout-json: ""
366 ## N-I mksh status: 1
367
368 #### Negative exponent
369 # bash explicitly disallows negative exponents!
370 echo $(( 2**-1 * 5 ))
371 ## stdout-json: ""
372 ## status: 1
373 ## OK zsh stdout: 2.5
374 ## OK zsh status: 0
375 ## N-I dash stdout-json: ""
376 ## N-I dash status: 2
377
378 #### Comment not allowed in the middle of multiline arithmetic
379 echo $((
380 1 +
381 2 + \
382 3
383 ))
384 echo $((
385 1 + 2 # not a comment
386 ))
387 (( a = 3 + 4 # comment
388 ))
389 echo [$a]
390 ## status: 1
391 ## STDOUT:
392 6
393 ## END
394 ## OK dash/osh status: 2
395 ## OK bash STDOUT:
396 6
397 []
398 ## END
399 ## OK bash status: 0
400
401 #### Add integer to indexed array (a[0] decay)
402 declare -a array=(1 2 3)
403 echo $((array + 5))
404 ## status: 0
405 ## STDOUT:
406 6
407 ## END
408 ## N-I dash status: 2
409 ## N-I dash stdout-json: ""
410 ## N-I mksh/zsh status: 1
411 ## N-I mksh/zsh stdout-json: ""
412
413 #### Add integer to associative array (a[0] decay)
414 typeset -A assoc
415 assoc[0]=42
416 echo $((assoc + 5))
417 ## status: 0
418 ## 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 -- array[0] decay
438 a=(4 5 6)
439 echo declared
440 b=$(( a ))
441 echo $b
442
443 ## status: 0
444 ## STDOUT:
445 declared
446 4
447 ## END
448 ## N-I dash status: 2
449 ## N-I dash stdout-json: ""
450 ## N-I zsh status: 1
451 ## N-I zsh STDOUT:
452 declared
453 ## END
454
455 #### result of ArithSub -- assoc[0] decay
456 declare -A A=(['foo']=bar ['spam']=eggs)
457 echo declared
458 b=$(( A ))
459 echo $b
460
461 ## status: 0
462 ## STDOUT:
463 declared
464 0
465 ## END
466
467 ## N-I mksh status: 1
468 ## N-I mksh stdout-json: ""
469
470
471 ## N-I dash status: 2
472 ## N-I dash stdout-json: ""
473
474 #### comma operator
475 a=(4 5 6)
476
477 # zsh and osh can't evaluate the array like that
478 # which is consistent with their behavior on $(( a ))
479
480 echo $(( a, last = a[2], 42 ))
481 echo last=$last
482
483 ## status: 0
484 ## STDOUT:
485 42
486 last=6
487 ## END
488 ## N-I dash status: 2
489 ## N-I dash stdout-json: ""
490 ## N-I zsh status: 1
491 ## N-I zsh stdout-json: ""
492
493
494 #### assignment with dynamic var name
495 foo=bar
496 echo $(( x$foo = 42 ))
497 echo xbar=$xbar
498 ## STDOUT:
499 42
500 xbar=42
501 ## END
502
503 #### array assignment with dynamic array name
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 foo=bar
520 xbar=42
521 echo $(( x$foo++ ))
522 echo xbar=$xbar
523 ## STDOUT:
524 42
525 xbar=43
526 ## END
527 ## BUG dash status: 2
528 ## BUG dash stdout-json: ""
529
530 #### unary array assignment with dynamic var name
531 foo=bar
532 xbar[5]=42
533 echo $(( x$foo[5]++ ))
534 echo 'xbar[5]='${xbar[5]}
535 ## STDOUT:
536 42
537 xbar[5]=43
538 ## END
539 ## BUG zsh STDOUT:
540 0
541 xbar[5]=42
542 ## END
543 ## N-I dash status: 2
544 ## N-I dash stdout-json: ""
545
546 #### Dynamic parsing of arithmetic
547 e=1+2
548 echo $(( e + 3 ))
549 [[ e -eq 3 ]] && echo true
550 [ e -eq 3 ]
551 echo status=$?
552 ## STDOUT:
553 6
554 true
555 status=2
556 ## END
557 ## BUG mksh STDOUT:
558 6
559 true
560 status=0
561 ## END
562 ## N-I dash status: 2
563 ## N-I dash stdout-json: ""
564
565 #### Dynamic parsing on empty string
566 a=''
567 echo $(( a ))
568
569 a2=' '
570 echo $(( a2 ))
571 ## STDOUT:
572 0
573 0
574 ## END
575
576 #### nested ternary (bug fix)
577 echo $((1?2?3:4:5))
578 ## STDOUT:
579 3
580 ## END
581
582 #### 1 ? a=1 : b=2 ( bug fix)
583 echo $((1 ? a=1 : 42 ))
584 echo a=$a
585
586 # this does NOT work
587 #echo $((1 ? a=1 : b=2 ))
588
589 ## STDOUT:
590 1
591 a=1
592 ## END
593 ## BUG zsh stdout-json: ""
594 ## BUG zsh status: 1
595
596 #### Invalid constant
597
598 echo $((a + x42))
599 echo status=$?
600
601 # weird asymmetry -- the above is a syntax error, but this isn't
602 $SH -c 'echo $((a + 42x))'
603 echo status=$?
604
605 # regression
606 echo $((a + 42x))
607 echo status=$?
608 ## status: 1
609 ## STDOUT:
610 0
611 status=0
612 status=1
613 ## END
614 ## OK dash status: 2
615 ## OK dash STDOUT:
616 0
617 status=0
618 status=2
619 ## END
620 ## BUG bash status: 0
621 ## BUG bash STDOUT:
622 0
623 status=0
624 status=1
625 status=1
626 ## END