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 echo $(($j + 1))
36 ## stdout: 1
37
38 #### BracedVarSub within ArithSub
39 echo $((${j:-5} + 1))
40 ## stdout: 6
41
42 #### Arith word part
43 foo=1; echo $((foo+1))bar$(($foo+1))
44 ## stdout: 2bar2
45
46 #### Arith sub with word parts
47 # Making 13 from two different kinds of sub. Geez.
48 echo $((1 + $(echo 1)${undefined:-3}))
49 ## stdout: 14
50
51 #### Constant with quotes like '1'
52 # NOTE: Compare with [[. That is a COMMAND level expression, while this is a
53 # WORD level expression.
54 echo $(('1' + 2))
55 ## status: 0
56 ## N-I bash/zsh status: 1
57 ## N-I dash status: 2
58
59 #### Arith sub within arith sub
60 # This is unnecessary but works in all shells.
61 echo $((1 + $((2 + 3)) + 4))
62 ## stdout: 10
63
64 #### Backticks within arith sub
65 # This is unnecessary but works in all shells.
66 echo $((`echo 1` + 2))
67 ## stdout: 3
68
69 #### Invalid string to int
70 # bash, mksh, and zsh all treat strings that don't look like numbers as zero.
71 s=foo
72 echo $((s+5))
73 ## OK dash stdout-json: ""
74 ## OK dash status: 2
75 ## OK bash/mksh/zsh/osh stdout: 5
76 ## OK bash/mksh/zsh/osh status: 0
77
78 #### Invalid string to int with strict-arith
79 set -o strict-arith || true
80 s=foo
81 echo $s
82 echo $((s+5))
83 echo 'should not get here'
84 ## status: 1
85 ## stdout-json: "foo\n"
86 ## N-I bash status: 0
87 ## N-I bash STDOUT:
88 foo
89 5
90 should not get here
91 ## END
92 ## N-I dash status: 2
93 ## N-I dash stdout-json: ""
94 ## N-I mksh status: 1
95 ## N-I mksh stdout-json: ""
96 ## N-I zsh status: 1
97 ## N-I zsh stdout-json: ""
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 (( undef1++ ))
128 (( ++undef2 ))
129 echo "[$undef1][$undef2]"
130 ## stdout: [1][1]
131 ## N-I dash stdout-json: "[][]\n"
132
133 #### Increment and decrement array
134 a=(5 6 7 8)
135 (( a[0]++, ++a[1], a[2]--, --a[3] ))
136 (( undef[0]++, ++undef[1], undef[2]--, --undef[3] ))
137 echo "${a[@]}" - "${undef[@]}"
138 ## stdout: 6 7 6 7 - 1 1 -1 -1
139 ## N-I dash stdout-json: ""
140 ## N-I dash status: 2
141 ## BUG zsh stdout: 5 6 7 8 -
142
143 #### Increment undefined variables with nounset
144 set -o nounset
145 (( undef1++ ))
146 (( ++undef2 ))
147 echo "[$undef1][$undef2]"
148 ## stdout-json: ""
149 ## status: 1
150 ## OK dash status: 2
151 ## BUG mksh/zsh status: 0
152 ## BUG mksh/zsh stdout-json: "[1][1]\n"
153
154 #### Comma operator (borrowed from C)
155 a=1
156 b=2
157 echo $((a,(b+1)))
158 ## stdout: 3
159 ## N-I dash status: 2
160 ## N-I dash stdout-json: ""
161
162 #### Augmented assignment
163 a=4
164 echo $((a+=1))
165 echo $a
166 ## stdout-json: "5\n5\n"
167
168 #### Comparison Ops
169 echo $(( 1 == 1 ))
170 echo $(( 1 != 1 ))
171 echo $(( 1 < 1 ))
172 echo $(( 1 <= 1 ))
173 echo $(( 1 > 1 ))
174 echo $(( 1 >= 1 ))
175 ## stdout-json: "1\n0\n0\n1\n0\n1\n"
176
177 #### Logical Ops
178 echo $((1 || 2))
179 echo $((1 && 2))
180 echo $((!(1 || 2)))
181 ## stdout-json: "1\n1\n0\n"
182
183 #### Logical Ops Short Circuit
184 x=11
185 (( 1 || (x = 22) ))
186 echo $x
187 (( 0 || (x = 33) ))
188 echo $x
189 (( 0 && (x = 44) ))
190 echo $x
191 (( 1 && (x = 55) ))
192 echo $x
193 ## stdout-json: "11\n33\n33\n55\n"
194 ## N-I dash stdout-json: "11\n11\n11\n11\n"
195
196 #### Bitwise ops
197 echo $((1|2))
198 echo $((1&2))
199 echo $((1^2))
200 echo $((~(1|2)))
201 ## stdout-json: "3\n0\n3\n-4\n"
202
203 #### Unary minus and plus
204 a=1
205 b=3
206 echo $((- a + + b))
207 ## stdout-json: "2\n"
208
209 #### No floating point
210 echo $((1 + 2.3))
211 ## status: 2
212 ## OK bash/mksh status: 1
213 ## BUG zsh status: 0
214
215 #### Array indexing in arith
216 # zsh does 1-based indexing!
217 array=(1 2 3 4)
218 echo $((array[1] + array[2]*3))
219 ## stdout: 11
220 ## OK zsh stdout: 7
221 ## N-I dash status: 2
222 ## N-I dash stdout-json: ""
223
224 #### Constants in base 36
225 echo $((36#a))-$((36#z))
226 ## stdout: 10-35
227 ## N-I dash stdout-json: ""
228 ## N-I dash status: 2
229
230 #### Constants in bases 2 to 64
231 # This is a truly bizarre syntax. Oh it comes from zsh... which allows 36.
232 echo $((64#a))-$((64#z)), $((64#A))-$((64#Z)), $((64#@)), $(( 64#_ ))
233 ## stdout: 10-35, 36-61, 62, 63
234 ## N-I dash stdout-json: ""
235 ## N-I dash status: 2
236 ## N-I mksh/zsh stdout-json: ""
237 ## N-I mksh/zsh status: 1
238
239 #### Dynamic base constants
240 base=16
241 echo $(( ${base}#a ))
242 ## stdout: 10
243 ## N-I dash stdout-json: ""
244 ## N-I dash status: 2
245
246 #### Octal constant
247 echo $(( 011 ))
248 ## stdout: 9
249 ## N-I mksh/zsh stdout: 11
250
251 #### Dynamic octal constant
252 zero=0
253 echo $(( ${zero}11 ))
254 ## stdout: 9
255 ## N-I mksh/zsh stdout: 11
256
257 #### Dynamic hex constants
258 zero=0
259 echo $(( ${zero}xAB ))
260 ## stdout: 171
261
262 #### Dynamic var names - result of runtime parse/eval
263 foo=5
264 x=oo
265 echo $(( foo + f$x + 1 ))
266 ## stdout: 11
267 ## OK osh stdout: 6
268
269 #### Bizarre recursive name evaluation - result of runtime parse/eval
270 foo=5
271 bar=foo
272 spam=bar
273 eggs=spam
274 echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
275 ## stdout: 6 6 6 6
276 ## OK osh stdout: 6 1 1 1
277 ## N-I dash stdout-json: ""
278 ## N-I dash status: 2
279
280 #### nounset with arithmetic
281 set -o nounset
282 x=$(( y + 5 ))
283 echo "should not get here: x=${x:-<unset>}"
284 ## stdout-json: ""
285 ## status: 1
286 ## BUG dash/mksh/zsh stdout: should not get here: x=5
287 ## BUG dash/mksh/zsh status: 0
288
289 #### Integer Overflow
290 set -o nounset
291 echo $(( 999999 * 999999 * 999999 * 999999 ))
292 ## stdout: 999996000005999996000001
293 ## BUG dash/bash/zsh stdout: -1996229794797103359
294 ## BUG mksh stdout: -15640831
295
296 #### Invalid LValue
297 a=9
298 (( (a + 2) = 3 ))
299 echo $a
300 ## status: 2
301 ## stdout-json: ""
302 ## OK bash/mksh/zsh stdout: 9
303 ## OK bash/mksh/zsh status: 0
304 # dash doesn't implement assignment
305 ## N-I dash status: 2
306 ## N-I dash stdout-json: ""
307
308 #### Invalid LValue that looks like array
309 (( 1[2] = 3 ))
310 echo "status=$?"
311 ## status: 2
312 ## stdout-json: ""
313 ## OK bash stdout: status=1
314 ## OK bash status: 0
315 ## OK mksh/zsh stdout: status=2
316 ## OK mksh/zsh status: 0
317 ## N-I dash stdout: status=127
318 ## N-I dash status: 0
319
320 #### Invalid LValue: two sets of brackets
321 (( a[1][2] = 3 ))
322 echo "status=$?"
323 # shells treat this as a NON-fatal error
324 ## status: 2
325 ## stdout-json: ""
326 ## OK bash stdout: status=1
327 ## OK mksh/zsh stdout: status=2
328 ## OK bash/mksh/zsh status: 0
329 # dash doesn't implement assignment
330 ## N-I dash stdout: status=127
331 ## N-I dash status: 0
332
333 #### Operator Precedence
334 echo $(( 1 + 2*3 - 8/2 ))
335 ## stdout: 3
336
337 #### Exponentiation with **
338 echo $(( 3 ** 0 ))
339 echo $(( 3 ** 1 ))
340 echo $(( 3 ** 2 ))
341 ## STDOUT:
342 1
343 3
344 9
345 ## END
346 ## N-I dash stdout-json: ""
347 ## N-I dash status: 2
348 ## N-I mksh stdout-json: ""
349 ## N-I mksh status: 1
350
351 #### Exponentiation operator has buggy precedence
352 # NOTE: All shells agree on this, but R and Python give -9, which is more
353 # mathematically correct.
354 echo $(( -3 ** 2 ))
355 ## osh stdout: 9
356 ## N-I dash stdout-json: ""
357 ## N-I dash status: 2
358 ## N-I mksh stdout-json: ""
359 ## N-I mksh status: 1
360
361 #### Negative exponent
362 # bash explicitly disallows negative exponents!
363 echo $(( 2**-1 * 5 ))
364 ## stdout-json: ""
365 ## status: 1
366 ## OK zsh stdout: 2.5
367 ## OK zsh status: 0
368 ## N-I dash stdout-json: ""
369 ## N-I dash status: 2
370
371 #### Comment not allowed in the middle of multiline arithmetic
372 echo $((
373 1 +
374 2 + \
375 3
376 ))
377 echo $((
378 1 + 2 # not a comment
379 ))
380 (( a = 3 + 4 # comment
381 ))
382 echo [$a]
383 ## status: 1
384 ## STDOUT:
385 6
386 ## END
387 ## OK dash/osh status: 2
388 ## OK bash STDOUT:
389 6
390 []
391 ## END
392 ## OK bash status: 0