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 |