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

998 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
138check() {
139 $SH -c "shopt --set strict_arith; echo $1"
140 echo status=$?
141}
142
143check '$(( 0x1X ))'
144check '$(( 09 ))'
145check '$(( 2#A ))'
146check '$(( 02#0110 ))'
147## STDOUT:
148status=1
149status=1
150status=1
151status=1
152## END
153
154## OK dash STDOUT:
155status=2
156status=2
157status=2
158status=2
159## END
160
161## BUG zsh STDOUT:
162status=1
1639
164status=0
165status=1
1666
167status=0
168## END
169
170## BUG mksh STDOUT:
171status=1
1729
173status=0
174status=1
1756
176status=0
177## END
178
179#### Newline in the middle of expression
180echo $((1
181+ 2))
182## stdout: 3
183
184#### Ternary operator
185a=1
186b=2
187echo $((a>b?5:10))
188## stdout: 10
189
190#### Preincrement
191a=4
192echo $((++a))
193echo $a
194## stdout-json: "5\n5\n"
195## N-I dash status: 0
196## N-I dash stdout-json: "4\n4\n"
197
198#### Postincrement
199a=4
200echo $((a++))
201echo $a
202## stdout-json: "4\n5\n"
203## N-I dash status: 2
204## N-I dash stdout-json: ""
205
206#### Increment undefined variables
207shopt -u strict_arith || true
208(( undef1++ ))
209(( ++undef2 ))
210echo "[$undef1][$undef2]"
211## stdout: [1][1]
212## N-I dash stdout: [][]
213
214#### Increment and decrement array elements
215shopt -u strict_arith || true
216a=(5 6 7 8)
217(( a[0]++, ++a[1], a[2]--, --a[3] ))
218(( undef[0]++, ++undef[1], undef[2]--, --undef[3] ))
219echo "${a[@]}" - "${undef[@]}"
220## stdout: 6 7 6 7 - 1 1 -1 -1
221## N-I dash stdout-json: ""
222## N-I dash status: 2
223## BUG zsh stdout: 5 6 7 8 -
224
225#### Increment undefined variables with nounset
226set -o nounset
227(( undef1++ ))
228(( ++undef2 ))
229echo "[$undef1][$undef2]"
230## stdout-json: ""
231## status: 1
232## OK dash status: 2
233## BUG mksh/zsh status: 0
234## BUG mksh/zsh stdout-json: "[1][1]\n"
235
236#### Comma operator (borrowed from C)
237a=1
238b=2
239echo $((a,(b+1)))
240## stdout: 3
241## N-I dash status: 2
242## N-I dash stdout-json: ""
243
244#### Augmented assignment
245a=4
246echo $((a+=1))
247echo $a
248## stdout-json: "5\n5\n"
249
250#### Comparison Ops
251echo $(( 1 == 1 ))
252echo $(( 1 != 1 ))
253echo $(( 1 < 1 ))
254echo $(( 1 <= 1 ))
255echo $(( 1 > 1 ))
256echo $(( 1 >= 1 ))
257## stdout-json: "1\n0\n0\n1\n0\n1\n"
258
259#### Logical Ops
260echo $((1 || 2))
261echo $((1 && 2))
262echo $((!(1 || 2)))
263## stdout-json: "1\n1\n0\n"
264
265#### Logical Ops Short Circuit
266x=11
267(( 1 || (x = 22) ))
268echo $x
269(( 0 || (x = 33) ))
270echo $x
271(( 0 && (x = 44) ))
272echo $x
273(( 1 && (x = 55) ))
274echo $x
275## stdout-json: "11\n33\n33\n55\n"
276## N-I dash stdout-json: "11\n11\n11\n11\n"
277
278#### Bitwise ops
279echo $((1|2))
280echo $((1&2))
281echo $((1^2))
282echo $((~(1|2)))
283## stdout-json: "3\n0\n3\n-4\n"
284
285#### Unary minus and plus
286a=1
287b=3
288echo $((- a + + b))
289## stdout-json: "2\n"
290
291#### No floating point
292echo $((1 + 2.3))
293## status: 2
294## OK bash/mksh status: 1
295## BUG zsh status: 0
296
297#### Array indexing in arith
298# zsh does 1-based indexing!
299array=(1 2 3 4)
300echo $((array[1] + array[2]*3))
301## stdout: 11
302## OK zsh stdout: 7
303## N-I dash status: 2
304## N-I dash stdout-json: ""
305
306#### Constants in base 36
307echo $((36#a))-$((36#z))
308## stdout: 10-35
309## N-I dash stdout-json: ""
310## N-I dash status: 2
311
312#### Constants in bases 2 to 64
313# This is a truly bizarre syntax. Oh it comes from zsh... which allows 36.
314echo $((64#a))-$((64#z)), $((64#A))-$((64#Z)), $((64#@)), $(( 64#_ ))
315## stdout: 10-35, 36-61, 62, 63
316## N-I dash stdout-json: ""
317## N-I dash status: 2
318## N-I mksh/zsh stdout-json: ""
319## N-I mksh/zsh status: 1
320
321#### Multiple digit constants with base N
322echo $((10#0123)), $((16#1b))
323## stdout: 123, 27
324## N-I dash stdout-json: ""
325## N-I dash status: 2
326
327#### Dynamic base constants
328base=16
329echo $(( ${base}#a ))
330## stdout: 10
331## N-I dash stdout-json: ""
332## N-I dash status: 2
333
334#### Octal constant
335echo $(( 011 ))
336## stdout: 9
337## N-I mksh/zsh stdout: 11
338
339#### Dynamic octal constant
340zero=0
341echo $(( ${zero}11 ))
342## stdout: 9
343## N-I mksh/zsh stdout: 11
344
345#### Dynamic hex constants
346zero=0
347echo $(( ${zero}xAB ))
348## stdout: 171
349
350#### Dynamic var names - result of runtime parse/eval
351foo=5
352x=oo
353echo $(( foo + f$x + 1 ))
354## stdout: 11
355
356#### Recursive name evaluation is a result of runtime parse/eval
357foo=5
358bar=foo
359spam=bar
360eggs=spam
361echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
362## stdout: 6 6 6 6
363## N-I dash stdout-json: ""
364## N-I dash status: 2
365
366#### nounset with arithmetic
367set -o nounset
368x=$(( y + 5 ))
369echo "should not get here: x=${x:-<unset>}"
370## stdout-json: ""
371## status: 1
372## BUG dash/mksh/zsh stdout: should not get here: x=5
373## BUG dash/mksh/zsh status: 0
374
375#### 64-bit integer doesn't overflow
376
377a=$(( 1 << 31 ))
378echo $a
379
380b=$(( a + a ))
381echo $b
382
383c=$(( b + a ))
384echo $c
385
386x=$(( 1 << 62 ))
387y=$(( x - 1 ))
388echo "max positive = $(( x + y ))"
389
390#echo "overflow $(( x + x ))"
391
392## STDOUT:
3932147483648
3944294967296
3956442450944
396max positive = 9223372036854775807
397## END
398
399# mksh still uses int!
400## BUG mksh STDOUT:
401-2147483648
4020
403-2147483648
404max positive = 2147483647
405## END
406
407#### More 64-bit ops
408case $SH in dash) exit ;; esac
409
410#shopt -s strict_arith
411
412# This overflows - the extra 9 puts it above 2**31
413#echo $(( 12345678909 ))
414
415[[ 12345678909 = $(( 1 << 30 )) ]]
416echo eq=$?
417[[ 12345678909 = 12345678909 ]]
418echo eq=$?
419
420# Try both [ and [[
421[ 12345678909 -gt $(( 1 << 30 )) ]
422echo greater=$?
423[[ 12345678909 -gt $(( 1 << 30 )) ]]
424echo greater=$?
425
426[[ 12345678909 -ge $(( 1 << 30 )) ]]
427echo ge=$?
428[[ 12345678909 -ge 12345678909 ]]
429echo ge=$?
430
431[[ 12345678909 -le $(( 1 << 30 )) ]]
432echo le=$?
433[[ 12345678909 -le 12345678909 ]]
434echo le=$?
435
436## STDOUT:
437eq=1
438eq=0
439greater=0
440greater=0
441ge=0
442ge=0
443le=1
444le=0
445## END
446## N-I dash STDOUT:
447## END
448## BUG mksh STDOUT:
449eq=1
450eq=0
451greater=1
452greater=1
453ge=1
454ge=0
455le=0
456le=0
457## END
458
459# mksh still uses int!
460
461#### Invalid LValue
462a=9
463(( (a + 2) = 3 ))
464echo $a
465## status: 2
466## stdout-json: ""
467## OK bash/mksh/zsh stdout: 9
468## OK bash/mksh/zsh status: 0
469# dash doesn't implement assignment
470## N-I dash status: 2
471## N-I dash stdout-json: ""
472
473#### Invalid LValue that looks like array
474(( 1[2] = 3 ))
475echo "status=$?"
476## status: 1
477## stdout-json: ""
478
479## OK bash stdout: status=1
480## OK bash status: 0
481
482## OK mksh/zsh stdout: status=2
483## OK mksh/zsh status: 0
484
485## N-I dash stdout: status=127
486## N-I dash status: 0
487
488#### Invalid LValue: two sets of brackets
489(( a[1][2] = 3 ))
490echo "status=$?"
491# shells treat this as a NON-fatal error
492## status: 2
493## stdout-json: ""
494## OK bash stdout: status=1
495## OK mksh/zsh stdout: status=2
496## OK bash/mksh/zsh status: 0
497# dash doesn't implement assignment
498## N-I dash stdout: status=127
499## N-I dash status: 0
500
501#### Operator Precedence
502echo $(( 1 + 2*3 - 8/2 ))
503## stdout: 3
504
505#### Exponentiation with **
506echo $(( 3 ** 0 ))
507echo $(( 3 ** 1 ))
508echo $(( 3 ** 2 ))
509## STDOUT:
5101
5113
5129
513## END
514## N-I dash stdout-json: ""
515## N-I dash status: 2
516## N-I mksh stdout-json: ""
517## N-I mksh status: 1
518
519#### Exponentiation operator has buggy precedence
520# NOTE: All shells agree on this, but R and Python give -9, which is more
521# mathematically correct.
522echo $(( -3 ** 2 ))
523## stdout: 9
524## N-I dash stdout-json: ""
525## N-I dash status: 2
526## N-I mksh stdout-json: ""
527## N-I mksh status: 1
528
529#### Negative exponent
530# bash explicitly disallows negative exponents!
531echo $(( 2**-1 * 5 ))
532## stdout-json: ""
533## status: 1
534## OK zsh stdout: 2.5
535## OK zsh status: 0
536## N-I dash stdout-json: ""
537## N-I dash status: 2
538
539#### Comment not allowed in the middle of multiline arithmetic
540echo $((
5411 +
5422 + \
5433
544))
545echo $((
5461 + 2 # not a comment
547))
548(( a = 3 + 4 # comment
549))
550echo [$a]
551## status: 1
552## STDOUT:
5536
554## END
555## OK dash/osh status: 2
556## OK bash STDOUT:
5576
558[]
559## END
560## OK bash status: 0
561
562#### Add integer to indexed array (a[0] decay)
563declare -a array=(1 2 3)
564echo $((array + 5))
565## status: 0
566## STDOUT:
5676
568## END
569## N-I dash status: 2
570## N-I dash stdout-json: ""
571## N-I mksh/zsh status: 1
572## N-I mksh/zsh stdout-json: ""
573
574#### Add integer to associative array (a[0] decay)
575typeset -A assoc
576assoc[0]=42
577echo $((assoc + 5))
578## status: 0
579## stdout: 47
580## BUG dash status: 0
581## BUG dash stdout: 5
582
583#### Double subscript
584a=(1 2 3)
585echo $(( a[1] ))
586echo $(( a[1][1] ))
587## status: 1
588## OK osh status: 2
589## STDOUT:
5902
591## END
592## N-I dash status: 2
593## N-I dash stdout-json: ""
594## OK zsh STDOUT:
5951
596## END
597
598#### result of ArithSub -- array[0] decay
599a=(4 5 6)
600echo declared
601b=$(( a ))
602echo $b
603
604## status: 0
605## STDOUT:
606declared
6074
608## END
609## N-I dash status: 2
610## N-I dash stdout-json: ""
611## N-I zsh status: 1
612## N-I zsh STDOUT:
613declared
614## END
615
616#### result of ArithSub -- assoc[0] decay
617declare -A A=(['foo']=bar ['spam']=eggs)
618echo declared
619b=$(( A ))
620echo $b
621
622## status: 0
623## STDOUT:
624declared
6250
626## END
627
628## N-I mksh status: 1
629## N-I mksh stdout-json: ""
630
631
632## N-I dash status: 2
633## N-I dash stdout-json: ""
634
635#### comma operator
636a=(4 5 6)
637
638# zsh and osh can't evaluate the array like that
639# which is consistent with their behavior on $(( a ))
640
641echo $(( a, last = a[2], 42 ))
642echo last=$last
643
644## status: 0
645## STDOUT:
64642
647last=6
648## END
649## N-I dash status: 2
650## N-I dash stdout-json: ""
651## N-I zsh status: 1
652## N-I zsh stdout-json: ""
653
654
655#### assignment with dynamic var name
656foo=bar
657echo $(( x$foo = 42 ))
658echo xbar=$xbar
659## STDOUT:
66042
661xbar=42
662## END
663
664#### array assignment with dynamic array name
665foo=bar
666echo $(( x$foo[5] = 42 ))
667echo 'xbar[5]='${xbar[5]}
668## STDOUT:
66942
670xbar[5]=42
671## END
672## BUG zsh STDOUT:
67342
674xbar[5]=
675## END
676## N-I dash status: 2
677## N-I dash stdout-json: ""
678
679#### unary assignment with dynamic var name
680foo=bar
681xbar=42
682echo $(( x$foo++ ))
683echo xbar=$xbar
684## STDOUT:
68542
686xbar=43
687## END
688## BUG dash status: 2
689## BUG dash stdout-json: ""
690
691#### unary array assignment with dynamic var name
692foo=bar
693xbar[5]=42
694echo $(( x$foo[5]++ ))
695echo 'xbar[5]='${xbar[5]}
696## STDOUT:
69742
698xbar[5]=43
699## END
700## BUG zsh STDOUT:
7010
702xbar[5]=42
703## END
704## N-I dash status: 2
705## N-I dash stdout-json: ""
706
707#### Dynamic parsing of arithmetic
708e=1+2
709echo $(( e + 3 ))
710[[ e -eq 3 ]] && echo true
711[ e -eq 3 ]
712echo status=$?
713## STDOUT:
7146
715true
716status=2
717## END
718## BUG mksh STDOUT:
7196
720true
721status=0
722## END
723## N-I dash status: 2
724## N-I dash stdout-json: ""
725
726#### Dynamic parsing on empty string
727a=''
728echo $(( a ))
729
730a2=' '
731echo $(( a2 ))
732## STDOUT:
7330
7340
735## END
736
737#### nested ternary (bug fix)
738echo $((1?2?3:4:5))
739## STDOUT:
7403
741## END
742
743#### 1 ? a=1 : b=2 ( bug fix)
744echo $((1 ? a=1 : 42 ))
745echo a=$a
746
747# this does NOT work
748#echo $((1 ? a=1 : b=2 ))
749
750## STDOUT:
7511
752a=1
753## END
754## BUG zsh stdout-json: ""
755## BUG zsh status: 1
756
757#### Invalid constant
758
759echo $((a + x42))
760echo status=$?
761
762# weird asymmetry -- the above is a syntax error, but this isn't
763$SH -c 'echo $((a + 42x))'
764echo status=$?
765
766# regression
767echo $((a + 42x))
768echo status=$?
769## status: 1
770## STDOUT:
7710
772status=0
773status=1
774## END
775## OK dash status: 2
776## OK dash STDOUT:
7770
778status=0
779status=2
780## END
781## BUG bash status: 0
782## BUG bash STDOUT:
7830
784status=0
785status=1
786status=1
787## END
788
789#### Negative numbers with integer division /
790
791echo $(( 10 / 3))
792echo $((-10 / 3))
793echo $(( 10 / -3))
794echo $((-10 / -3))
795
796echo ---
797
798a=20
799: $(( a /= 3 ))
800echo $a
801
802a=-20
803: $(( a /= 3 ))
804echo $a
805
806a=20
807: $(( a /= -3 ))
808echo $a
809
810a=-20
811: $(( a /= -3 ))
812echo $a
813
814## STDOUT:
8153
816-3
817-3
8183
819---
8206
821-6
822-6
8236
824## END
825
826#### Negative numbers with %
827
828echo $(( 10 % 3))
829echo $((-10 % 3))
830echo $(( 10 % -3))
831echo $((-10 % -3))
832
833## STDOUT:
8341
835-1
8361
837-1
838## END
839
840#### Negative numbers with bit shift
841
842echo $(( 5 << 1 ))
843echo $(( 5 << 0 ))
844$SH -c 'echo $(( 5 << -1 ))' # implementation defined - OSH fails
845echo ---
846
847echo $(( 16 >> 1 ))
848echo $(( 16 >> 0 ))
849$SH -c 'echo $(( 16 >> -1 ))' # not sure why this is zero
850$SH -c 'echo $(( 16 >> -2 ))' # also 0
851echo ---
852
853## STDOUT:
85410
8555
856---
8578
85816
859---
860## END
861
862## OK bash/dash/mksh/zsh STDOUT:
86310
8645
865-9223372036854775808
866---
8678
86816
8690
8700
871---
872## END
873
874## BUG mksh STDOUT:
87510
8765
877-2147483648
878---
8798
88016
8810
8820
883---
884## END
885
886#### undef[0]
887case $SH in dash) exit ;; esac
888
889echo ARITH $(( undef[0] ))
890echo status=$?
891echo
892
893(( undef[0] ))
894echo status=$?
895echo
896
897echo UNDEF ${undef[0]}
898echo status=$?
899
900## STDOUT:
901ARITH 0
902status=0
903
904status=1
905
906UNDEF
907status=0
908## END
909## N-I dash STDOUT:
910## END
911
912#### undef[0] with nounset
913case $SH in dash) exit ;; esac
914
915set -o nounset
916echo UNSET $(( undef[0] ))
917echo status=$?
918
919## status: 1
920## STDOUT:
921## END
922
923## N-I dash status: 0
924
925## BUG mksh/zsh status: 0
926## BUG mksh/zsh STDOUT:
927UNSET 0
928status=0
929## END
930
931## N-I dash STDOUT:
932## END
933
934#### s[0] with string abc
935case $SH in dash) exit ;; esac
936
937s='abc'
938echo abc $(( s[0] )) $(( s[1] ))
939echo status=$?
940echo
941
942(( s[0] ))
943echo status=$?
944echo
945
946## STDOUT:
947abc 0 0
948status=0
949
950status=1
951
952## END
953## N-I dash STDOUT:
954## END
955
956#### s[0] with string 42
957case $SH in dash) exit ;; esac
958
959s='42'
960echo 42 $(( s[0] )) $(( s[1] ))
961echo status=$?
962
963## STDOUT:
96442 42 0
965status=0
966## END
967## N-I dash STDOUT:
968## END
969
970## BUG zsh STDOUT:
97142 0 4
972status=0
973## END
974
975#### s[0] with string '12 34'
976
977s='12 34'
978echo '12 34' $(( s[0] )) $(( s[1] ))
979echo status=$?
980
981## status: 1
982## STDOUT:
983## END
984
985## OK dash status: 2
986
987## BUG zsh status: 0
988## BUG zsh STDOUT:
98912 34 0 1
990status=0
991## END
992
993# bash prints an error, but doesn't fail
994
995## BUG bash status: 0
996## BUG bash STDOUT:
997status=1
998## END