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

384 lines, 191 significant
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
38shopt -s extglob
39mkdir -p 0
40cd 0
41touch {foo,bar}.cc {foo,bar,baz}.h
42echo @(*.cc|*.h)
43## stdout: bar.cc bar.h baz.h foo.cc foo.h
44
45#### ?() matches 0 or 1
46shopt -s extglob
47mkdir -p 1
48cd 1
49touch {foo,bar}.cc {foo,bar,baz}.h foo. foo.hh
50ext=cc
51echo foo.?($ext|h)
52## stdout: foo. foo.cc foo.h
53
54#### *() matches 0 or more
55shopt -s extglob
56mkdir -p eg1
57touch eg1/_ eg1/_One eg1/_OneOne eg1/_TwoTwo eg1/_OneTwo
58echo eg1/_*(One|Two)
59## stdout: eg1/_ eg1/_One eg1/_OneOne eg1/_OneTwo eg1/_TwoTwo
60
61#### +() matches 1 or more
62shopt -s extglob
63mkdir -p eg2
64touch eg2/_ eg2/_One eg2/_OneOne eg2/_TwoTwo eg2/_OneTwo
65echo eg2/_+(One|$(echo Two))
66## stdout: eg2/_One eg2/_OneOne eg2/_OneTwo eg2/_TwoTwo
67
68#### !(*.h|*.cc) to match everything except C++
69shopt -s extglob
70mkdir -p extglob2
71touch extglob2/{foo,bar}.cc extglob2/{foo,bar,baz}.h \
72 extglob2/{foo,bar,baz}.py
73echo extglob2/!(*.h|*.cc)
74## stdout: extglob2/bar.py extglob2/baz.py extglob2/foo.py
75
76#### Two adjacent alternations
77shopt -s extglob
78mkdir -p 2
79touch 2/{aa,ab,ac,ba,bb,bc,ca,cb,cc}
80echo 2/!(b)@(b|c)
81echo 2/!(b)?@(b|c) # wildcard in between
82echo 2/!(b)a@(b|c) # constant in between
83## STDOUT:
842/ab 2/ac 2/cb 2/cc
852/ab 2/ac 2/bb 2/bc 2/cb 2/cc
862/ab 2/ac
87## END
88
89#### Nested extended glob pattern
90shopt -s extglob
91mkdir -p eg6
92touch eg6/{ab,ac,ad,az,bc,bd}
93echo eg6/a@(!(c|d))
94echo eg6/a!(@(ab|b*))
95## STDOUT:
96eg6/ab eg6/az
97eg6/ac eg6/ad eg6/az
98## END
99
100#### Extended glob patterns with spaces
101shopt -s extglob
102mkdir -p eg4
103touch eg4/a 'eg4/a b' eg4/foo
104argv.py eg4/@(a b|foo)
105## STDOUT:
106['eg4/a b', 'eg4/foo']
107## END
108
109#### Filenames with spaces
110shopt -s extglob
111mkdir -p eg5
112touch eg5/'a b'{cd,de,ef}
113argv.py eg5/'a '@(bcd|bde|zzz)
114## STDOUT:
115['eg5/a bcd', 'eg5/a bde']
116## END
117
118#### nullglob with extended glob
119shopt -s extglob
120mkdir eg6
121argv.py eg6/@(no|matches) # no matches
122shopt -s nullglob # test this too
123argv.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)
134shopt -s extglob
135mkdir -p eg5
136cd eg5
137touch __{aa,'<>','{}','#','&&'}
138argv.py @(__aa|'__<>'|__{}|__#|__&&|)
139
140# mksh sorts them differently
141## STDOUT:
142['__#', '__&&', '__<>', '__aa', '__{}']
143## END
144
145#### More glob escaping
146shopt -s extglob
147mkdir -p eg7
148cd eg7
149touch '_[:]' '_*' '_?'
150argv.py @('_[:]'|'_*'|'_?')
151argv.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)
160shopt -s extglob
161
162mkdir -p extpipe
163cd extpipe
164
165touch '__|' foo
166argv.py @('foo'|__\||bar)
167argv.py @('foo'|'__|'|bar)
168
169## STDOUT:
170['__|', 'foo']
171['__|', 'foo']
172## END
173
174#### Extended glob as argument to ${undef:-} (dynamic globbing)
175
176# This case popped into my mind after inspecting osh/word_eval.py for calls to
177# _EvalWordToParts()
178
179shopt -s extglob
180
181mkdir -p eg8
182cd eg8
183touch {foo,bar,spam}.py
184
185# regular glob
186echo ${undef:-*.py}
187
188# extended glob
189echo ${undef:-@(foo|bar).py}
190
191## STDOUT:
192bar.py foo.py spam.py
193bar.py foo.py
194## END
195## OK mksh STDOUT:
196bar.py foo.py spam.py
197@(foo|bar).py
198## END
199## OK osh status: 1
200## OK osh STDOUT:
201bar.py foo.py spam.py
202## END
203
204#### Extended glob in assignment builtin
205
206# Another invocation of _EvalWordToParts() that OSH should handle
207
208shopt -s extglob
209mkdir -p eg9
210cd eg9
211touch {foo,bar}.py
212typeset -@(*.py) myvar
213echo status=$?
214## STDOUT:
215status=2
216## END
217## OK mksh STDOUT:
218status=1
219## END
220## OK osh status: 1
221## OK osh STDOUT:
222## END
223
224#### Extended glob in same word as array
225shopt -s extglob
226mkdir -p eg10
227cd eg10
228
229touch {'a b c',bee,cee}.{py,cc}
230set -- 'a b' 'c'
231
232argv.py "$@"
233
234# This works!
235argv.py star glob "$*"*.py
236argv.py star extglob "$*"*@(.py|cc)
237
238# Hm this actually still works! the first two parts are literal. And then
239# there's something like the simple_word_eval algorithm on the rest. Gah.
240argv.py at extglob "$@"*@(.py|cc)
241
242## STDOUT:
243['a b', 'c']
244['star', 'glob', 'a b c.py']
245['star', 'extglob', 'a b c.cc', 'a b c.py']
246['at', 'extglob', 'a b', 'cee.cc', 'cee.py']
247## END
248## N-I osh STDOUT:
249['a b', 'c']
250['star', 'glob', 'a b c.py']
251['star', 'extglob', 'a b c.cc', 'a b c.py']
252## END
253## N-I osh status: 1
254
255#### Extended glob with word splitting
256shopt -s extglob
257mkdir -p 3
258cd 3
259
260x='a b'
261touch bar.{cc,h}
262
263# OSH may disallow splitting when there's an extended glob
264argv.py $x*.@(cc|h)
265
266## STDOUT:
267['a', 'bar.cc', 'bar.h']
268## END
269## N-I osh STDOUT:
270['a b*.@(cc|h)']
271## END
272
273#### In Array Literal and for loop
274shopt -s extglob
275mkdir -p eg11
276cd eg11
277touch {foo,bar,spam}.py
278for x in @(fo*|bar).py; do
279 echo $x
280done
281
282echo ---
283declare -a A
284A=(zzz @(fo*|bar).py)
285echo "${A[@]}"
286## STDOUT:
287bar.py
288foo.py
289---
290zzz bar.py foo.py
291## END
292
293#### No extended glob with simple_word_eval (Oil evaluation)
294shopt -s oil:all
295shopt -s extglob
296mkdir -p eg12
297cd eg12
298touch {foo,bar,spam}.py
299builtin write -- x@(fo*|bar).py
300builtin write -- @(fo*|bar).py
301## status: 1
302## STDOUT:
303## END
304
305#### no match
306shopt -s extglob
307echo @(__nope__)
308
309# OSH has glob quoting here
310echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
311
312if test $SH != osh; then
313 exit
314fi
315
316# OSH has this alias for @()
317echo ,(osh|style)
318
319## STDOUT:
320@(__nope__)
321@(__nope__*|__nope__?|*|?|[:alpha:]||)
322## END
323
324#### dashglob
325shopt -s extglob
326mkdir -p opts
327cd opts
328
329touch -- foo bar -dash
330echo @(*)
331
332shopt -u dashglob
333echo @(*)
334
335
336## STDOUT:
337-dash bar foo
338bar foo
339## END
340## N-I bash/mksh STDOUT:
341-dash bar foo
342-dash bar foo
343## END
344
345#### noglob
346shopt -s extglob
347mkdir -p _noglob
348cd _noglob
349
350set -o noglob
351echo @(*)
352echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
353
354## STDOUT:
355@(*)
356@(__nope__*|__nope__?|*|?|[:alpha:]||)
357## END
358
359#### failglob
360shopt -s extglob
361
362rm -f _failglob/*
363mkdir -p _failglob
364cd _failglob
365
366shopt -s failglob
367echo @(*)
368echo status=$?
369
370touch foo
371echo @(*)
372echo status=$?
373
374## STDOUT:
375status=1
376foo
377status=0
378## END
379## N-I mksh STDOUT:
380@(*)
381status=0
382foo
383status=0
384## END