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 # status: 1
84 # stdout-json: "foo\n"
85 # N-I bash status: 0
86 # N-I bash stdout-json: "foo\n5\n"
87 # N-I dash status: 2
88 # N-I dash stdout-json: ""
89 # N-I mksh status: 1
90 # N-I mksh stdout-json: ""
91 # N-I zsh status: 1
92 # N-I zsh stdout-json: ""
93
94 ### Newline in the middle of expression
95 echo $((1
96 + 2))
97 # stdout: 3
98
99 ### Ternary operator
100 a=1
101 b=2
102 echo $((a>b?5:10))
103 # stdout: 10
104
105 ### Preincrement
106 a=4
107 echo $((++a))
108 echo $a
109 # stdout-json: "5\n5\n"
110 # N-I dash status: 0
111 # N-I dash stdout-json: "4\n4\n"
112
113 ### Postincrement
114 a=4
115 echo $((a++))
116 echo $a
117 # stdout-json: "4\n5\n"
118 # N-I dash status: 2
119 # N-I dash stdout-json: ""
120
121 ### Increment undefined variables
122 (( undef1++ ))
123 (( ++undef2 ))
124 echo "[$undef1][$undef2]"
125 # stdout: [1][1]
126 # N-I dash stdout-json: "[][]\n"
127
128 ### Increment and decrement array
129 a=(5 6 7 8)
130 (( a[0]++, ++a[1], a[2]--, --a[3] ))
131 (( undef[0]++, ++undef[1], undef[2]--, --undef[3] ))
132 echo "${a[@]}" - "${undef[@]}"
133 # stdout: 6 7 6 7 - 1 1 -1 -1
134 # N-I dash stdout-json: ""
135 # N-I dash status: 2
136 # BUG zsh stdout: 5 6 7 8 -
137
138 ### Increment undefined variables with nounset
139 set -o nounset
140 (( undef1++ ))
141 (( ++undef2 ))
142 echo "[$undef1][$undef2]"
143 # stdout-json: ""
144 # status: 1
145 # OK dash status: 2
146 # BUG mksh/zsh status: 0
147 # BUG mksh/zsh stdout-json: "[1][1]\n"
148
149 ### Comma operator (borrowed from C)
150 a=1
151 b=2
152 echo $((a,(b+1)))
153 # stdout: 3
154 # N-I dash status: 2
155 # N-I dash stdout-json: ""
156
157 ### Augmented assignment
158 a=4
159 echo $((a+=1))
160 echo $a
161 # stdout-json: "5\n5\n"
162
163 ### Comparison Ops
164 echo $(( 1 == 1 ))
165 echo $(( 1 != 1 ))
166 echo $(( 1 < 1 ))
167 echo $(( 1 <= 1 ))
168 echo $(( 1 > 1 ))
169 echo $(( 1 >= 1 ))
170 # stdout-json: "1\n0\n0\n1\n0\n1\n"
171
172 ### Logical Ops
173 echo $((1 || 2))
174 echo $((1 && 2))
175 echo $((!(1 || 2)))
176 # stdout-json: "1\n1\n0\n"
177
178 ### Logical Ops Short Circuit
179 x=11
180 (( 1 || (x = 22) ))
181 echo $x
182 (( 0 || (x = 33) ))
183 echo $x
184 (( 0 && (x = 44) ))
185 echo $x
186 (( 1 && (x = 55) ))
187 echo $x
188 # stdout-json: "11\n33\n33\n55\n"
189 # N-I dash stdout-json: "11\n11\n11\n11\n"
190
191 ### Bitwise ops
192 echo $((1|2))
193 echo $((1&2))
194 echo $((1^2))
195 echo $((~(1|2)))
196 # stdout-json: "3\n0\n3\n-4\n"
197
198 ### Unary minus and plus
199 a=1
200 b=3
201 echo $((- a + + b))
202 # stdout-json: "2\n"
203
204 ### No floating point
205 echo $((1 + 2.3))
206 # status: 2
207 # OK bash/mksh status: 1
208 # BUG zsh status: 0
209
210 ### Array indexing in arith
211 # zsh does 1-based indexing!
212 array=(1 2 3 4)
213 echo $((array[1] + array[2]*3))
214 # stdout: 11
215 # OK zsh stdout: 7
216 # N-I dash status: 2
217 # N-I dash stdout-json: ""
218
219 ### Constants in base 36
220 echo $((36#a))-$((36#z))
221 # stdout: 10-35
222 # N-I dash stdout-json: ""
223 # N-I dash status: 2
224
225 ### Constants in bases 2 to 64
226 # This is a truly bizarre syntax. Oh it comes from zsh... which allows 36.
227 echo $((64#a))-$((64#z)), $((64#A))-$((64#Z)), $((64#@)), $(( 64#_ ))
228 # stdout: 10-35, 36-61, 62, 63
229 # N-I dash stdout-json: ""
230 # N-I dash status: 2
231 # N-I mksh/zsh stdout-json: ""
232 # N-I mksh/zsh status: 1
233
234 ### Dynamic base constants
235 base=16
236 echo $(( ${base}#a ))
237 # stdout: 10
238 # N-I dash stdout-json: ""
239 # N-I dash status: 2
240
241 ### Octal constant
242 echo $(( 011 ))
243 # stdout: 9
244 # N-I mksh/zsh stdout: 11
245
246 ### Dynamic octal constant
247 zero=0
248 echo $(( ${zero}11 ))
249 # stdout: 9
250 # N-I mksh/zsh stdout: 11
251
252 ### Dynamic hex constants
253 zero=0
254 echo $(( ${zero}xAB ))
255 # stdout: 171
256
257 ### Dynamic var names - result of runtime parse/eval
258 foo=5
259 x=oo
260 echo $(( foo + f$x + 1 ))
261 # stdout: 11
262
263 ### Bizarre recursive name evaluation - result of runtime parse/eval
264 foo=5
265 bar=foo
266 spam=bar
267 eggs=spam
268 echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
269 # stdout: 6 6 6 6
270 # N-I dash stdout-json: ""
271 # N-I dash status: 2
272
273 ### nounset with arithmetic
274 set -o nounset
275 x=$(( y + 5 ))
276 echo "should not get here: x=${x:-<unset>}"
277 # stdout-json: ""
278 # status: 1
279 # BUG dash/mksh/zsh stdout: should not get here: x=5
280 # BUG dash/mksh/zsh status: 0
281
282 ### Integer Overflow
283 set -o nounset
284 echo $(( 999999 * 999999 * 999999 * 999999 ))
285 # stdout: 999996000005999996000001
286 # BUG dash/bash/zsh stdout: -1996229794797103359
287 # BUG mksh stdout: -15640831
288
289 ### Invalid LValue
290 a=9
291 (( (a + 2) = 3 ))
292 echo $a
293 # status: 2
294 # stdout-json: ""
295 # OK bash/mksh/zsh stdout: 9
296 # OK bash/mksh/zsh status: 0
297 # dash doesn't implement assignment
298 # N-I dash status: 2
299 # N-I dash stdout-json: ""
300
301 ### Invalid LValue that looks like array
302 (( 1[2] = 3 ))
303 echo "status=$?"
304 # status: 2
305 # stdout-json: ""
306 # OK bash stdout: status=1
307 # OK bash status: 0
308 # OK mksh/zsh stdout: status=2
309 # OK mksh/zsh status: 0
310 # N-I dash stdout: status=127
311 # N-I dash status: 0
312
313 ### Invalid LValue: two sets of brackets
314 (( a[1][2] = 3 ))
315 echo "status=$?"
316 # shells treat this as a NON-fatal error
317 # status: 2
318 # stdout-json: ""
319 # OK bash stdout: status=1
320 # OK mksh/zsh stdout: status=2
321 # OK bash/mksh/zsh status: 0
322 # dash doesn't implement assignment
323 # N-I dash stdout: status=127
324 # N-I dash status: 0
325