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