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