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