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