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