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

918 lines, 394 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#### Newline in the middle of expression
100echo $((1
101+ 2))
102## stdout: 3
103
104#### Ternary operator
105a=1
106b=2
107echo $((a>b?5:10))
108## stdout: 10
109
110#### Preincrement
111a=4
112echo $((++a))
113echo $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
119a=4
120echo $((a++))
121echo $a
122## stdout-json: "4\n5\n"
123## N-I dash status: 2
124## N-I dash stdout-json: ""
125
126#### Increment undefined variables
127shopt -u strict_arith || true
128(( undef1++ ))
129(( ++undef2 ))
130echo "[$undef1][$undef2]"
131## stdout: [1][1]
132## N-I dash stdout: [][]
133
134#### Increment and decrement array elements
135shopt -u strict_arith || true
136a=(5 6 7 8)
137(( a[0]++, ++a[1], a[2]--, --a[3] ))
138(( undef[0]++, ++undef[1], undef[2]--, --undef[3] ))
139echo "${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
146set -o nounset
147(( undef1++ ))
148(( ++undef2 ))
149echo "[$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)
157a=1
158b=2
159echo $((a,(b+1)))
160## stdout: 3
161## N-I dash status: 2
162## N-I dash stdout-json: ""
163
164#### Augmented assignment
165a=4
166echo $((a+=1))
167echo $a
168## stdout-json: "5\n5\n"
169
170#### Comparison Ops
171echo $(( 1 == 1 ))
172echo $(( 1 != 1 ))
173echo $(( 1 < 1 ))
174echo $(( 1 <= 1 ))
175echo $(( 1 > 1 ))
176echo $(( 1 >= 1 ))
177## stdout-json: "1\n0\n0\n1\n0\n1\n"
178
179#### Logical Ops
180echo $((1 || 2))
181echo $((1 && 2))
182echo $((!(1 || 2)))
183## stdout-json: "1\n1\n0\n"
184
185#### Logical Ops Short Circuit
186x=11
187(( 1 || (x = 22) ))
188echo $x
189(( 0 || (x = 33) ))
190echo $x
191(( 0 && (x = 44) ))
192echo $x
193(( 1 && (x = 55) ))
194echo $x
195## stdout-json: "11\n33\n33\n55\n"
196## N-I dash stdout-json: "11\n11\n11\n11\n"
197
198#### Bitwise ops
199echo $((1|2))
200echo $((1&2))
201echo $((1^2))
202echo $((~(1|2)))
203## stdout-json: "3\n0\n3\n-4\n"
204
205#### Unary minus and plus
206a=1
207b=3
208echo $((- a + + b))
209## stdout-json: "2\n"
210
211#### No floating point
212echo $((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!
219array=(1 2 3 4)
220echo $((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
227echo $((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.
234echo $((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
242echo $((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
248base=16
249echo $(( ${base}#a ))
250## stdout: 10
251## N-I dash stdout-json: ""
252## N-I dash status: 2
253
254#### Octal constant
255echo $(( 011 ))
256## stdout: 9
257## N-I mksh/zsh stdout: 11
258
259#### Dynamic octal constant
260zero=0
261echo $(( ${zero}11 ))
262## stdout: 9
263## N-I mksh/zsh stdout: 11
264
265#### Dynamic hex constants
266zero=0
267echo $(( ${zero}xAB ))
268## stdout: 171
269
270#### Dynamic var names - result of runtime parse/eval
271foo=5
272x=oo
273echo $(( foo + f$x + 1 ))
274## stdout: 11
275
276#### Recursive name evaluation is a result of runtime parse/eval
277foo=5
278bar=foo
279spam=bar
280eggs=spam
281echo $((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
287set -o nounset
288x=$(( y + 5 ))
289echo "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
297a=$(( 1 << 31 ))
298echo $a
299
300b=$(( a + a ))
301echo $b
302
303c=$(( b + a ))
304echo $c
305
306x=$(( 1 << 62 ))
307y=$(( x - 1 ))
308echo "max positive = $(( x + y ))"
309
310#echo "overflow $(( x + x ))"
311
312## STDOUT:
3132147483648
3144294967296
3156442450944
316max positive = 9223372036854775807
317## END
318
319# mksh still uses int!
320## BUG mksh STDOUT:
321-2147483648
3220
323-2147483648
324max positive = 2147483647
325## END
326
327#### More 64-bit ops
328case $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 )) ]]
336echo eq=$?
337[[ 12345678909 = 12345678909 ]]
338echo eq=$?
339
340# Try both [ and [[
341[ 12345678909 -gt $(( 1 << 30 )) ]
342echo greater=$?
343[[ 12345678909 -gt $(( 1 << 30 )) ]]
344echo greater=$?
345
346[[ 12345678909 -ge $(( 1 << 30 )) ]]
347echo ge=$?
348[[ 12345678909 -ge 12345678909 ]]
349echo ge=$?
350
351[[ 12345678909 -le $(( 1 << 30 )) ]]
352echo le=$?
353[[ 12345678909 -le 12345678909 ]]
354echo le=$?
355
356## STDOUT:
357eq=1
358eq=0
359greater=0
360greater=0
361ge=0
362ge=0
363le=1
364le=0
365## END
366## N-I dash STDOUT:
367## END
368## BUG mksh STDOUT:
369eq=1
370eq=0
371greater=1
372greater=1
373ge=1
374ge=0
375le=0
376le=0
377## END
378
379# mksh still uses int!
380
381#### Invalid LValue
382a=9
383(( (a + 2) = 3 ))
384echo $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 ))
395echo "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 ))
410echo "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
422echo $(( 1 + 2*3 - 8/2 ))
423## stdout: 3
424
425#### Exponentiation with **
426echo $(( 3 ** 0 ))
427echo $(( 3 ** 1 ))
428echo $(( 3 ** 2 ))
429## STDOUT:
4301
4313
4329
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.
442echo $(( -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!
451echo $(( 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
460echo $((
4611 +
4622 + \
4633
464))
465echo $((
4661 + 2 # not a comment
467))
468(( a = 3 + 4 # comment
469))
470echo [$a]
471## status: 1
472## STDOUT:
4736
474## END
475## OK dash/osh status: 2
476## OK bash STDOUT:
4776
478[]
479## END
480## OK bash status: 0
481
482#### Add integer to indexed array (a[0] decay)
483declare -a array=(1 2 3)
484echo $((array + 5))
485## status: 0
486## STDOUT:
4876
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)
495typeset -A assoc
496assoc[0]=42
497echo $((assoc + 5))
498## status: 0
499## stdout: 47
500## BUG dash status: 0
501## BUG dash stdout: 5
502
503#### Double subscript
504a=(1 2 3)
505echo $(( a[1] ))
506echo $(( a[1][1] ))
507## status: 1
508## OK osh status: 2
509## STDOUT:
5102
511## END
512## N-I dash status: 2
513## N-I dash stdout-json: ""
514## OK zsh STDOUT:
5151
516## END
517
518#### result of ArithSub -- array[0] decay
519a=(4 5 6)
520echo declared
521b=$(( a ))
522echo $b
523
524## status: 0
525## STDOUT:
526declared
5274
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:
533declared
534## END
535
536#### result of ArithSub -- assoc[0] decay
537declare -A A=(['foo']=bar ['spam']=eggs)
538echo declared
539b=$(( A ))
540echo $b
541
542## status: 0
543## STDOUT:
544declared
5450
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
556a=(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
561echo $(( a, last = a[2], 42 ))
562echo last=$last
563
564## status: 0
565## STDOUT:
56642
567last=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
576foo=bar
577echo $(( x$foo = 42 ))
578echo xbar=$xbar
579## STDOUT:
58042
581xbar=42
582## END
583
584#### array assignment with dynamic array name
585foo=bar
586echo $(( x$foo[5] = 42 ))
587echo 'xbar[5]='${xbar[5]}
588## STDOUT:
58942
590xbar[5]=42
591## END
592## BUG zsh STDOUT:
59342
594xbar[5]=
595## END
596## N-I dash status: 2
597## N-I dash stdout-json: ""
598
599#### unary assignment with dynamic var name
600foo=bar
601xbar=42
602echo $(( x$foo++ ))
603echo xbar=$xbar
604## STDOUT:
60542
606xbar=43
607## END
608## BUG dash status: 2
609## BUG dash stdout-json: ""
610
611#### unary array assignment with dynamic var name
612foo=bar
613xbar[5]=42
614echo $(( x$foo[5]++ ))
615echo 'xbar[5]='${xbar[5]}
616## STDOUT:
61742
618xbar[5]=43
619## END
620## BUG zsh STDOUT:
6210
622xbar[5]=42
623## END
624## N-I dash status: 2
625## N-I dash stdout-json: ""
626
627#### Dynamic parsing of arithmetic
628e=1+2
629echo $(( e + 3 ))
630[[ e -eq 3 ]] && echo true
631[ e -eq 3 ]
632echo status=$?
633## STDOUT:
6346
635true
636status=2
637## END
638## BUG mksh STDOUT:
6396
640true
641status=0
642## END
643## N-I dash status: 2
644## N-I dash stdout-json: ""
645
646#### Dynamic parsing on empty string
647a=''
648echo $(( a ))
649
650a2=' '
651echo $(( a2 ))
652## STDOUT:
6530
6540
655## END
656
657#### nested ternary (bug fix)
658echo $((1?2?3:4:5))
659## STDOUT:
6603
661## END
662
663#### 1 ? a=1 : b=2 ( bug fix)
664echo $((1 ? a=1 : 42 ))
665echo a=$a
666
667# this does NOT work
668#echo $((1 ? a=1 : b=2 ))
669
670## STDOUT:
6711
672a=1
673## END
674## BUG zsh stdout-json: ""
675## BUG zsh status: 1
676
677#### Invalid constant
678
679echo $((a + x42))
680echo status=$?
681
682# weird asymmetry -- the above is a syntax error, but this isn't
683$SH -c 'echo $((a + 42x))'
684echo status=$?
685
686# regression
687echo $((a + 42x))
688echo status=$?
689## status: 1
690## STDOUT:
6910
692status=0
693status=1
694## END
695## OK dash status: 2
696## OK dash STDOUT:
6970
698status=0
699status=2
700## END
701## BUG bash status: 0
702## BUG bash STDOUT:
7030
704status=0
705status=1
706status=1
707## END
708
709#### Negative numbers with integer division /
710
711echo $(( 10 / 3))
712echo $((-10 / 3))
713echo $(( 10 / -3))
714echo $((-10 / -3))
715
716echo ---
717
718a=20
719: $(( a /= 3 ))
720echo $a
721
722a=-20
723: $(( a /= 3 ))
724echo $a
725
726a=20
727: $(( a /= -3 ))
728echo $a
729
730a=-20
731: $(( a /= -3 ))
732echo $a
733
734## STDOUT:
7353
736-3
737-3
7383
739---
7406
741-6
742-6
7436
744## END
745
746#### Negative numbers with %
747
748echo $(( 10 % 3))
749echo $((-10 % 3))
750echo $(( 10 % -3))
751echo $((-10 % -3))
752
753## STDOUT:
7541
755-1
7561
757-1
758## END
759
760#### Negative numbers with bit shift
761
762echo $(( 5 << 1 ))
763echo $(( 5 << 0 ))
764$SH -c 'echo $(( 5 << -1 ))' # implementation defined - OSH fails
765echo ---
766
767echo $(( 16 >> 1 ))
768echo $(( 16 >> 0 ))
769$SH -c 'echo $(( 16 >> -1 ))' # not sure why this is zero
770$SH -c 'echo $(( 16 >> -2 ))' # also 0
771echo ---
772
773## STDOUT:
77410
7755
776---
7778
77816
779---
780## END
781
782## OK bash/dash/mksh/zsh STDOUT:
78310
7845
785-9223372036854775808
786---
7878
78816
7890
7900
791---
792## END
793
794## BUG mksh STDOUT:
79510
7965
797-2147483648
798---
7998
80016
8010
8020
803---
804## END
805
806#### undef[0]
807case $SH in dash) exit ;; esac
808
809echo ARITH $(( undef[0] ))
810echo status=$?
811echo
812
813(( undef[0] ))
814echo status=$?
815echo
816
817echo UNDEF ${undef[0]}
818echo status=$?
819
820## STDOUT:
821ARITH 0
822status=0
823
824status=1
825
826UNDEF
827status=0
828## END
829## N-I dash STDOUT:
830## END
831
832#### undef[0] with nounset
833case $SH in dash) exit ;; esac
834
835set -o nounset
836echo UNSET $(( undef[0] ))
837echo 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:
847UNSET 0
848status=0
849## END
850
851## N-I dash STDOUT:
852## END
853
854#### s[0] with string abc
855case $SH in dash) exit ;; esac
856
857s='abc'
858echo abc $(( s[0] )) $(( s[1] ))
859echo status=$?
860echo
861
862(( s[0] ))
863echo status=$?
864echo
865
866## STDOUT:
867abc 0 0
868status=0
869
870status=1
871
872## END
873## N-I dash STDOUT:
874## END
875
876#### s[0] with string 42
877case $SH in dash) exit ;; esac
878
879s='42'
880echo 42 $(( s[0] )) $(( s[1] ))
881echo status=$?
882
883## STDOUT:
88442 42 0
885status=0
886## END
887## N-I dash STDOUT:
888## END
889
890## BUG zsh STDOUT:
89142 0 4
892status=0
893## END
894
895#### s[0] with string '12 34'
896
897s='12 34'
898echo '12 34' $(( s[0] )) $(( s[1] ))
899echo 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:
90912 34 0 1
910status=0
911## END
912
913# bash prints an error, but doesn't fail
914
915## BUG bash status: 0
916## BUG bash STDOUT:
917status=1
918## END