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

381 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
161mkdir -p extpipe
162cd extpipe
163touch '__|' foo
164argv.py @('foo'|__\||bar)
165argv.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
176shopt -s extglob
177
178mkdir -p eg8
179cd eg8
180touch {foo,bar,spam}.py
181
182# regular glob
183echo ${undef:-*.py}
184
185# extended glob
186echo ${undef:-@(foo|bar).py}
187
188## STDOUT:
189bar.py foo.py spam.py
190bar.py foo.py
191## END
192## OK mksh STDOUT:
193bar.py foo.py spam.py
194@(foo|bar).py
195## END
196## OK osh status: 1
197## OK osh STDOUT:
198bar.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
205shopt -s extglob
206mkdir -p eg9
207cd eg9
208touch {foo,bar}.py
209typeset -@(*.py) myvar
210echo status=$?
211## STDOUT:
212status=2
213## END
214## OK mksh STDOUT:
215status=1
216## END
217## OK osh status: 1
218## OK osh STDOUT:
219## END
220
221#### Extended glob in same word as array
222shopt -s extglob
223mkdir -p eg10
224cd eg10
225
226touch {'a b c',bee,cee}.{py,cc}
227set -- 'a b' 'c'
228
229argv.py "$@"
230
231# This works!
232argv.py star glob "$*"*.py
233argv.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.
237argv.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
253shopt -s extglob
254mkdir -p 3
255cd 3
256
257x='a b'
258touch bar.{cc,h}
259
260# OSH may disallow splitting when there's an extended glob
261argv.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
271shopt -s extglob
272mkdir -p eg11
273cd eg11
274touch {foo,bar,spam}.py
275for x in @(fo*|bar).py; do
276 echo $x
277done
278
279echo ---
280declare -a A
281A=(zzz @(fo*|bar).py)
282echo "${A[@]}"
283## STDOUT:
284bar.py
285foo.py
286---
287zzz bar.py foo.py
288## END
289
290#### No extended glob with simple_word_eval (Oil evaluation)
291shopt -s oil:all
292shopt -s extglob
293mkdir -p eg12
294cd eg12
295touch {foo,bar,spam}.py
296builtin write -- x@(fo*|bar).py
297builtin write -- @(fo*|bar).py
298## status: 1
299## STDOUT:
300## END
301
302#### no match
303shopt -s extglob
304echo @(__nope__)
305
306# OSH has glob quoting here
307echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
308
309if test $SH != osh; then
310 exit
311fi
312
313# OSH has this alias for @()
314echo ,(osh|style)
315
316## STDOUT:
317@(__nope__)
318@(__nope__*|__nope__?|*|?|[:alpha:]||)
319## END
320
321#### dashglob
322shopt -s extglob
323mkdir -p opts
324cd opts
325
326touch -- foo bar -dash
327echo @(*)
328
329shopt -u dashglob
330echo @(*)
331
332
333## STDOUT:
334-dash bar foo
335bar foo
336## END
337## N-I bash/mksh STDOUT:
338-dash bar foo
339-dash bar foo
340## END
341
342#### noglob
343shopt -s extglob
344mkdir -p _noglob
345cd _noglob
346
347set -o noglob
348echo @(*)
349echo @(__nope__*|__nope__?|'*'|'?'|'[:alpha:]'|'|')
350
351## STDOUT:
352@(*)
353@(__nope__*|__nope__?|*|?|[:alpha:]||)
354## END
355
356#### failglob
357shopt -s extglob
358
359rm -f _failglob/*
360mkdir -p _failglob
361cd _failglob
362
363shopt -s failglob
364echo @(*)
365echo status=$?
366
367touch foo
368echo @(*)
369echo status=$?
370
371## STDOUT:
372status=1
373foo
374status=0
375## END
376## N-I mksh STDOUT:
377@(*)
378status=0
379foo
380status=0
381## END