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-json: ""
269 ## OK osh status: 1
270
271 #### Bizarre recursive name evaluation - result of runtime parse/eval
272 foo=5
273 bar=foo
274 spam=bar
275 eggs=spam
276 echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
277 ## stdout: 6 6 6 6
278 ## OK osh stdout-json: ""
279 ## OK osh status: 1
280 ## N-I dash stdout-json: ""
281 ## N-I dash status: 2
282
283 #### nounset with arithmetic
284 set -o nounset
285 x=$(( y + 5 ))
286 echo "should not get here: x=${x:-<unset>}"
287 ## stdout-json: ""
288 ## status: 1
289 ## BUG dash/mksh/zsh stdout: should not get here: x=5
290 ## BUG dash/mksh/zsh status: 0
291
292 #### Integer Overflow
293 set -o nounset
294 echo $(( 999999 * 999999 * 999999 * 999999 ))
295 ## stdout: 999996000005999996000001
296 ## BUG dash/bash/zsh stdout: -1996229794797103359
297 ## BUG mksh stdout: -15640831
298
299 #### Invalid LValue
300 a=9
301 (( (a + 2) = 3 ))
302 echo $a
303 ## status: 2
304 ## stdout-json: ""
305 ## OK bash/mksh/zsh stdout: 9
306 ## OK bash/mksh/zsh status: 0
307 # dash doesn't implement assignment
308 ## N-I dash status: 2
309 ## N-I dash stdout-json: ""
310
311 #### Invalid LValue that looks like array
312 (( 1[2] = 3 ))
313 echo "status=$?"
314 ## status: 2
315 ## stdout-json: ""
316 ## OK bash stdout: status=1
317 ## OK bash status: 0
318 ## OK mksh/zsh stdout: status=2
319 ## OK mksh/zsh status: 0
320 ## N-I dash stdout: status=127
321 ## N-I dash status: 0
322
323 #### Invalid LValue: two sets of brackets
324 (( a[1][2] = 3 ))
325 echo "status=$?"
326 # shells treat this as a NON-fatal error
327 ## status: 2
328 ## stdout-json: ""
329 ## OK bash stdout: status=1
330 ## OK mksh/zsh stdout: status=2
331 ## OK bash/mksh/zsh status: 0
332 # dash doesn't implement assignment
333 ## N-I dash stdout: status=127
334 ## N-I dash status: 0
335
336 #### Operator Precedence
337 echo $(( 1 + 2*3 - 8/2 ))
338 ## stdout: 3
339
340 #### Exponentiation with **
341 echo $(( 3 ** 0 ))
342 echo $(( 3 ** 1 ))
343 echo $(( 3 ** 2 ))
344 ## STDOUT:
345 1
346 3
347 9
348 ## END
349 ## N-I dash stdout-json: ""
350 ## N-I dash status: 2
351 ## N-I mksh stdout-json: ""
352 ## N-I mksh status: 1
353
354 #### Exponentiation operator has buggy precedence
355 # NOTE: All shells agree on this, but R and Python give -9, which is more
356 # mathematically correct.
357 echo $(( -3 ** 2 ))
358 ## osh stdout: 9
359 ## N-I dash stdout-json: ""
360 ## N-I dash status: 2
361 ## N-I mksh stdout-json: ""
362 ## N-I mksh status: 1
363
364 #### Negative exponent
365 # bash explicitly disallows negative exponents!
366 echo $(( 2**-1 * 5 ))
367 ## stdout-json: ""
368 ## status: 1
369 ## OK zsh stdout: 2.5
370 ## OK zsh status: 0
371 ## N-I dash stdout-json: ""
372 ## N-I dash status: 2
373
374 #### Comment not allowed in the middle of multiline arithmetic
375 echo $((
376 1 +
377 2 + \
378 3
379 ))
380 echo $((
381 1 + 2 # not a comment
382 ))
383 (( a = 3 + 4 # comment
384 ))
385 echo [$a]
386 ## status: 1
387 ## STDOUT:
388 6
389 ## END
390 ## OK dash/osh status: 2
391 ## OK bash STDOUT:
392 6
393 []
394 ## END
395 ## OK bash status: 0
396
397 #### Can't add integer to indexed array
398 declare -a array=(1 2 3)
399 echo $((array + 5))
400 ## status: 1
401 ## stdout-json: ""
402 ## BUG bash status: 0
403 ## BUG bash STDOUT:
404 6
405 ## END
406 ## N-I dash status: 2
407
408 #### Can't add integer to associative array
409 typeset -A assoc
410 assoc[0]=42
411 echo $((assoc + 5))
412 ## status: 1
413 ## stdout-json: ""
414 ## BUG bash/mksh/zsh status: 0
415 ## BUG bash/mksh/zsh stdout: 47
416 ## BUG dash status: 0
417 ## BUG dash stdout: 5