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 as argument to ${undef:-} (dynamic globbing)
168
169 # This case popped into my mind after inspecting osh/word_eval.py for calls to
170 # _EvalWordToParts()
171
172 shopt -s extglob
173
174 mkdir -p eg8
175 cd eg8
176 touch {foo,bar,spam}.py
177
178 # regular glob
179 echo ${undef:-*.py}
180
181 # extended glob
182 echo ${undef:-@(foo|bar).py}
183
184 ## STDOUT:
185 bar.py foo.py spam.py
186 bar.py foo.py
187 ## END
188 ## OK mksh STDOUT:
189 bar.py foo.py spam.py
190 @(foo|bar).py
191 ## END
192 ## OK osh status: 1
193 ## OK osh STDOUT:
194 bar.py foo.py spam.py
195 ## END
196
197 #### Extended glob in assignment builtin
198
199 # Another invocation of _EvalWordToParts() that OSH should handle
200
201 shopt -s extglob
202 mkdir -p eg9
203 cd eg9
204 touch {foo,bar}.py
205 typeset -@(*.py) myvar
206 echo status=$?
207 ## STDOUT:
208 status=2
209 ## END
210 ## OK mksh STDOUT:
211 status=1
212 ## END
213 ## OK osh status: 1
214 ## OK osh STDOUT:
215 ## END
216
217 #### Extended glob in same word as array
218 shopt -s extglob
219 mkdir -p eg10
220 cd eg10
221
222 touch {'a b c',bee,cee}.{py,cc}
223 set -- 'a b' 'c'
224
225 argv.py "$@"
226
227 # This works!
228 argv.py star glob "$*"*.py
229 argv.py star extglob "$*"*@(.py|cc)
230
231 # Hm this actually still works! the first two parts are literal. And then
232 # there's something like the simple_word_eval algorithm on the rest. Gah.
233 argv.py at extglob "$@"*@(.py|cc)
234
235 ## STDOUT:
236 ['a b', 'c']
237 ['star', 'glob', 'a b c.py']
238 ['star', 'extglob', 'a b c.cc', 'a b c.py']
239 ['at', 'extglob', 'a b', 'cee.cc', 'cee.py']
240 ## END
241 ## N-I osh STDOUT:
242 ['a b', 'c']
243 ['star', 'glob', 'a b c.py']
244 ['star', 'extglob', 'a b c.cc', 'a b c.py']
245 ## END
246 ## N-I osh status: 1
247
248 #### Extended glob with word splitting
249 shopt -s extglob
250 mkdir -p 3
251 cd 3
252
253 x='a b'
254 touch bar.{cc,h}
255
256 # OSH may disallow splitting when there's an extended glob
257 argv.py $x*.@(cc|h)
258
259 ## STDOUT:
260 ['a', 'bar.cc', 'bar.h']
261 ## END
262 ## N-I osh STDOUT:
263 ['a b*.@(cc|h)']
264 ## END
265
266 #### In Array Literal and for loop
267 shopt -s extglob
268 mkdir -p eg11
269 cd eg11
270 touch {foo,bar,spam}.py
271 for x in @(fo*|bar).py; do
272 echo $x
273 done
274
275 echo ---
276 declare -a A
277 A=(zzz @(fo*|bar).py)
278 echo "${A[@]}"
279 ## STDOUT:
280 bar.py
281 foo.py
282 ---
283 zzz bar.py foo.py
284 ## END
285
286 #### No extended glob with simple_word_eval (Oil evaluation)
287 shopt -s oil:all
288 shopt -s extglob
289 mkdir -p eg12
290 cd eg12
291 touch {foo,bar,spam}.py
292 builtin write -- x@(fo*|bar).py
293 builtin write -- @(fo*|bar).py
294 ## status: 1
295 ## STDOUT:
296 ## END
297
298 #### no match
299 shopt -s extglob
300 echo @(__nope__)
301
302 # OSH has glob quoting here
303 echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
304
305 if test $SH != osh; then
306 exit
307 fi
308
309 # OSH has this alias for @()
310 echo ,(osh|style)
311
312 ## STDOUT:
313 @(__nope__)
314 @(__nope__*|__nope__?|*|?|[:alpha:]||)
315 ## END
316
317 #### dashglob
318 shopt -s extglob
319 mkdir -p opts
320 cd opts
321
322 touch -- foo bar -dash
323 echo @(*)
324
325 shopt -u dashglob
326 echo @(*)
327
328
329 ## STDOUT:
330 -dash bar foo
331 bar foo
332 ## END
333 ## N-I bash/mksh STDOUT:
334 -dash bar foo
335 -dash bar foo
336 ## END
337
338 #### noglob
339 shopt -s extglob
340 mkdir -p _noglob
341 cd _noglob
342
343 set -o noglob
344 echo @(*)
345 echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
346
347 ## STDOUT:
348 @(*)
349 @(__nope__*|__nope__?|*|?|[:alpha:]||)
350 ## END
351
352 #### failglob
353 shopt -s extglob
354
355 rm -f _failglob/*
356 mkdir -p _failglob
357 cd _failglob
358
359 shopt -s failglob
360 echo @(*)
361 echo status=$?
362
363 touch foo
364 echo @(*)
365 echo status=$?
366
367 ## STDOUT:
368 status=1
369 foo
370 status=0
371 ## END
372 ## N-I mksh STDOUT:
373 @(*)
374 status=0
375 foo
376 status=0
377 ## END