1 #!/usr/bin/env bash
2 #
3 # Test combination of var ops.
4 #
5 # NOTE: There are also slice tests in {array,arith-context}.test.sh.
6
7 #### String length
8 v=foo
9 echo ${#v}
10 ## stdout: 3
11
12 #### Unicode string length (UTF-8)
13 v=$'_\u03bc_'
14 echo ${#v}
15 ## stdout: 3
16 ## N-I dash stdout: 9
17 ## N-I mksh stdout: 4
18
19 #### Unicode string length (spec/testdata/utf8-chars.txt)
20 v=$(cat spec/testdata/utf8-chars.txt)
21 echo ${#v}
22 ## stdout: 7
23 ## N-I dash stdout: 13
24 ## N-I mksh stdout: 13
25
26 #### String length with incomplete utf-8
27 for num_bytes in 0 1 2 3 4 5 6 7 8 9 10 11 12 13; do
28 s=$(head -c $num_bytes spec/testdata/utf8-chars.txt)
29 echo ${#s}
30 done
31 ## STDOUT:
32 0
33 1
34 2
35 -1
36 3
37 4
38 -1
39 -1
40 5
41 6
42 -1
43 -1
44 -1
45 7
46 ## END
47 ## STDERR:
48 osh warning: Incomplete UTF-8 character
49 osh warning: Incomplete UTF-8 character
50 osh warning: Incomplete UTF-8 character
51 osh warning: Incomplete UTF-8 character
52 osh warning: Incomplete UTF-8 character
53 osh warning: Incomplete UTF-8 character
54 ## END
55 # zsh behavior actually matches bash!
56 ## BUG bash/zsh stderr-json: ""
57 ## BUG bash/zsh STDOUT:
58 0
59 1
60 2
61 3
62 3
63 4
64 5
65 6
66 5
67 6
68 7
69 8
70 9
71 7
72 ## END
73 ## BUG dash/mksh stderr-json: ""
74 ## N-I dash/mksh STDOUT:
75 0
76 1
77 2
78 3
79 4
80 5
81 6
82 7
83 8
84 9
85 10
86 11
87 12
88 13
89 ## END
90
91 #### String length with invalid utf-8 continuation bytes
92 for num_bytes in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14; do
93 s=$(head -c $num_bytes spec/testdata/utf8-chars.txt)$(echo -e "\xFF")
94 echo ${#s}
95 done
96 ## STDOUT:
97 -1
98 -1
99 -1
100 -1
101 -1
102 -1
103 -1
104 -1
105 -1
106 -1
107 -1
108 -1
109 -1
110 -1
111 -1
112 ## END
113 ## STDERR:
114 osh warning: Invalid start of UTF-8 character
115 osh warning: Invalid start of UTF-8 character
116 osh warning: Invalid start of UTF-8 character
117 osh warning: Invalid UTF-8 continuation byte
118 osh warning: Invalid start of UTF-8 character
119 osh warning: Invalid start of UTF-8 character
120 osh warning: Invalid UTF-8 continuation byte
121 osh warning: Invalid UTF-8 continuation byte
122 osh warning: Invalid start of UTF-8 character
123 osh warning: Invalid start of UTF-8 character
124 osh warning: Invalid UTF-8 continuation byte
125 osh warning: Invalid UTF-8 continuation byte
126 osh warning: Invalid UTF-8 continuation byte
127 osh warning: Invalid start of UTF-8 character
128 osh warning: Invalid start of UTF-8 character
129 ## END
130 ## BUG bash/zsh stderr-json: ""
131 ## BUG bash/zsh STDOUT:
132 1
133 2
134 3
135 4
136 4
137 5
138 6
139 7
140 6
141 7
142 8
143 9
144 10
145 8
146 8
147 ## N-I dash stderr-json: ""
148 ## N-I dash STDOUT:
149 7
150 8
151 9
152 10
153 11
154 12
155 13
156 14
157 15
158 16
159 17
160 18
161 19
162 20
163 20
164 ## END
165 ## N-I mksh stderr-json: ""
166 ## N-I mksh STDOUT:
167 1
168 2
169 3
170 4
171 5
172 6
173 7
174 8
175 9
176 10
177 11
178 12
179 13
180 14
181 14
182 ## END
183
184 #### Length of undefined variable
185 echo ${#undef}
186 ## stdout: 0
187
188 #### Length of undefined variable with nounset
189 set -o nounset
190 echo ${#undef}
191 ## status: 1
192 ## OK dash status: 2
193
194 #### Cannot take length of substring slice
195 # These are runtime errors, but we could make them parse time errors.
196 v=abcde
197 echo ${#v:1:3}
198 ## status: 1
199 ## OK osh status: 2
200 ## N-I dash status: 0
201 ## N-I dash stdout: 5
202 # zsh actually implements this!
203 ## OK zsh stdout: 3
204 ## OK zsh status: 0
205
206 #### Pattern replacement
207 v=abcde
208 echo ${v/c*/XX}
209 ## stdout: abXX
210 ## N-I dash status: 2
211 ## N-I dash stdout-json: ""
212
213 #### Pattern replacement on unset variable
214 echo -${v/x/y}-
215 echo status=$?
216 set -o nounset # make sure this fails
217 echo -${v/x/y}-
218 ## STDOUT:
219 --
220 status=0
221 ## BUG mksh STDOUT:
222 # patsub disrespects nounset!
223 --
224 status=0
225 --
226 ## status: 1
227 ## BUG mksh status: 0
228 ## N-I dash status: 2
229 ## N-I dash stdout-json: ""
230
231 #### Global Pattern replacement with /
232 s=xx_xx_xx
233 echo ${s/xx?/yy_} ${s//xx?/yy_}
234 ## stdout: yy_xx_xx yy_yy_xx
235 ## N-I dash status: 2
236 ## N-I dash stdout-json: ""
237
238 #### Left Anchored Pattern replacement with #
239 s=xx_xx_xx
240 echo ${s/?xx/_yy} ${s/#?xx/_yy}
241 ## stdout: xx_yy_xx xx_xx_xx
242 ## N-I dash status: 2
243 ## N-I dash stdout-json: ""
244
245 #### Right Anchored Pattern replacement with %
246 s=xx_xx_xx
247 echo ${s/?xx/_yy} ${s/%?xx/_yy}
248 ## stdout: xx_yy_xx xx_xx_yy
249 ## N-I dash status: 2
250 ## N-I dash stdout-json: ""
251
252 #### Replace fixed strings
253 s=xx_xx
254 echo ${s/xx/yy} ${s//xx/yy} ${s/#xx/yy} ${s/%xx/yy}
255 ## stdout: yy_xx yy_yy yy_xx xx_yy
256 ## N-I dash status: 2
257 ## N-I dash stdout-json: ""
258
259 #### Replace is longest match
260 # If it were shortest, then you would just replace the first <html>
261 s='begin <html></html> end'
262 echo ${s/<*>/[]}
263 ## stdout: begin [] end
264 ## N-I dash status: 2
265 ## N-I dash stdout-json: ""
266
267 #### Replace char class
268 s=xx_xx_xx
269 echo ${s//[[:alpha:]]/y} ${s//[^[:alpha:]]/-}
270 ## stdout: yy_yy_yy xx-xx-xx
271 ## N-I mksh stdout: xx_xx_xx xx_xx_xx
272 ## N-I dash status: 2
273 ## N-I dash stdout-json: ""
274
275 #### Replace hard glob
276 s='aa*bb+cc'
277 echo ${s//\**+/__} # Literal *, then any sequence of characters, then literal +
278 ## stdout: aa__cc
279 ## N-I dash status: 2
280 ## N-I dash stdout-json: ""
281
282 #### Pattern replacement ${v/} is not valid
283 v=abcde
284 echo -${v/}-
285 echo status=$?
286 ## status: 2
287 ## stdout-json: ""
288 ## N-I dash status: 2
289 ## N-I dash stdout-json: ""
290 ## BUG bash/mksh/zsh status: 0
291 ## BUG bash/mksh/zsh stdout-json: "-abcde-\nstatus=0\n"
292
293 #### Pattern replacement ${v//} is not valid
294 v='a/b/c'
295 echo -${v//}-
296 echo status=$?
297 ## status: 2
298 ## stdout-json: ""
299 ## N-I dash status: 2
300 ## N-I dash stdout-json: ""
301 ## BUG bash/mksh/zsh status: 0
302 ## BUG bash/mksh/zsh stdout-json: "-a/b/c-\nstatus=0\n"
303
304 #### ${v/a} is the same as ${v/a/} -- no replacement string
305 v='aabb'
306 echo ${v/a}
307 echo status=$?
308 ## stdout-json: "abb\nstatus=0\n"
309 ## N-I dash stdout-json: ""
310 ## N-I dash status: 2
311
312 #### String slice
313 foo=abcdefg
314 echo ${foo:1:3}
315 ## STDOUT:
316 bcd
317 ## END
318 ## N-I dash status: 2
319 ## N-I dash stdout-json: ""
320
321 #### Out of range string slice: begin
322 # out of range begin doesn't raise error in bash, but in mksh it skips the
323 # whole thing!
324 foo=abcdefg
325 echo _${foo:100:3}
326 echo $?
327 ## STDOUT:
328 _
329 0
330 ## END
331 ## BUG mksh stdout-json: "\n0\n"
332 ## N-I dash status: 2
333 ## N-I dash stdout-json: ""
334
335 #### Out of range string slice: length
336 # OK in both bash and mksh
337 foo=abcdefg
338 echo _${foo:3:100}
339 echo $?
340 ## STDOUT:
341 _defg
342 0
343 ## END
344 ## BUG mksh stdout-json: "_defg\n0\n"
345 ## N-I dash status: 2
346 ## N-I dash stdout-json: ""
347
348 #### String slice: negative begin
349 foo=abcdefg
350 echo ${foo: -4:3}
351 ## OK osh stdout:
352 ## stdout: def
353 ## N-I dash status: 2
354 ## N-I dash stdout-json: ""
355
356 #### String slice: negative second arg is position, not length
357 foo=abcdefg
358 echo ${foo:3:-1} ${foo: 3: -2} ${foo:3 :-3 }
359 ## OK osh stdout:
360 ## stdout: def de d
361 ## BUG mksh stdout: defg defg defg
362 ## N-I dash status: 2
363 ## N-I dash stdout-json: ""
364
365 #### strict-word-eval with string slice
366 set -o strict-word-eval || true
367 echo slice
368 s='abc'
369 echo -${s: -2}-
370 ## stdout-json: "slice\n"
371 ## status: 1
372 ## N-I bash status: 0
373 ## N-I bash stdout-json: "slice\n-bc-\n"
374 ## N-I dash status: 2
375 ## N-I dash stdout-json: ""
376 ## N-I mksh/zsh status: 1
377 ## N-I mksh/zsh stdout-json: ""
378
379 #### String slice with math
380 # I think this is the $(()) language inside?
381 i=1
382 foo=abcdefg
383 echo ${foo: i+4-2 : i + 2}
384 ## stdout: def
385 ## N-I dash status: 2
386 ## N-I dash stdout-json: ""
387
388 #### Slice undefined
389 echo -${undef:1:2}-
390 set -o nounset
391 echo -${undef:1:2}-
392 echo -done-
393 ## STDOUT:
394 --
395 ## END
396 ## status: 1
397 # mksh doesn't respect nounset!
398 ## BUG mksh status: 0
399 ## BUG mksh STDOUT:
400 --
401 --
402 -done-
403 ## END
404 ## N-I dash status: 2
405 ## N-I dash stdout-json: ""
406
407 #### Slice UTF-8 String
408 # mksh slices by bytes.
409 foo='--μ--'
410 echo ${foo:1:3}
411 ## stdout: -μ-
412 ## BUG mksh stdout: -μ
413 ## N-I dash status: 2
414 ## N-I dash stdout-json: ""
415
416 #### Slice string with invalid UTF-8 results in empty string and warning
417 s=$(echo -e "\xFF")bcdef
418 echo -${s:1:3}-
419 ## status: 0
420 ## stdout-json: "--\n"
421 ## stderr-json: "osh warning: Invalid start of UTF-8 character\n"
422 ## BUG bash/mksh/zsh status: 0
423 ## BUG bash/mksh/zsh stdout-json: "-bcd-\n"
424 ## BUG bash/mksh/zsh stderr-json: ""
425 ## N-I dash status: 2
426 ## N-I dash stdout-json: ""
427 ## N-I dash stderr-json: "_tmp/spec-bin/dash: 2: Bad substitution\n"
428
429
430 #### Slice string with invalid UTF-8 with strict-word-eval
431 set -o strict-word-eval || true
432 echo slice
433 s=$(echo -e "\xFF")bcdef
434 echo -${s:1:3}-
435 ## status: 1
436 ## stdout-json: "slice\n"
437 ## N-I mksh/zsh status: 1
438 ## N-I mksh/zsh stdout-json: ""
439 ## N-I dash status: 2
440 ## N-I dash stdout-json: ""
441 ## N-I bash status: 0
442 ## N-I bash stdout-json: "slice\n-bcd-\n"
443
444 #### Lower Case with , and ,,
445 x='ABC DEF'
446 echo ${x,}
447 echo ${x,,}
448 ## STDOUT:
449 aBC DEF
450 abc def
451 ## END
452 ## N-I dash/mksh/zsh stdout-json: ""
453 ## N-I dash status: 2
454 ## N-I mksh/zsh status: 1
455
456
457 #### Upper Case with ^ and ^^
458 x='abc def'
459 echo ${x^}
460 echo ${x^^}
461 ## STDOUT:
462 Abc def
463 ABC DEF
464 ## END
465 ## N-I dash/mksh/zsh stdout-json: ""
466 ## N-I dash status: 2
467 ## N-I mksh/zsh status: 1
468
469 #### Lower Case with constant string (VERY WEIRD)
470 x='AAA ABC DEF'
471 echo ${x,A}
472 echo ${x,,A} # replaces every A only?
473 ## STDOUT:
474 aAA ABC DEF
475 aaa aBC DEF
476 ## END
477 ## N-I dash/mksh/zsh stdout-json: ""
478 ## N-I dash status: 2
479 ## N-I mksh/zsh status: 1
480
481 #### Lower Case glob
482 x='ABC DEF'
483 echo ${x,[d-f]}
484 echo ${x,,[d-f]} # This seems buggy, it doesn't include F?
485 ## STDOUT:
486 ABC DEF
487 ABC deF
488 ## END
489 ## N-I dash/mksh/zsh stdout-json: ""
490 ## N-I dash status: 2
491 ## N-I mksh/zsh status: 1