1 # Test ${x/pat*/replace}
2 # TODO: can add $BUSYBOX_ASH
3
4 ## oils_failures_allowed: 2
5 ## compare_shells: bash mksh zsh
6
7 #### Pattern replacement
8 v=abcde
9 echo ${v/c*/XX}
10 ## stdout: abXX
11
12 #### Pattern replacement on unset variable
13 echo -${v/x/y}-
14 echo status=$?
15 set -o nounset # make sure this fails
16 echo -${v/x/y}-
17 ## STDOUT:
18 --
19 status=0
20 ## BUG mksh STDOUT:
21 # patsub disrespects nounset!
22 --
23 status=0
24 --
25 ## status: 1
26 ## OK ash status: 2
27 ## BUG mksh status: 0
28
29 #### Global Pattern replacement with /
30 s=xx_xx_xx
31 echo ${s/xx?/yy_} ${s//xx?/yy_}
32 ## stdout: yy_xx_xx yy_yy_xx
33
34 #### Left Anchored Pattern replacement with #
35 s=xx_xx_xx
36 echo ${s/?xx/_yy} ${s/#?xx/_yy}
37 ## stdout: xx_yy_xx xx_xx_xx
38
39 #### Right Anchored Pattern replacement with %
40 s=xx_xx_xx
41 echo ${s/?xx/_yy} ${s/%?xx/_yy}
42 ## STDOUT:
43 xx_yy_xx xx_xx_yy
44 ## END
45 ## BUG ash STDOUT:
46 xx_yy_xx xx_xx_xx
47 ## END
48
49 #### Replace fixed strings
50 s=xx_xx
51 echo ${s/xx/yy} ${s//xx/yy} ${s/#xx/yy} ${s/%xx/yy}
52 ## STDOUT:
53 yy_xx yy_yy yy_xx xx_yy
54 ## END
55 ## BUG ash STDOUT:
56 yy_xx yy_yy xx_xx xx_xx
57 ## END
58
59 #### Replace is longest match
60 # If it were shortest, then you would just replace the first <html>
61 s='begin <html></html> end'
62 echo ${s/<*>/[]}
63 ## stdout: begin [] end
64
65 #### Replace char class
66 s=xx_xx_xx
67 echo ${s//[[:alpha:]]/y} ${s//[^[:alpha:]]/-}
68 ## stdout: yy_yy_yy xx-xx-xx
69 ## N-I mksh stdout: xx_xx_xx xx_xx_xx
70
71 #### Replace hard glob
72 s='aa*bb+cc'
73 echo ${s//\**+/__} # Literal *, then any sequence of characters, then literal +
74 ## stdout: aa__cc
75
76 #### ${v/} is empty search and replacement
77 v=abcde
78 echo -${v/}-
79 echo status=$?
80 ## status: 0
81 ## STDOUT:
82 -abcde-
83 status=0
84 ## END
85 ## BUG ash STDOUT:
86 -abcde -
87 status=0
88 ## END
89
90 #### ${v//} is empty search and replacement
91 v='a/b/c'
92 echo -${v//}-
93 echo status=$?
94 ## status: 0
95 ## STDOUT:
96 -a/b/c-
97 status=0
98 ## END
99 ## BUG ash STDOUT:
100 -a/b/c -
101 status=0
102 ## END
103
104 #### Confusing unquoted slash matches bash (and ash)
105 x='/_/'
106 echo ${x////c}
107
108 echo ${x//'/'/c}
109
110 ## STDOUT:
111 c_c
112 c_c
113 ## END
114 ## BUG mksh STDOUT:
115 /_/
116 c_c
117 ## END
118 ## BUG zsh STDOUT:
119 /c//c_/c/
120 /_/
121 ## END
122 ## BUG ash STDOUT:
123 c_c
124 /_/ /c
125 ## END
126
127 #### Synthesized ${x///} bug (similar to above)
128
129 # found via test/parse-errors.sh
130
131 x='slash / brace } hi'
132 echo 'ambiguous:' ${x///}
133
134 echo 'quoted: ' ${x//'/'}
135
136 # Wow we have all combination here -- TERRIBLE
137
138 ## STDOUT:
139 ambiguous: slash brace } hi
140 quoted: slash brace } hi
141 ## END
142 ## BUG mksh STDOUT:
143 ambiguous: slash / brace } hi
144 quoted: slash brace } hi
145 ## END
146 ## BUG zsh STDOUT:
147 ambiguous: slash / brace } hi
148 quoted: slash / brace } hi
149 ## END
150 ## BUG ash STDOUT:
151 ambiguous: slash brace } hi
152 quoted: slash / brace } hi
153 ## END
154
155
156 #### ${v/a} is the same as ${v/a/} -- no replacement string
157 v='aabb'
158 echo ${v/a}
159 echo status=$?
160 ## STDOUT:
161 abb
162 status=0
163 ## END
164
165 #### Replacement with special chars (bug fix)
166 v=xx
167 echo ${v/x/"?"}
168 ## stdout: ?x
169
170 #### Replace backslash
171 v='[\f]'
172 x='\f'
173 echo ${v/"$x"/_}
174
175 # mksh and zsh differ on this case, but this is consistent with the fact that
176 # \f as a glob means 'f', not '\f'. TODO: Warn that it's a bad glob?
177 # The canonical form is 'f'.
178 echo ${v/$x/_}
179
180 echo ${v/\f/_}
181 echo ${v/\\f/_}
182 ## STDOUT:
183 [_]
184 [\_]
185 [\_]
186 [_]
187 ## END
188 ## BUG mksh/zsh STDOUT:
189 [_]
190 [_]
191 [\_]
192 [_]
193 ## END
194
195 #### Replace right ]
196 v='--]--'
197 x=']'
198 echo ${v/"$x"/_}
199 echo ${v/$x/_}
200 ## STDOUT:
201 --_--
202 --_--
203 ## END
204
205 #### Substitute glob characters in pattern, quoted and unquoted
206
207 # INFINITE LOOP in ash!
208 case $SH in ash) exit ;; esac
209
210 g='*'
211 v='a*b'
212 echo ${v//"$g"/-}
213 echo ${v//$g/-}
214 ## STDOUT:
215 a-b
216 -
217 ## END
218 ## BUG zsh STDOUT:
219 a-b
220 a-b
221 ## END
222
223 #### Substitute one unicode character (UTF-8)
224 export LANG='en_US.UTF-8'
225
226 s='_μ_ and _μ_'
227
228 # ? should match one char
229
230 echo ${s//_?_/foo} # all
231 echo ${s/#_?_/foo} # left
232 echo ${s/%_?_/foo} # right
233
234 ## STDOUT:
235 foo and foo
236 foo and _μ_
237 _μ_ and foo
238 ## END
239 ## BUG mksh STDOUT:
240 _μ_ and _μ_
241 _μ_ and _μ_
242 _μ_ and _μ_
243 ## END
244
245 #### When LC_ALL=C, pattern ? doesn't match multibyte character
246 export LC_ALL='C'
247
248 s='_μ_ and _μ_'
249
250 # ? should match one char
251
252 echo ${s//_?_/foo} # all
253 echo ${s/#_?_/foo} # left
254 echo ${s/%_?_/foo} # right
255 echo
256
257 a='_x_ and _y_'
258
259 echo ${a//_?_/foo} # all
260 echo ${a/#_?_/foo} # left
261 echo ${a/%_?_/foo} # right
262
263 ## STDOUT:
264 _μ_ and _μ_
265 _μ_ and _μ_
266 _μ_ and _μ_
267
268 foo and foo
269 foo and _y_
270 _x_ and foo
271 ## END
272
273 #### ${x/^} regression
274 x=abc
275 echo ${x/^}
276 echo ${x/!}
277
278 y=^^^
279 echo ${y/^}
280 echo ${y/!}
281
282 z=!!!
283 echo ${z/^}
284 echo ${z/!}
285
286 s=a^b!c
287 echo ${s/a^}
288 echo ${s/b!}
289
290 ## STDOUT:
291 abc
292 abc
293 ^^
294 ^^^
295 !!!
296 !!
297 b!c
298 a^c
299 ## END
300
301 #### \(\) in pattern (regression)
302
303 # Not extended globs
304 x='foo()'
305 echo 1 ${x//*\(\)/z}
306 echo 2 ${x//*\(\)/z}
307 echo 3 ${x//\(\)/z}
308 echo 4 ${x//*\(\)/z}
309
310 ## STDOUT:
311 1 z
312 2 z
313 3 fooz
314 4 z
315 ## END
316
317 #### patsub with single quotes and hyphen in character class (regression)
318
319 # from Crestwave's bf.bash
320
321 program='^++--hello.,world<>[]'
322 program=${program//[^'><+-.,[]']}
323 echo $program
324 ## STDOUT:
325 ++--.,<>[]
326 ## END
327 ## BUG mksh STDOUT:
328 helloworld
329 ## END
330
331 #### patsub with [^]]
332
333 # This is a PARSING divergence. In Oil we match [], rather than using POSIX
334 # rules!
335
336 pat='[^]]'
337 s='ab^cd^'
338 echo ${s//$pat/z}
339 ## STDOUT:
340 ab^cd^
341 ## END
342
343 #### [a-z] Invalid range end is syntax error
344 x=fooz
345 pat='[z-a]' # Invalid range. Other shells don't catch it!
346 #pat='[a-y]'
347 echo ${x//$pat}
348 echo status=$?
349 ## stdout-json: ""
350 ## status: 1
351 ## OK bash/mksh/zsh/ash STDOUT:
352 fooz
353 status=0
354 ## END
355 ## OK bash/mksh/zsh/ash status: 0
356
357
358 #### Pattern is empty $foo$bar -- regression for infinite loop
359
360 x=-foo-
361
362 echo ${x//$foo$bar/bar}
363
364 ## STDOUT:
365 -foo-
366 ## END
367
368 # feels like memory unsafety in ZSH
369 ## BUG zsh STDOUT:
370 bar-barfbarobarobar-
371 ## END
372
373 #### Chromium from http://www.oilshell.org/blog/2016/11/07.html
374
375 case $SH in zsh) exit ;; esac
376
377 HOST_PATH=/foo/bar/baz
378 echo ${HOST_PATH////\\/}
379
380 # The way bash parses it
381 echo ${HOST_PATH//'/'/\\/}
382
383 ## STDOUT:
384 \/foo\/bar\/baz
385 \/foo\/bar\/baz
386 ## END
387
388 # zsh has crazy bugs
389 ## BUG zsh stdout-json: ""
390
391 ## BUG mksh STDOUT:
392 /foo/bar/baz
393 \/foo\/bar\/baz
394 ## END
395
396
397 #### ${x//~homedir/}
398
399 path=~/git/oilshell
400
401 # ~ expansion occurs
402 #echo path=$path
403
404 echo ${path//~/z}
405
406 echo ${path/~/z}
407
408 ## STDOUT:
409 z/git/oilshell
410 z/git/oilshell
411 ## END
412
413