OILS / spec / extglob-files.test.sh View on Github | oilshell.org

377 lines, 191 significant
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
34shopt -s extglob
35mkdir -p 0
36cd 0
37touch {foo,bar}.cc {foo,bar,baz}.h
38echo @(*.cc|*.h)
39## stdout: bar.cc bar.h baz.h foo.cc foo.h
40
41#### ?() matches 0 or 1
42shopt -s extglob
43mkdir -p 1
44cd 1
45touch {foo,bar}.cc {foo,bar,baz}.h foo. foo.hh
46ext=cc
47echo foo.?($ext|h)
48## stdout: foo. foo.cc foo.h
49
50#### *() matches 0 or more
51shopt -s extglob
52mkdir -p eg1
53touch eg1/_ eg1/_One eg1/_OneOne eg1/_TwoTwo eg1/_OneTwo
54echo eg1/_*(One|Two)
55## stdout: eg1/_ eg1/_One eg1/_OneOne eg1/_OneTwo eg1/_TwoTwo
56
57#### +() matches 1 or more
58shopt -s extglob
59mkdir -p eg2
60touch eg2/_ eg2/_One eg2/_OneOne eg2/_TwoTwo eg2/_OneTwo
61echo eg2/_+(One|$(echo Two))
62## stdout: eg2/_One eg2/_OneOne eg2/_OneTwo eg2/_TwoTwo
63
64#### !(*.h|*.cc) to match everything except C++
65shopt -s extglob
66mkdir -p extglob2
67touch extglob2/{foo,bar}.cc extglob2/{foo,bar,baz}.h \
68 extglob2/{foo,bar,baz}.py
69echo extglob2/!(*.h|*.cc)
70## stdout: extglob2/bar.py extglob2/baz.py extglob2/foo.py
71
72#### Two adjacent alternations
73shopt -s extglob
74mkdir -p 2
75touch 2/{aa,ab,ac,ba,bb,bc,ca,cb,cc}
76echo 2/!(b)@(b|c)
77echo 2/!(b)?@(b|c) # wildcard in between
78echo 2/!(b)a@(b|c) # constant in between
79## STDOUT:
802/ab 2/ac 2/cb 2/cc
812/ab 2/ac 2/bb 2/bc 2/cb 2/cc
822/ab 2/ac
83## END
84
85#### Nested extended glob pattern
86shopt -s extglob
87mkdir -p eg6
88touch eg6/{ab,ac,ad,az,bc,bd}
89echo eg6/a@(!(c|d))
90echo eg6/a!(@(ab|b*))
91## STDOUT:
92eg6/ab eg6/az
93eg6/ac eg6/ad eg6/az
94## END
95
96#### Extended glob patterns with spaces
97shopt -s extglob
98mkdir -p eg4
99touch eg4/a 'eg4/a b' eg4/foo
100argv.py eg4/@(a b|foo)
101## STDOUT:
102['eg4/a b', 'eg4/foo']
103## END
104
105#### Filenames with spaces
106shopt -s extglob
107mkdir -p eg5
108touch eg5/'a b'{cd,de,ef}
109argv.py eg5/'a '@(bcd|bde|zzz)
110## STDOUT:
111['eg5/a bcd', 'eg5/a bde']
112## END
113
114#### nullglob with extended glob
115shopt -s extglob
116mkdir eg6
117argv.py eg6/@(no|matches) # no matches
118shopt -s nullglob # test this too
119argv.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)
130shopt -s extglob
131mkdir -p eg5
132cd eg5
133touch __{aa,'<>','{}','#','&&'}
134argv.py @(__aa|'__<>'|__{}|__#|__&&|)
135
136# mksh sorts them differently
137## STDOUT:
138['__#', '__&&', '__<>', '__aa', '__{}']
139## END
140
141#### More glob escaping
142shopt -s extglob
143mkdir -p eg7
144cd eg7
145touch '_[:]' '_*' '_?'
146argv.py @('_[:]'|'_*'|'_?')
147argv.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)
156shopt -s extglob
157mkdir -p extpipe
158cd extpipe
159touch '__|' foo
160argv.py @('foo'|__\||bar)
161argv.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
172shopt -s extglob
173
174mkdir -p eg8
175cd eg8
176touch {foo,bar,spam}.py
177
178# regular glob
179echo ${undef:-*.py}
180
181# extended glob
182echo ${undef:-@(foo|bar).py}
183
184## STDOUT:
185bar.py foo.py spam.py
186bar.py foo.py
187## END
188## OK mksh STDOUT:
189bar.py foo.py spam.py
190@(foo|bar).py
191## END
192## OK osh status: 1
193## OK osh STDOUT:
194bar.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
201shopt -s extglob
202mkdir -p eg9
203cd eg9
204touch {foo,bar}.py
205typeset -@(*.py) myvar
206echo status=$?
207## STDOUT:
208status=2
209## END
210## OK mksh STDOUT:
211status=1
212## END
213## OK osh status: 1
214## OK osh STDOUT:
215## END
216
217#### Extended glob in same word as array
218shopt -s extglob
219mkdir -p eg10
220cd eg10
221
222touch {'a b c',bee,cee}.{py,cc}
223set -- 'a b' 'c'
224
225argv.py "$@"
226
227# This works!
228argv.py star glob "$*"*.py
229argv.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.
233argv.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
249shopt -s extglob
250mkdir -p 3
251cd 3
252
253x='a b'
254touch bar.{cc,h}
255
256# OSH may disallow splitting when there's an extended glob
257argv.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
267shopt -s extglob
268mkdir -p eg11
269cd eg11
270touch {foo,bar,spam}.py
271for x in @(fo*|bar).py; do
272 echo $x
273done
274
275echo ---
276declare -a A
277A=(zzz @(fo*|bar).py)
278echo "${A[@]}"
279## STDOUT:
280bar.py
281foo.py
282---
283zzz bar.py foo.py
284## END
285
286#### No extended glob with simple_word_eval (Oil evaluation)
287shopt -s oil:all
288shopt -s extglob
289mkdir -p eg12
290cd eg12
291touch {foo,bar,spam}.py
292builtin write -- x@(fo*|bar).py
293builtin write -- @(fo*|bar).py
294## status: 1
295## STDOUT:
296## END
297
298#### no match
299shopt -s extglob
300echo @(__nope__)
301
302# OSH has glob quoting here
303echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
304
305if test $SH != osh; then
306 exit
307fi
308
309# OSH has this alias for @()
310echo ,(osh|style)
311
312## STDOUT:
313@(__nope__)
314@(__nope__*|__nope__?|*|?|[:alpha:]||)
315## END
316
317#### dashglob
318shopt -s extglob
319mkdir -p opts
320cd opts
321
322touch -- foo bar -dash
323echo @(*)
324
325shopt -u dashglob
326echo @(*)
327
328
329## STDOUT:
330-dash bar foo
331bar foo
332## END
333## N-I bash/mksh STDOUT:
334-dash bar foo
335-dash bar foo
336## END
337
338#### noglob
339shopt -s extglob
340mkdir -p _noglob
341cd _noglob
342
343set -o noglob
344echo @(*)
345echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
346
347## STDOUT:
348@(*)
349@(__nope__*|__nope__?|*|?|[:alpha:]||)
350## END
351
352#### failglob
353shopt -s extglob
354
355rm -f _failglob/*
356mkdir -p _failglob
357cd _failglob
358
359shopt -s failglob
360echo @(*)
361echo status=$?
362
363touch foo
364echo @(*)
365echo status=$?
366
367## STDOUT:
368status=1
369foo
370status=0
371## END
372## N-I mksh STDOUT:
373@(*)
374status=0
375foo
376status=0
377## END