OILS / spec / arith.test.sh View on Github | oilshell.org

997 lines, 446 significant
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
18a=(4 5 6)
19echo "${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
26i=1
27echo $(($i+1))
28## stdout: 2
29
30#### $ is optional
31i=1
32echo $((i+1))
33## stdout: 2
34
35#### SimpleVarSub within arith
36j=0
37echo $(($j + 42))
38## stdout: 42
39
40#### BracedVarSub within ArithSub
41echo $((${j:-5} + 1))
42## stdout: 6
43
44#### Arith word part
45foo=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.
50echo $((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.
56echo $(('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.
63echo $((1 + $((2 + 3)) + 4))
64## stdout: 10
65
66#### Backticks within arith sub
67# This is unnecessary but works in all shells.
68echo $((`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.
73shopt -u strict_arith || true
74s=foo
75echo $((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
82shopt -s strict_arith || true
83s=foo
84echo $s
85echo $((s+5))
86echo 'should not get here'
87## status: 1
88## STDOUT:
89foo
90## END
91## OK dash status: 2
92## N-I bash/mksh/zsh STDOUT:
93foo
945
95should not get here
96## END
97## N-I bash/mksh/zsh status: 0
98
99#### Integer constant parsing
100echo $(( 0x12A ))
101echo $(( 0x0A ))
102echo $(( 0777 ))
103echo $(( 0010 ))
104echo $(( 24#ag7 ))
105## STDOUT:
106298
10710
108511
1098
1106151
111## END
112
113## N-I dash status: 2
114## N-I dash STDOUT:
115298
11610
117511
1188
119## END
120
121## BUG zsh STDOUT:
122298
12310
124777
12510
1266151
127## END
128
129## BUG mksh STDOUT:
130298
13110
132777
13310
1346151
135## END
136
137#### Integer constant validation
138$SH -c 'echo $(( 0x1X ))'
139echo status=$?
140$SH -c 'echo $(( 09 ))'
141echo status=$?
142$SH -c 'echo $(( 2#A ))'
143echo status=$?
144$SH -c 'echo $(( 02#0110 ))'
145echo status=$?
146## STDOUT:
147status=1
148status=1
149status=1
150status=1
151## END
152
153## OK dash STDOUT:
154status=2
155status=2
156status=2
157status=2
158## END
159
160## BUG zsh STDOUT:
161status=1
1629
163status=0
164status=1
1656
166status=0
167## END
168
169## BUG mksh STDOUT:
170status=1
1719
172status=0
173status=1
1746
175status=0
176## END
177
178#### Newline in the middle of expression
179echo $((1
180+ 2))
181## stdout: 3
182
183#### Ternary operator
184a=1
185b=2
186echo $((a>b?5:10))
187## stdout: 10
188
189#### Preincrement
190a=4
191echo $((++a))
192echo $a
193## stdout-json: "5\n5\n"
194## N-I dash status: 0
195## N-I dash stdout-json: "4\n4\n"
196
197#### Postincrement
198a=4
199echo $((a++))
200echo $a
201## stdout-json: "4\n5\n"
202## N-I dash status: 2
203## N-I dash stdout-json: ""
204
205#### Increment undefined variables
206shopt -u strict_arith || true
207(( undef1++ ))
208(( ++undef2 ))
209echo "[$undef1][$undef2]"
210## stdout: [1][1]
211## N-I dash stdout: [][]
212
213#### Increment and decrement array elements
214shopt -u strict_arith || true
215a=(5 6 7 8)
216(( a[0]++, ++a[1], a[2]--, --a[3] ))
217(( undef[0]++, ++undef[1], undef[2]--, --undef[3] ))
218echo "${a[@]}" - "${undef[@]}"
219## stdout: 6 7 6 7 - 1 1 -1 -1
220## N-I dash stdout-json: ""
221## N-I dash status: 2
222## BUG zsh stdout: 5 6 7 8 -
223
224#### Increment undefined variables with nounset
225set -o nounset
226(( undef1++ ))
227(( ++undef2 ))
228echo "[$undef1][$undef2]"
229## stdout-json: ""
230## status: 1
231## OK dash status: 2
232## BUG mksh/zsh status: 0
233## BUG mksh/zsh stdout-json: "[1][1]\n"
234
235#### Comma operator (borrowed from C)
236a=1
237b=2
238echo $((a,(b+1)))
239## stdout: 3
240## N-I dash status: 2
241## N-I dash stdout-json: ""
242
243#### Augmented assignment
244a=4
245echo $((a+=1))
246echo $a
247## stdout-json: "5\n5\n"
248
249#### Comparison Ops
250echo $(( 1 == 1 ))
251echo $(( 1 != 1 ))
252echo $(( 1 < 1 ))
253echo $(( 1 <= 1 ))
254echo $(( 1 > 1 ))
255echo $(( 1 >= 1 ))
256## stdout-json: "1\n0\n0\n1\n0\n1\n"
257
258#### Logical Ops
259echo $((1 || 2))
260echo $((1 && 2))
261echo $((!(1 || 2)))
262## stdout-json: "1\n1\n0\n"
263
264#### Logical Ops Short Circuit
265x=11
266(( 1 || (x = 22) ))
267echo $x
268(( 0 || (x = 33) ))
269echo $x
270(( 0 && (x = 44) ))
271echo $x
272(( 1 && (x = 55) ))
273echo $x
274## stdout-json: "11\n33\n33\n55\n"
275## N-I dash stdout-json: "11\n11\n11\n11\n"
276
277#### Bitwise ops
278echo $((1|2))
279echo $((1&2))
280echo $((1^2))
281echo $((~(1|2)))
282## stdout-json: "3\n0\n3\n-4\n"
283
284#### Unary minus and plus
285a=1
286b=3
287echo $((- a + + b))
288## stdout-json: "2\n"
289
290#### No floating point
291echo $((1 + 2.3))
292## status: 2
293## OK bash/mksh status: 1
294## BUG zsh status: 0
295
296#### Array indexing in arith
297# zsh does 1-based indexing!
298array=(1 2 3 4)
299echo $((array[1] + array[2]*3))
300## stdout: 11
301## OK zsh stdout: 7
302## N-I dash status: 2
303## N-I dash stdout-json: ""
304
305#### Constants in base 36
306echo $((36#a))-$((36#z))
307## stdout: 10-35
308## N-I dash stdout-json: ""
309## N-I dash status: 2
310
311#### Constants in bases 2 to 64
312# This is a truly bizarre syntax. Oh it comes from zsh... which allows 36.
313echo $((64#a))-$((64#z)), $((64#A))-$((64#Z)), $((64#@)), $(( 64#_ ))
314## stdout: 10-35, 36-61, 62, 63
315## N-I dash stdout-json: ""
316## N-I dash status: 2
317## N-I mksh/zsh stdout-json: ""
318## N-I mksh/zsh status: 1
319
320#### Multiple digit constants with base N
321echo $((10#0123)), $((16#1b))
322## stdout: 123, 27
323## N-I dash stdout-json: ""
324## N-I dash status: 2
325
326#### Dynamic base constants
327base=16
328echo $(( ${base}#a ))
329## stdout: 10
330## N-I dash stdout-json: ""
331## N-I dash status: 2
332
333#### Octal constant
334echo $(( 011 ))
335## stdout: 9
336## N-I mksh/zsh stdout: 11
337
338#### Dynamic octal constant
339zero=0
340echo $(( ${zero}11 ))
341## stdout: 9
342## N-I mksh/zsh stdout: 11
343
344#### Dynamic hex constants
345zero=0
346echo $(( ${zero}xAB ))
347## stdout: 171
348
349#### Dynamic var names - result of runtime parse/eval
350foo=5
351x=oo
352echo $(( foo + f$x + 1 ))
353## stdout: 11
354
355#### Recursive name evaluation is a result of runtime parse/eval
356foo=5
357bar=foo
358spam=bar
359eggs=spam
360echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
361## stdout: 6 6 6 6
362## N-I dash stdout-json: ""
363## N-I dash status: 2
364
365#### nounset with arithmetic
366set -o nounset
367x=$(( y + 5 ))
368echo "should not get here: x=${x:-<unset>}"
369## stdout-json: ""
370## status: 1
371## BUG dash/mksh/zsh stdout: should not get here: x=5
372## BUG dash/mksh/zsh status: 0
373
374#### 64-bit integer doesn't overflow
375
376a=$(( 1 << 31 ))
377echo $a
378
379b=$(( a + a ))
380echo $b
381
382c=$(( b + a ))
383echo $c
384
385x=$(( 1 << 62 ))
386y=$(( x - 1 ))
387echo "max positive = $(( x + y ))"
388
389#echo "overflow $(( x + x ))"
390
391## STDOUT:
3922147483648
3934294967296
3946442450944
395max positive = 9223372036854775807
396## END
397
398# mksh still uses int!
399## BUG mksh STDOUT:
400-2147483648
4010
402-2147483648
403max positive = 2147483647
404## END
405
406#### More 64-bit ops
407case $SH in dash) exit ;; esac
408
409#shopt -s strict_arith
410
411# This overflows - the extra 9 puts it above 2**31
412#echo $(( 12345678909 ))
413
414[[ 12345678909 = $(( 1 << 30 )) ]]
415echo eq=$?
416[[ 12345678909 = 12345678909 ]]
417echo eq=$?
418
419# Try both [ and [[
420[ 12345678909 -gt $(( 1 << 30 )) ]
421echo greater=$?
422[[ 12345678909 -gt $(( 1 << 30 )) ]]
423echo greater=$?
424
425[[ 12345678909 -ge $(( 1 << 30 )) ]]
426echo ge=$?
427[[ 12345678909 -ge 12345678909 ]]
428echo ge=$?
429
430[[ 12345678909 -le $(( 1 << 30 )) ]]
431echo le=$?
432[[ 12345678909 -le 12345678909 ]]
433echo le=$?
434
435## STDOUT:
436eq=1
437eq=0
438greater=0
439greater=0
440ge=0
441ge=0
442le=1
443le=0
444## END
445## N-I dash STDOUT:
446## END
447## BUG mksh STDOUT:
448eq=1
449eq=0
450greater=1
451greater=1
452ge=1
453ge=0
454le=0
455le=0
456## END
457
458# mksh still uses int!
459
460#### Invalid LValue
461a=9
462(( (a + 2) = 3 ))
463echo $a
464## status: 2
465## stdout-json: ""
466## OK bash/mksh/zsh stdout: 9
467## OK bash/mksh/zsh status: 0
468# dash doesn't implement assignment
469## N-I dash status: 2
470## N-I dash stdout-json: ""
471
472#### Invalid LValue that looks like array
473(( 1[2] = 3 ))
474echo "status=$?"
475## status: 1
476## stdout-json: ""
477
478## OK bash stdout: status=1
479## OK bash status: 0
480
481## OK mksh/zsh stdout: status=2
482## OK mksh/zsh status: 0
483
484## N-I dash stdout: status=127
485## N-I dash status: 0
486
487#### Invalid LValue: two sets of brackets
488(( a[1][2] = 3 ))
489echo "status=$?"
490# shells treat this as a NON-fatal error
491## status: 2
492## stdout-json: ""
493## OK bash stdout: status=1
494## OK mksh/zsh stdout: status=2
495## OK bash/mksh/zsh status: 0
496# dash doesn't implement assignment
497## N-I dash stdout: status=127
498## N-I dash status: 0
499
500#### Operator Precedence
501echo $(( 1 + 2*3 - 8/2 ))
502## stdout: 3
503
504#### Exponentiation with **
505echo $(( 3 ** 0 ))
506echo $(( 3 ** 1 ))
507echo $(( 3 ** 2 ))
508## STDOUT:
5091
5103
5119
512## END
513## N-I dash stdout-json: ""
514## N-I dash status: 2
515## N-I mksh stdout-json: ""
516## N-I mksh status: 1
517
518#### Exponentiation operator has buggy precedence
519# NOTE: All shells agree on this, but R and Python give -9, which is more
520# mathematically correct.
521echo $(( -3 ** 2 ))
522## stdout: 9
523## N-I dash stdout-json: ""
524## N-I dash status: 2
525## N-I mksh stdout-json: ""
526## N-I mksh status: 1
527
528#### Negative exponent
529# bash explicitly disallows negative exponents!
530echo $(( 2**-1 * 5 ))
531## stdout-json: ""
532## status: 1
533## OK zsh stdout: 2.5
534## OK zsh status: 0
535## N-I dash stdout-json: ""
536## N-I dash status: 2
537
538#### Comment not allowed in the middle of multiline arithmetic
539echo $((
5401 +
5412 + \
5423
543))
544echo $((
5451 + 2 # not a comment
546))
547(( a = 3 + 4 # comment
548))
549echo [$a]
550## status: 1
551## STDOUT:
5526
553## END
554## OK dash/osh status: 2
555## OK bash STDOUT:
5566
557[]
558## END
559## OK bash status: 0
560
561#### Add integer to indexed array (a[0] decay)
562declare -a array=(1 2 3)
563echo $((array + 5))
564## status: 0
565## STDOUT:
5666
567## END
568## N-I dash status: 2
569## N-I dash stdout-json: ""
570## N-I mksh/zsh status: 1
571## N-I mksh/zsh stdout-json: ""
572
573#### Add integer to associative array (a[0] decay)
574typeset -A assoc
575assoc[0]=42
576echo $((assoc + 5))
577## status: 0
578## stdout: 47
579## BUG dash status: 0
580## BUG dash stdout: 5
581
582#### Double subscript
583a=(1 2 3)
584echo $(( a[1] ))
585echo $(( a[1][1] ))
586## status: 1
587## OK osh status: 2
588## STDOUT:
5892
590## END
591## N-I dash status: 2
592## N-I dash stdout-json: ""
593## OK zsh STDOUT:
5941
595## END
596
597#### result of ArithSub -- array[0] decay
598a=(4 5 6)
599echo declared
600b=$(( a ))
601echo $b
602
603## status: 0
604## STDOUT:
605declared
6064
607## END
608## N-I dash status: 2
609## N-I dash stdout-json: ""
610## N-I zsh status: 1
611## N-I zsh STDOUT:
612declared
613## END
614
615#### result of ArithSub -- assoc[0] decay
616declare -A A=(['foo']=bar ['spam']=eggs)
617echo declared
618b=$(( A ))
619echo $b
620
621## status: 0
622## STDOUT:
623declared
6240
625## END
626
627## N-I mksh status: 1
628## N-I mksh stdout-json: ""
629
630
631## N-I dash status: 2
632## N-I dash stdout-json: ""
633
634#### comma operator
635a=(4 5 6)
636
637# zsh and osh can't evaluate the array like that
638# which is consistent with their behavior on $(( a ))
639
640echo $(( a, last = a[2], 42 ))
641echo last=$last
642
643## status: 0
644## STDOUT:
64542
646last=6
647## END
648## N-I dash status: 2
649## N-I dash stdout-json: ""
650## N-I zsh status: 1
651## N-I zsh stdout-json: ""
652
653
654#### assignment with dynamic var name
655foo=bar
656echo $(( x$foo = 42 ))
657echo xbar=$xbar
658## STDOUT:
65942
660xbar=42
661## END
662
663#### array assignment with dynamic array name
664foo=bar
665echo $(( x$foo[5] = 42 ))
666echo 'xbar[5]='${xbar[5]}
667## STDOUT:
66842
669xbar[5]=42
670## END
671## BUG zsh STDOUT:
67242
673xbar[5]=
674## END
675## N-I dash status: 2
676## N-I dash stdout-json: ""
677
678#### unary assignment with dynamic var name
679foo=bar
680xbar=42
681echo $(( x$foo++ ))
682echo xbar=$xbar
683## STDOUT:
68442
685xbar=43
686## END
687## BUG dash status: 2
688## BUG dash stdout-json: ""
689
690#### unary array assignment with dynamic var name
691foo=bar
692xbar[5]=42
693echo $(( x$foo[5]++ ))
694echo 'xbar[5]='${xbar[5]}
695## STDOUT:
69642
697xbar[5]=43
698## END
699## BUG zsh STDOUT:
7000
701xbar[5]=42
702## END
703## N-I dash status: 2
704## N-I dash stdout-json: ""
705
706#### Dynamic parsing of arithmetic
707e=1+2
708echo $(( e + 3 ))
709[[ e -eq 3 ]] && echo true
710[ e -eq 3 ]
711echo status=$?
712## STDOUT:
7136
714true
715status=2
716## END
717## BUG mksh STDOUT:
7186
719true
720status=0
721## END
722## N-I dash status: 2
723## N-I dash stdout-json: ""
724
725#### Dynamic parsing on empty string
726a=''
727echo $(( a ))
728
729a2=' '
730echo $(( a2 ))
731## STDOUT:
7320
7330
734## END
735
736#### nested ternary (bug fix)
737echo $((1?2?3:4:5))
738## STDOUT:
7393
740## END
741
742#### 1 ? a=1 : b=2 ( bug fix)
743echo $((1 ? a=1 : 42 ))
744echo a=$a
745
746# this does NOT work
747#echo $((1 ? a=1 : b=2 ))
748
749## STDOUT:
7501
751a=1
752## END
753## BUG zsh stdout-json: ""
754## BUG zsh status: 1
755
756#### Invalid constant
757
758echo $((a + x42))
759echo status=$?
760
761# weird asymmetry -- the above is a syntax error, but this isn't
762$SH -c 'echo $((a + 42x))'
763echo status=$?
764
765# regression
766echo $((a + 42x))
767echo status=$?
768## status: 1
769## STDOUT:
7700
771status=0
772status=1
773## END
774## OK dash status: 2
775## OK dash STDOUT:
7760
777status=0
778status=2
779## END
780## BUG bash status: 0
781## BUG bash STDOUT:
7820
783status=0
784status=1
785status=1
786## END
787
788#### Negative numbers with integer division /
789
790echo $(( 10 / 3))
791echo $((-10 / 3))
792echo $(( 10 / -3))
793echo $((-10 / -3))
794
795echo ---
796
797a=20
798: $(( a /= 3 ))
799echo $a
800
801a=-20
802: $(( a /= 3 ))
803echo $a
804
805a=20
806: $(( a /= -3 ))
807echo $a
808
809a=-20
810: $(( a /= -3 ))
811echo $a
812
813## STDOUT:
8143
815-3
816-3
8173
818---
8196
820-6
821-6
8226
823## END
824
825#### Negative numbers with %
826
827echo $(( 10 % 3))
828echo $((-10 % 3))
829echo $(( 10 % -3))
830echo $((-10 % -3))
831
832## STDOUT:
8331
834-1
8351
836-1
837## END
838
839#### Negative numbers with bit shift
840
841echo $(( 5 << 1 ))
842echo $(( 5 << 0 ))
843$SH -c 'echo $(( 5 << -1 ))' # implementation defined - OSH fails
844echo ---
845
846echo $(( 16 >> 1 ))
847echo $(( 16 >> 0 ))
848$SH -c 'echo $(( 16 >> -1 ))' # not sure why this is zero
849$SH -c 'echo $(( 16 >> -2 ))' # also 0
850echo ---
851
852## STDOUT:
85310
8545
855---
8568
85716
858---
859## END
860
861## OK bash/dash/mksh/zsh STDOUT:
86210
8635
864-9223372036854775808
865---
8668
86716
8680
8690
870---
871## END
872
873## BUG mksh STDOUT:
87410
8755
876-2147483648
877---
8788
87916
8800
8810
882---
883## END
884
885#### undef[0]
886case $SH in dash) exit ;; esac
887
888echo ARITH $(( undef[0] ))
889echo status=$?
890echo
891
892(( undef[0] ))
893echo status=$?
894echo
895
896echo UNDEF ${undef[0]}
897echo status=$?
898
899## STDOUT:
900ARITH 0
901status=0
902
903status=1
904
905UNDEF
906status=0
907## END
908## N-I dash STDOUT:
909## END
910
911#### undef[0] with nounset
912case $SH in dash) exit ;; esac
913
914set -o nounset
915echo UNSET $(( undef[0] ))
916echo status=$?
917
918## status: 1
919## STDOUT:
920## END
921
922## N-I dash status: 0
923
924## BUG mksh/zsh status: 0
925## BUG mksh/zsh STDOUT:
926UNSET 0
927status=0
928## END
929
930## N-I dash STDOUT:
931## END
932
933#### s[0] with string abc
934case $SH in dash) exit ;; esac
935
936s='abc'
937echo abc $(( s[0] )) $(( s[1] ))
938echo status=$?
939echo
940
941(( s[0] ))
942echo status=$?
943echo
944
945## STDOUT:
946abc 0 0
947status=0
948
949status=1
950
951## END
952## N-I dash STDOUT:
953## END
954
955#### s[0] with string 42
956case $SH in dash) exit ;; esac
957
958s='42'
959echo 42 $(( s[0] )) $(( s[1] ))
960echo status=$?
961
962## STDOUT:
96342 42 0
964status=0
965## END
966## N-I dash STDOUT:
967## END
968
969## BUG zsh STDOUT:
97042 0 4
971status=0
972## END
973
974#### s[0] with string '12 34'
975
976s='12 34'
977echo '12 34' $(( s[0] )) $(( s[1] ))
978echo status=$?
979
980## status: 1
981## STDOUT:
982## END
983
984## OK dash status: 2
985
986## BUG zsh status: 0
987## BUG zsh STDOUT:
98812 34 0 1
989status=0
990## END
991
992# bash prints an error, but doesn't fail
993
994## BUG bash status: 0
995## BUG bash STDOUT:
996status=1
997## END