1 ## compare_shells: bash dash mksh zsh
2
3
4 # Interesting interpretation of constants.
5 #
6 # "Constants with a leading 0 are interpreted as octal numbers. A leading ‘0x’
7 # or ‘0X’ denotes hexadecimal. Otherwise, numbers take the form [base#]n, where
8 # the optional base is a decimal number between 2 and 64 representing the
9 # arithmetic base, and n is a number in that base. If base# is omitted, then
10 # base 10 is used. When specifying n, the digits greater than 9 are represented
11 # by the lowercase letters, the uppercase letters, ‘@’, and ‘_’, in that order.
12 # If base is less than or equal to 36, lowercase and uppercase letters may be
13 # used interchangeably to represent numbers between 10 and 35. "
14 #
15 # NOTE $(( 8#9 )) can fail, and this can be done at parse time...
16
17 #### Side Effect in Array Indexing
18 a=(4 5 6)
19 echo "${a[b=2]} b=$b"
20 ## stdout: 6 b=2
21 ## OK zsh stdout: 5 b=2
22 ## N-I dash stdout-json: ""
23 ## N-I dash status: 2
24
25 #### Add one to var
26 i=1
27 echo $(($i+1))
28 ## stdout: 2
29
30 #### $ is optional
31 i=1
32 echo $((i+1))
33 ## stdout: 2
34
35 #### SimpleVarSub within arith
36 j=0
37 echo $(($j + 42))
38 ## stdout: 42
39
40 #### BracedVarSub within ArithSub
41 echo $((${j:-5} + 1))
42 ## stdout: 6
43
44 #### Arith word part
45 foo=1; echo $((foo+1))bar$(($foo+1))
46 ## stdout: 2bar2
47
48 #### Arith sub with word parts
49 # Making 13 from two different kinds of sub. Geez.
50 echo $((1 + $(echo 1)${undefined:-3}))
51 ## stdout: 14
52
53 #### Constant with quotes like '1'
54 # NOTE: Compare with [[. That is a COMMAND level expression, while this is a
55 # WORD level expression.
56 echo $(('1' + 2))
57 ## status: 0
58 ## N-I bash/zsh status: 1
59 ## N-I dash status: 2
60
61 #### Arith sub within arith sub
62 # This is unnecessary but works in all shells.
63 echo $((1 + $((2 + 3)) + 4))
64 ## stdout: 10
65
66 #### Backticks within arith sub
67 # This is unnecessary but works in all shells.
68 echo $((`echo 1` + 2))
69 ## stdout: 3
70
71 #### Invalid string to int
72 # bash, mksh, and zsh all treat strings that don't look like numbers as zero.
73 shopt -u strict_arith || true
74 s=foo
75 echo $((s+5))
76 ## OK dash stdout-json: ""
77 ## OK dash status: 2
78 ## OK bash/mksh/zsh/osh stdout: 5
79 ## OK bash/mksh/zsh/osh status: 0
80
81 #### Invalid string to int with strict_arith
82 shopt -s strict_arith || true
83 s=foo
84 echo $s
85 echo $((s+5))
86 echo 'should not get here'
87 ## status: 1
88 ## STDOUT:
89 foo
90 ## END
91 ## OK dash status: 2
92 ## N-I bash/mksh/zsh STDOUT:
93 foo
94 5
95 should not get here
96 ## END
97 ## N-I bash/mksh/zsh status: 0
98
99 #### Newline in the middle of expression
100 echo $((1
101 + 2))
102 ## stdout: 3
103
104 #### Ternary operator
105 a=1
106 b=2
107 echo $((a>b?5:10))
108 ## stdout: 10
109
110 #### Preincrement
111 a=4
112 echo $((++a))
113 echo $a
114 ## stdout-json: "5\n5\n"
115 ## N-I dash status: 0
116 ## N-I dash stdout-json: "4\n4\n"
117
118 #### Postincrement
119 a=4
120 echo $((a++))
121 echo $a
122 ## stdout-json: "4\n5\n"
123 ## N-I dash status: 2
124 ## N-I dash stdout-json: ""
125
126 #### Increment undefined variables
127 shopt -u strict_arith || true
128 (( undef1++ ))
129 (( ++undef2 ))
130 echo "[$undef1][$undef2]"
131 ## stdout: [1][1]
132 ## N-I dash stdout: [][]
133
134 #### Increment and decrement array elements
135 shopt -u strict_arith || true
136 a=(5 6 7 8)
137 (( a[0]++, ++a[1], a[2]--, --a[3] ))
138 (( undef[0]++, ++undef[1], undef[2]--, --undef[3] ))
139 echo "${a[@]}" - "${undef[@]}"
140 ## stdout: 6 7 6 7 - 1 1 -1 -1
141 ## N-I dash stdout-json: ""
142 ## N-I dash status: 2
143 ## BUG zsh stdout: 5 6 7 8 -
144
145 #### Increment undefined variables with nounset
146 set -o nounset
147 (( undef1++ ))
148 (( ++undef2 ))
149 echo "[$undef1][$undef2]"
150 ## stdout-json: ""
151 ## status: 1
152 ## OK dash status: 2
153 ## BUG mksh/zsh status: 0
154 ## BUG mksh/zsh stdout-json: "[1][1]\n"
155
156 #### Comma operator (borrowed from C)
157 a=1
158 b=2
159 echo $((a,(b+1)))
160 ## stdout: 3
161 ## N-I dash status: 2
162 ## N-I dash stdout-json: ""
163
164 #### Augmented assignment
165 a=4
166 echo $((a+=1))
167 echo $a
168 ## stdout-json: "5\n5\n"
169
170 #### Comparison Ops
171 echo $(( 1 == 1 ))
172 echo $(( 1 != 1 ))
173 echo $(( 1 < 1 ))
174 echo $(( 1 <= 1 ))
175 echo $(( 1 > 1 ))
176 echo $(( 1 >= 1 ))
177 ## stdout-json: "1\n0\n0\n1\n0\n1\n"
178
179 #### Logical Ops
180 echo $((1 || 2))
181 echo $((1 && 2))
182 echo $((!(1 || 2)))
183 ## stdout-json: "1\n1\n0\n"
184
185 #### Logical Ops Short Circuit
186 x=11
187 (( 1 || (x = 22) ))
188 echo $x
189 (( 0 || (x = 33) ))
190 echo $x
191 (( 0 && (x = 44) ))
192 echo $x
193 (( 1 && (x = 55) ))
194 echo $x
195 ## stdout-json: "11\n33\n33\n55\n"
196 ## N-I dash stdout-json: "11\n11\n11\n11\n"
197
198 #### Bitwise ops
199 echo $((1|2))
200 echo $((1&2))
201 echo $((1^2))
202 echo $((~(1|2)))
203 ## stdout-json: "3\n0\n3\n-4\n"
204
205 #### Unary minus and plus
206 a=1
207 b=3
208 echo $((- a + + b))
209 ## stdout-json: "2\n"
210
211 #### No floating point
212 echo $((1 + 2.3))
213 ## status: 2
214 ## OK bash/mksh status: 1
215 ## BUG zsh status: 0
216
217 #### Array indexing in arith
218 # zsh does 1-based indexing!
219 array=(1 2 3 4)
220 echo $((array[1] + array[2]*3))
221 ## stdout: 11
222 ## OK zsh stdout: 7
223 ## N-I dash status: 2
224 ## N-I dash stdout-json: ""
225
226 #### Constants in base 36
227 echo $((36#a))-$((36#z))
228 ## stdout: 10-35
229 ## N-I dash stdout-json: ""
230 ## N-I dash status: 2
231
232 #### Constants in bases 2 to 64
233 # This is a truly bizarre syntax. Oh it comes from zsh... which allows 36.
234 echo $((64#a))-$((64#z)), $((64#A))-$((64#Z)), $((64#@)), $(( 64#_ ))
235 ## stdout: 10-35, 36-61, 62, 63
236 ## N-I dash stdout-json: ""
237 ## N-I dash status: 2
238 ## N-I mksh/zsh stdout-json: ""
239 ## N-I mksh/zsh status: 1
240
241 #### Multiple digit constants with base N
242 echo $((10#0123)), $((16#1b))
243 ## stdout: 123, 27
244 ## N-I dash stdout-json: ""
245 ## N-I dash status: 2
246
247 #### Dynamic base constants
248 base=16
249 echo $(( ${base}#a ))
250 ## stdout: 10
251 ## N-I dash stdout-json: ""
252 ## N-I dash status: 2
253
254 #### Octal constant
255 echo $(( 011 ))
256 ## stdout: 9
257 ## N-I mksh/zsh stdout: 11
258
259 #### Dynamic octal constant
260 zero=0
261 echo $(( ${zero}11 ))
262 ## stdout: 9
263 ## N-I mksh/zsh stdout: 11
264
265 #### Dynamic hex constants
266 zero=0
267 echo $(( ${zero}xAB ))
268 ## stdout: 171
269
270 #### Dynamic var names - result of runtime parse/eval
271 foo=5
272 x=oo
273 echo $(( foo + f$x + 1 ))
274 ## stdout: 11
275
276 #### Recursive name evaluation is a 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 ## 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 #### 64-bit integer doesn't overflow
296
297 a=$(( 1 << 31 ))
298 echo $a
299
300 b=$(( a + a ))
301 echo $b
302
303 c=$(( b + a ))
304 echo $c
305
306 x=$(( 1 << 62 ))
307 y=$(( x - 1 ))
308 echo "max positive = $(( x + y ))"
309
310 #echo "overflow $(( x + x ))"
311
312 ## STDOUT:
313 2147483648
314 4294967296
315 6442450944
316 max positive = 9223372036854775807
317 ## END
318
319 # mksh still uses int!
320 ## BUG mksh STDOUT:
321 -2147483648
322 0
323 -2147483648
324 max positive = 2147483647
325 ## END
326
327 #### More 64-bit ops
328 case $SH in dash) exit ;; esac
329
330 #shopt -s strict_arith
331
332 # This overflows - the extra 9 puts it above 2**31
333 #echo $(( 12345678909 ))
334
335 [[ 12345678909 = $(( 1 << 30 )) ]]
336 echo eq=$?
337 [[ 12345678909 = 12345678909 ]]
338 echo eq=$?
339
340 # Try both [ and [[
341 [ 12345678909 -gt $(( 1 << 30 )) ]
342 echo greater=$?
343 [[ 12345678909 -gt $(( 1 << 30 )) ]]
344 echo greater=$?
345
346 [[ 12345678909 -ge $(( 1 << 30 )) ]]
347 echo ge=$?
348 [[ 12345678909 -ge 12345678909 ]]
349 echo ge=$?
350
351 [[ 12345678909 -le $(( 1 << 30 )) ]]
352 echo le=$?
353 [[ 12345678909 -le 12345678909 ]]
354 echo le=$?
355
356 ## STDOUT:
357 eq=1
358 eq=0
359 greater=0
360 greater=0
361 ge=0
362 ge=0
363 le=1
364 le=0
365 ## END
366 ## N-I dash STDOUT:
367 ## END
368 ## BUG mksh STDOUT:
369 eq=1
370 eq=0
371 greater=1
372 greater=1
373 ge=1
374 ge=0
375 le=0
376 le=0
377 ## END
378
379 # mksh still uses int!
380
381 #### Invalid LValue
382 a=9
383 (( (a + 2) = 3 ))
384 echo $a
385 ## status: 2
386 ## stdout-json: ""
387 ## OK bash/mksh/zsh stdout: 9
388 ## OK bash/mksh/zsh status: 0
389 # dash doesn't implement assignment
390 ## N-I dash status: 2
391 ## N-I dash stdout-json: ""
392
393 #### Invalid LValue that looks like array
394 (( 1[2] = 3 ))
395 echo "status=$?"
396 ## status: 1
397 ## stdout-json: ""
398
399 ## OK bash stdout: status=1
400 ## OK bash status: 0
401
402 ## OK mksh/zsh stdout: status=2
403 ## OK mksh/zsh status: 0
404
405 ## N-I dash stdout: status=127
406 ## N-I dash status: 0
407
408 #### Invalid LValue: two sets of brackets
409 (( a[1][2] = 3 ))
410 echo "status=$?"
411 # shells treat this as a NON-fatal error
412 ## status: 2
413 ## stdout-json: ""
414 ## OK bash stdout: status=1
415 ## OK mksh/zsh stdout: status=2
416 ## OK bash/mksh/zsh status: 0
417 # dash doesn't implement assignment
418 ## N-I dash stdout: status=127
419 ## N-I dash status: 0
420
421 #### Operator Precedence
422 echo $(( 1 + 2*3 - 8/2 ))
423 ## stdout: 3
424
425 #### Exponentiation with **
426 echo $(( 3 ** 0 ))
427 echo $(( 3 ** 1 ))
428 echo $(( 3 ** 2 ))
429 ## STDOUT:
430 1
431 3
432 9
433 ## END
434 ## N-I dash stdout-json: ""
435 ## N-I dash status: 2
436 ## N-I mksh stdout-json: ""
437 ## N-I mksh status: 1
438
439 #### Exponentiation operator has buggy precedence
440 # NOTE: All shells agree on this, but R and Python give -9, which is more
441 # mathematically correct.
442 echo $(( -3 ** 2 ))
443 ## stdout: 9
444 ## N-I dash stdout-json: ""
445 ## N-I dash status: 2
446 ## N-I mksh stdout-json: ""
447 ## N-I mksh status: 1
448
449 #### Negative exponent
450 # bash explicitly disallows negative exponents!
451 echo $(( 2**-1 * 5 ))
452 ## stdout-json: ""
453 ## status: 1
454 ## OK zsh stdout: 2.5
455 ## OK zsh status: 0
456 ## N-I dash stdout-json: ""
457 ## N-I dash status: 2
458
459 #### Comment not allowed in the middle of multiline arithmetic
460 echo $((
461 1 +
462 2 + \
463 3
464 ))
465 echo $((
466 1 + 2 # not a comment
467 ))
468 (( a = 3 + 4 # comment
469 ))
470 echo [$a]
471 ## status: 1
472 ## STDOUT:
473 6
474 ## END
475 ## OK dash/osh status: 2
476 ## OK bash STDOUT:
477 6
478 []
479 ## END
480 ## OK bash status: 0
481
482 #### Add integer to indexed array (a[0] decay)
483 declare -a array=(1 2 3)
484 echo $((array + 5))
485 ## status: 0
486 ## STDOUT:
487 6
488 ## END
489 ## N-I dash status: 2
490 ## N-I dash stdout-json: ""
491 ## N-I mksh/zsh status: 1
492 ## N-I mksh/zsh stdout-json: ""
493
494 #### Add integer to associative array (a[0] decay)
495 typeset -A assoc
496 assoc[0]=42
497 echo $((assoc + 5))
498 ## status: 0
499 ## stdout: 47
500 ## BUG dash status: 0
501 ## BUG dash stdout: 5
502
503 #### Double subscript
504 a=(1 2 3)
505 echo $(( a[1] ))
506 echo $(( a[1][1] ))
507 ## status: 1
508 ## OK osh status: 2
509 ## STDOUT:
510 2
511 ## END
512 ## N-I dash status: 2
513 ## N-I dash stdout-json: ""
514 ## OK zsh STDOUT:
515 1
516 ## END
517
518 #### result of ArithSub -- array[0] decay
519 a=(4 5 6)
520 echo declared
521 b=$(( a ))
522 echo $b
523
524 ## status: 0
525 ## STDOUT:
526 declared
527 4
528 ## END
529 ## N-I dash status: 2
530 ## N-I dash stdout-json: ""
531 ## N-I zsh status: 1
532 ## N-I zsh STDOUT:
533 declared
534 ## END
535
536 #### result of ArithSub -- assoc[0] decay
537 declare -A A=(['foo']=bar ['spam']=eggs)
538 echo declared
539 b=$(( A ))
540 echo $b
541
542 ## status: 0
543 ## STDOUT:
544 declared
545 0
546 ## END
547
548 ## N-I mksh status: 1
549 ## N-I mksh stdout-json: ""
550
551
552 ## N-I dash status: 2
553 ## N-I dash stdout-json: ""
554
555 #### comma operator
556 a=(4 5 6)
557
558 # zsh and osh can't evaluate the array like that
559 # which is consistent with their behavior on $(( a ))
560
561 echo $(( a, last = a[2], 42 ))
562 echo last=$last
563
564 ## status: 0
565 ## STDOUT:
566 42
567 last=6
568 ## END
569 ## N-I dash status: 2
570 ## N-I dash stdout-json: ""
571 ## N-I zsh status: 1
572 ## N-I zsh stdout-json: ""
573
574
575 #### assignment with dynamic var name
576 foo=bar
577 echo $(( x$foo = 42 ))
578 echo xbar=$xbar
579 ## STDOUT:
580 42
581 xbar=42
582 ## END
583
584 #### array assignment with dynamic array name
585 foo=bar
586 echo $(( x$foo[5] = 42 ))
587 echo 'xbar[5]='${xbar[5]}
588 ## STDOUT:
589 42
590 xbar[5]=42
591 ## END
592 ## BUG zsh STDOUT:
593 42
594 xbar[5]=
595 ## END
596 ## N-I dash status: 2
597 ## N-I dash stdout-json: ""
598
599 #### unary assignment with dynamic var name
600 foo=bar
601 xbar=42
602 echo $(( x$foo++ ))
603 echo xbar=$xbar
604 ## STDOUT:
605 42
606 xbar=43
607 ## END
608 ## BUG dash status: 2
609 ## BUG dash stdout-json: ""
610
611 #### unary array assignment with dynamic var name
612 foo=bar
613 xbar[5]=42
614 echo $(( x$foo[5]++ ))
615 echo 'xbar[5]='${xbar[5]}
616 ## STDOUT:
617 42
618 xbar[5]=43
619 ## END
620 ## BUG zsh STDOUT:
621 0
622 xbar[5]=42
623 ## END
624 ## N-I dash status: 2
625 ## N-I dash stdout-json: ""
626
627 #### Dynamic parsing of arithmetic
628 e=1+2
629 echo $(( e + 3 ))
630 [[ e -eq 3 ]] && echo true
631 [ e -eq 3 ]
632 echo status=$?
633 ## STDOUT:
634 6
635 true
636 status=2
637 ## END
638 ## BUG mksh STDOUT:
639 6
640 true
641 status=0
642 ## END
643 ## N-I dash status: 2
644 ## N-I dash stdout-json: ""
645
646 #### Dynamic parsing on empty string
647 a=''
648 echo $(( a ))
649
650 a2=' '
651 echo $(( a2 ))
652 ## STDOUT:
653 0
654 0
655 ## END
656
657 #### nested ternary (bug fix)
658 echo $((1?2?3:4:5))
659 ## STDOUT:
660 3
661 ## END
662
663 #### 1 ? a=1 : b=2 ( bug fix)
664 echo $((1 ? a=1 : 42 ))
665 echo a=$a
666
667 # this does NOT work
668 #echo $((1 ? a=1 : b=2 ))
669
670 ## STDOUT:
671 1
672 a=1
673 ## END
674 ## BUG zsh stdout-json: ""
675 ## BUG zsh status: 1
676
677 #### Invalid constant
678
679 echo $((a + x42))
680 echo status=$?
681
682 # weird asymmetry -- the above is a syntax error, but this isn't
683 $SH -c 'echo $((a + 42x))'
684 echo status=$?
685
686 # regression
687 echo $((a + 42x))
688 echo status=$?
689 ## status: 1
690 ## STDOUT:
691 0
692 status=0
693 status=1
694 ## END
695 ## OK dash status: 2
696 ## OK dash STDOUT:
697 0
698 status=0
699 status=2
700 ## END
701 ## BUG bash status: 0
702 ## BUG bash STDOUT:
703 0
704 status=0
705 status=1
706 status=1
707 ## END
708
709 #### Negative numbers with integer division /
710
711 echo $(( 10 / 3))
712 echo $((-10 / 3))
713 echo $(( 10 / -3))
714 echo $((-10 / -3))
715
716 echo ---
717
718 a=20
719 : $(( a /= 3 ))
720 echo $a
721
722 a=-20
723 : $(( a /= 3 ))
724 echo $a
725
726 a=20
727 : $(( a /= -3 ))
728 echo $a
729
730 a=-20
731 : $(( a /= -3 ))
732 echo $a
733
734 ## STDOUT:
735 3
736 -3
737 -3
738 3
739 ---
740 6
741 -6
742 -6
743 6
744 ## END
745
746 #### Negative numbers with %
747
748 echo $(( 10 % 3))
749 echo $((-10 % 3))
750 echo $(( 10 % -3))
751 echo $((-10 % -3))
752
753 ## STDOUT:
754 1
755 -1
756 1
757 -1
758 ## END
759
760 #### Negative numbers with bit shift
761
762 echo $(( 5 << 1 ))
763 echo $(( 5 << 0 ))
764 $SH -c 'echo $(( 5 << -1 ))' # implementation defined - OSH fails
765 echo ---
766
767 echo $(( 16 >> 1 ))
768 echo $(( 16 >> 0 ))
769 $SH -c 'echo $(( 16 >> -1 ))' # not sure why this is zero
770 $SH -c 'echo $(( 16 >> -2 ))' # also 0
771 echo ---
772
773 ## STDOUT:
774 10
775 5
776 ---
777 8
778 16
779 ---
780 ## END
781
782 ## OK bash/dash/mksh/zsh STDOUT:
783 10
784 5
785 -9223372036854775808
786 ---
787 8
788 16
789 0
790 0
791 ---
792 ## END
793
794 ## BUG mksh STDOUT:
795 10
796 5
797 -2147483648
798 ---
799 8
800 16
801 0
802 0
803 ---
804 ## END
805
806 #### undef[0]
807 case $SH in dash) exit ;; esac
808
809 echo ARITH $(( undef[0] ))
810 echo status=$?
811 echo
812
813 (( undef[0] ))
814 echo status=$?
815 echo
816
817 echo UNDEF ${undef[0]}
818 echo status=$?
819
820 ## STDOUT:
821 ARITH 0
822 status=0
823
824 status=1
825
826 UNDEF
827 status=0
828 ## END
829 ## N-I dash STDOUT:
830 ## END
831
832 #### undef[0] with nounset
833 case $SH in dash) exit ;; esac
834
835 set -o nounset
836 echo UNSET $(( undef[0] ))
837 echo status=$?
838
839 ## status: 1
840 ## STDOUT:
841 ## END
842
843 ## N-I dash status: 0
844
845 ## BUG mksh/zsh status: 0
846 ## BUG mksh/zsh STDOUT:
847 UNSET 0
848 status=0
849 ## END
850
851 ## N-I dash STDOUT:
852 ## END
853
854 #### s[0] with string abc
855 case $SH in dash) exit ;; esac
856
857 s='abc'
858 echo abc $(( s[0] )) $(( s[1] ))
859 echo status=$?
860 echo
861
862 (( s[0] ))
863 echo status=$?
864 echo
865
866 ## STDOUT:
867 abc 0 0
868 status=0
869
870 status=1
871
872 ## END
873 ## N-I dash STDOUT:
874 ## END
875
876 #### s[0] with string 42
877 case $SH in dash) exit ;; esac
878
879 s='42'
880 echo 42 $(( s[0] )) $(( s[1] ))
881 echo status=$?
882
883 ## STDOUT:
884 42 42 0
885 status=0
886 ## END
887 ## N-I dash STDOUT:
888 ## END
889
890 ## BUG zsh STDOUT:
891 42 0 4
892 status=0
893 ## END
894
895 #### s[0] with string '12 34'
896
897 s='12 34'
898 echo '12 34' $(( s[0] )) $(( s[1] ))
899 echo status=$?
900
901 ## status: 1
902 ## STDOUT:
903 ## END
904
905 ## OK dash status: 2
906
907 ## BUG zsh status: 0
908 ## BUG zsh STDOUT:
909 12 34 0 1
910 status=0
911 ## END
912
913 # bash prints an error, but doesn't fail
914
915 ## BUG bash status: 0
916 ## BUG bash STDOUT:
917 status=1
918 ## END