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 |