1 ## oils_failures_allowed: 1
2 ## compare_shells: bash mksh
3
4
5 # Extended globs are an OPTION in bash, but not mksh (because the feature
6 # originated in ksh).
7 #
8 # However all extended globs are syntax errors if shopt -s extglob isn't set.
9 # In Oil, they are not PARSE TIME errors, but the syntax won't be respected at
10 # RUNTIME, i.e. when passed to fnmatch().
11 #
12 # GNU libc has the FNM_EXTMATCH extension to fnmatch(). (I don't think musl
13 # libc has it.) However, this came after all popular shells were implemented!
14 # I don't think any shell uses it, but we're taking advantage of it.
15 #
16 # Extended glob syntax is ugly, but I guess it's handy because it's similar to
17 # *.[ch]... but the extensions can be different length: *.@(cc|h)
18 # It's also used for negation like
19 #
20 # cp !(_*) /tmp
21 #
22 # I tend to use 'find', but this is a shorter syntax.
23
24 # From the bash manual:
25
26 # "In addition to the traditional globs (supported by all Bourne-family shells)
27 # that we've seen so far, Bash (and Korn Shell) offers extended globs, which
28 # have the expressive power of regular expressions. Korn shell enables these by
29 # default; in Bash, you must run the command "
30
31 # ?(pattern-list): Matches empty or one of the patterns
32 # *(pattern-list): Matches empty or any number of occurrences of the patterns
33 # +(pattern-list): Matches at least one occurrences of the patterns
34 # @(pattern-list): Matches exactly one of the patterns
35 # !(pattern-list): Matches anything EXCEPT any of the patterns
36
37 #### @() matches exactly one of the patterns
38 shopt -s extglob
39 mkdir -p 0
40 cd 0
41 touch {foo,bar}.cc {foo,bar,baz}.h
42 echo @(*.cc|*.h)
43 ## stdout: bar.cc bar.h baz.h foo.cc foo.h
44
45 #### ?() matches 0 or 1
46 shopt -s extglob
47 mkdir -p 1
48 cd 1
49 touch {foo,bar}.cc {foo,bar,baz}.h foo. foo.hh
50 ext=cc
51 echo foo.?($ext|h)
52 ## stdout: foo. foo.cc foo.h
53
54 #### *() matches 0 or more
55 shopt -s extglob
56 mkdir -p eg1
57 touch eg1/_ eg1/_One eg1/_OneOne eg1/_TwoTwo eg1/_OneTwo
58 echo eg1/_*(One|Two)
59 ## stdout: eg1/_ eg1/_One eg1/_OneOne eg1/_OneTwo eg1/_TwoTwo
60
61 #### +() matches 1 or more
62 shopt -s extglob
63 mkdir -p eg2
64 touch eg2/_ eg2/_One eg2/_OneOne eg2/_TwoTwo eg2/_OneTwo
65 echo eg2/_+(One|$(echo Two))
66 ## stdout: eg2/_One eg2/_OneOne eg2/_OneTwo eg2/_TwoTwo
67
68 #### !(*.h|*.cc) to match everything except C++
69 shopt -s extglob
70 mkdir -p extglob2
71 touch extglob2/{foo,bar}.cc extglob2/{foo,bar,baz}.h \
72 extglob2/{foo,bar,baz}.py
73 echo extglob2/!(*.h|*.cc)
74 ## stdout: extglob2/bar.py extglob2/baz.py extglob2/foo.py
75
76 #### Two adjacent alternations
77 shopt -s extglob
78 mkdir -p 2
79 touch 2/{aa,ab,ac,ba,bb,bc,ca,cb,cc}
80 echo 2/!(b)@(b|c)
81 echo 2/!(b)?@(b|c) # wildcard in between
82 echo 2/!(b)a@(b|c) # constant in between
83 ## STDOUT:
84 2/ab 2/ac 2/cb 2/cc
85 2/ab 2/ac 2/bb 2/bc 2/cb 2/cc
86 2/ab 2/ac
87 ## END
88
89 #### Nested extended glob pattern
90 shopt -s extglob
91 mkdir -p eg6
92 touch eg6/{ab,ac,ad,az,bc,bd}
93 echo eg6/a@(!(c|d))
94 echo eg6/a!(@(ab|b*))
95 ## STDOUT:
96 eg6/ab eg6/az
97 eg6/ac eg6/ad eg6/az
98 ## END
99
100 #### Extended glob patterns with spaces
101 shopt -s extglob
102 mkdir -p eg4
103 touch eg4/a 'eg4/a b' eg4/foo
104 argv.py eg4/@(a b|foo)
105 ## STDOUT:
106 ['eg4/a b', 'eg4/foo']
107 ## END
108
109 #### Filenames with spaces
110 shopt -s extglob
111 mkdir -p eg5
112 touch eg5/'a b'{cd,de,ef}
113 argv.py eg5/'a '@(bcd|bde|zzz)
114 ## STDOUT:
115 ['eg5/a bcd', 'eg5/a bde']
116 ## END
117
118 #### nullglob with extended glob
119 shopt -s extglob
120 mkdir eg6
121 argv.py eg6/@(no|matches) # no matches
122 shopt -s nullglob # test this too
123 argv.py eg6/@(no|matches) # no matches
124 ## STDOUT:
125 ['eg6/@(no|matches)']
126 []
127 ## END
128 ## BUG mksh STDOUT:
129 ['eg6/@(no|matches)']
130 ['eg6/@(no|matches)']
131 ## END
132
133 #### Glob other punctuation chars (lexer mode)
134 shopt -s extglob
135 mkdir -p eg5
136 cd eg5
137 touch __{aa,'<>','{}','#','&&'}
138 argv.py @(__aa|'__<>'|__{}|__#|__&&|)
139
140 # mksh sorts them differently
141 ## STDOUT:
142 ['__#', '__&&', '__<>', '__aa', '__{}']
143 ## END
144
145 #### More glob escaping
146 shopt -s extglob
147 mkdir -p eg7
148 cd eg7
149 touch '_[:]' '_*' '_?'
150 argv.py @('_[:]'|'_*'|'_?')
151 argv.py @(nested|'_?'|@('_[:]'|'_*'))
152
153 # mksh sorts them differently
154 ## STDOUT:
155 ['_*', '_?', '_[:]']
156 ['_*', '_?', '_[:]']
157 ## END
158
159 #### Escaping of pipe (glibc bug, see demo/glibc_fnmatch.c)
160 shopt -s extglob
161
162 mkdir -p extpipe
163 cd extpipe
164
165 touch '__|' foo
166 argv.py @('foo'|__\||bar)
167 argv.py @('foo'|'__|'|bar)
168
169 ## STDOUT:
170 ['__|', 'foo']
171 ['__|', 'foo']
172 ## END
173
174 #### Extended glob as argument to ${undef:-} (dynamic globbing)
175
176 # This case popped into my mind after inspecting osh/word_eval.py for calls to
177 # _EvalWordToParts()
178
179 shopt -s extglob
180
181 mkdir -p eg8
182 cd eg8
183 touch {foo,bar,spam}.py
184
185 # regular glob
186 echo ${undef:-*.py}
187
188 # extended glob
189 echo ${undef:-@(foo|bar).py}
190
191 ## STDOUT:
192 bar.py foo.py spam.py
193 bar.py foo.py
194 ## END
195 ## OK mksh STDOUT:
196 bar.py foo.py spam.py
197 @(foo|bar).py
198 ## END
199 ## OK osh status: 1
200 ## OK osh STDOUT:
201 bar.py foo.py spam.py
202 ## END
203
204 #### Extended glob in assignment builtin
205
206 # Another invocation of _EvalWordToParts() that OSH should handle
207
208 shopt -s extglob
209 mkdir -p eg9
210 cd eg9
211 touch {foo,bar}.py
212 typeset -@(*.py) myvar
213 echo status=$?
214 ## STDOUT:
215 status=2
216 ## END
217 ## OK mksh STDOUT:
218 status=1
219 ## END
220 ## OK osh status: 1
221 ## OK osh STDOUT:
222 ## END
223
224 #### Extended glob in same word as array
225 shopt -s extglob
226 mkdir -p eg10
227 cd eg10
228
229 touch {'a b c',bee,cee}.{py,cc}
230 set -- 'a b' 'c'
231
232 argv.py "$@"
233
234 # This works!
235 argv.py star glob "$*"*.py
236 argv.py star extglob "$*"*@(.py|cc)
237
238 # Hm this actually still works! the first two parts are literal. And then
239 # there's something like the simple_word_eval algorithm on the rest. Gah.
240 argv.py at extglob "$@"*@(.py|cc)
241
242 ## STDOUT:
243 ['a b', 'c']
244 ['star', 'glob', 'a b c.py']
245 ['star', 'extglob', 'a b c.cc', 'a b c.py']
246 ['at', 'extglob', 'a b', 'cee.cc', 'cee.py']
247 ## END
248 ## N-I osh STDOUT:
249 ['a b', 'c']
250 ['star', 'glob', 'a b c.py']
251 ['star', 'extglob', 'a b c.cc', 'a b c.py']
252 ## END
253 ## N-I osh status: 1
254
255 #### Extended glob with word splitting
256 shopt -s extglob
257 mkdir -p 3
258 cd 3
259
260 x='a b'
261 touch bar.{cc,h}
262
263 # OSH may disallow splitting when there's an extended glob
264 argv.py $x*.@(cc|h)
265
266 ## STDOUT:
267 ['a', 'bar.cc', 'bar.h']
268 ## END
269 ## N-I osh STDOUT:
270 ['a b*.@(cc|h)']
271 ## END
272
273 #### In Array Literal and for loop
274 shopt -s extglob
275 mkdir -p eg11
276 cd eg11
277 touch {foo,bar,spam}.py
278 for x in @(fo*|bar).py; do
279 echo $x
280 done
281
282 echo ---
283 declare -a A
284 A=(zzz @(fo*|bar).py)
285 echo "${A[@]}"
286 ## STDOUT:
287 bar.py
288 foo.py
289 ---
290 zzz bar.py foo.py
291 ## END
292
293 #### No extended glob with simple_word_eval (Oil evaluation)
294 shopt -s oil:all
295 shopt -s extglob
296 mkdir -p eg12
297 cd eg12
298 touch {foo,bar,spam}.py
299 builtin write -- x@(fo*|bar).py
300 builtin write -- @(fo*|bar).py
301 ## status: 1
302 ## STDOUT:
303 ## END
304
305 #### no match
306 shopt -s extglob
307 echo @(__nope__)
308
309 # OSH has glob quoting here
310 echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
311
312 if test $SH != osh; then
313 exit
314 fi
315
316 # OSH has this alias for @()
317 echo ,(osh|style)
318
319 ## STDOUT:
320 @(__nope__)
321 @(__nope__*|__nope__?|*|?|[:alpha:]||)
322 ## END
323
324 #### dashglob
325 shopt -s extglob
326 mkdir -p opts
327 cd opts
328
329 touch -- foo bar -dash
330 echo @(*)
331
332 shopt -u dashglob
333 echo @(*)
334
335
336 ## STDOUT:
337 -dash bar foo
338 bar foo
339 ## END
340 ## N-I bash/mksh STDOUT:
341 -dash bar foo
342 -dash bar foo
343 ## END
344
345 #### noglob
346 shopt -s extglob
347 mkdir -p _noglob
348 cd _noglob
349
350 set -o noglob
351 echo @(*)
352 echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
353
354 ## STDOUT:
355 @(*)
356 @(__nope__*|__nope__?|*|?|[:alpha:]||)
357 ## END
358
359 #### failglob
360 shopt -s extglob
361
362 rm -f _failglob/*
363 mkdir -p _failglob
364 cd _failglob
365
366 shopt -s failglob
367 echo @(*)
368 echo status=$?
369
370 touch foo
371 echo @(*)
372 echo status=$?
373
374 ## STDOUT:
375 status=1
376 foo
377 status=0
378 ## END
379 ## N-I mksh STDOUT:
380 @(*)
381 status=0
382 foo
383 status=0
384 ## END