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 echo foo.?(cc|h)
47 ## stdout: foo. foo.cc foo.h
48
49 #### *() matches 0 or more
50 shopt -s extglob
51 mkdir -p eg1
52 touch eg1/_ eg1/_One eg1/_OneOne eg1/_TwoTwo eg1/_OneTwo
53 echo eg1/_*(One|Two)
54 ## stdout: eg1/_ eg1/_One eg1/_OneOne eg1/_OneTwo eg1/_TwoTwo
55
56 #### +() matches 1 or more
57 shopt -s extglob
58 mkdir -p eg2
59 touch eg2/_ eg2/_One eg2/_OneOne eg2/_TwoTwo eg2/_OneTwo
60 echo eg2/_+(One|Two)
61 ## stdout: eg2/_One eg2/_OneOne eg2/_OneTwo eg2/_TwoTwo
62
63 #### !(*.h|*.cc) to match everything except C++
64 shopt -s extglob
65 mkdir -p extglob2
66 touch extglob2/{foo,bar}.cc extglob2/{foo,bar,baz}.h \
67 extglob2/{foo,bar,baz}.py
68 echo extglob2/!(*.h|*.cc)
69 ## stdout: extglob2/bar.py extglob2/baz.py extglob2/foo.py
70
71 #### Two adjacent alternations
72 shopt -s extglob
73 mkdir -p 2
74 touch 2/{aa,ab,ac,ba,bb,bc,ca,cb,cc}
75 echo 2/!(b)@(b|c)
76 echo 2/!(b)?@(b|c) # wildcard in between
77 echo 2/!(b)a@(b|c) # constant in between
78 ## STDOUT:
79 2/ab 2/ac 2/cb 2/cc
80 2/ab 2/ac 2/bb 2/bc 2/cb 2/cc
81 2/ab 2/ac
82 ## END
83
84 #### Nested extended glob pattern
85 shopt -s extglob
86 mkdir -p eg6
87 touch eg6/{ab,ac,ad,az,bc,bd}
88 echo eg6/a@(!(c|d))
89 echo eg6/a!(@(ab|b*))
90 ## STDOUT:
91 eg6/ab eg6/az
92 eg6/ac eg6/ad eg6/az
93 ## END
94
95 #### Extended glob patterns with spaces
96 shopt -s extglob
97 mkdir -p eg4
98 touch eg4/a 'eg4/a b' eg4/foo
99 argv.py eg4/@(a b|foo)
100 ## STDOUT:
101 ['eg4/a b', 'eg4/foo']
102 ## END
103
104 #### Filenames with spaces
105 shopt -s extglob
106 mkdir -p eg5
107 touch eg5/'a b'{cd,de,ef}
108 argv.py eg5/'a '@(bcd|bde|zzz)
109 ## STDOUT:
110 ['eg5/a bcd', 'eg5/a bde']
111 ## END
112
113 #### nullglob with extended glob
114 shopt -s extglob
115 shopt -s nullglob # test this too
116 mkdir eg6
117 argv.py eg6/@(no|matches) # no matches
118 ## STDOUT:
119 []
120 ## END
121 ## BUG mksh STDOUT:
122 ['eg6/@(no|matches)']
123 ## END
124
125
126 #### glob other punctuation chars (lexer mode)
127 # mksh sorts them differently
128 shopt -s extglob
129 mkdir -p eg5
130 cd eg5
131 touch __{'<>','{}','|','#','&&'}
132 argv.py @('__<>'|__{}|__\||__#|__&&)
133 ## stdout: ['__<>', '__|', '__{}', '__&&', '__#']
134 ## OK mksh stdout: ['__#', '__&&', '__<>', '__{}', '__|']
135
136 #### dynamic extglob from variable
137
138 # mksh does static parsing so it doesn't like this?
139 shopt -s extglob
140 mkdir -p eg3
141 touch eg3/{foo,bar}
142 g=eg3/@(foo|bar)
143 echo $g "$g" # quoting inhibits globbing
144 ## stdout: eg3/bar eg3/foo eg3/@(foo|bar)
145 ## N-I mksh stdout: eg3/@(foo|bar) eg3/@(foo|bar)
146
147 #### Extended glob syntax in bad redirect context
148 shopt -s extglob
149 rm bad_*
150
151 # They actually write this literal file! This is what EvalWordToString() does,
152 # as opposed to _EvalWordToParts.
153 echo foo > bad_@(*.cc|*.h)
154 echo bad_*
155 ## STDOUT:
156 bad_@(*.cc|*.h)
157 ## END
158
159 #### Extended glob as argument to ${undef:-} (dynamic globbing)
160
161 # This case popped into my mind after inspecting osh/word_eval.py for calls to
162 # _EvalWordToParts()
163
164 shopt -s extglob
165
166 mkdir -p eg8
167 cd eg8
168 touch {foo,bar,spam}.py
169
170 # regular glob
171 echo ${undef:-*.py}
172
173 # extended glob
174 echo ${undef:-@(foo|bar).py}
175
176 ## STDOUT:
177 bar.py foo.py spam.py
178 bar.py foo.py
179 ## END
180 ## OK mksh STDOUT:
181 bar.py foo.py spam.py
182 @(foo|bar).py
183 ## END
184
185 #### Extended glob in assignment builtin
186
187 # Another invocation of _EvalWordToParts() that OSH should handle
188
189 shopt -s extglob
190 mkdir -p eg9
191 cd eg9
192 touch {foo,bar}.py
193 typeset -@(*.py) myvar
194 echo status=$?
195 ## STDOUT:
196 status=2
197 ## END
198 ## OK mksh STDOUT:
199 status=1
200 ## END
201
202 #### Extended glob in same word as array
203 shopt -s extglob
204 mkdir -p eg10
205 cd eg10
206
207 touch {'a b c',bee,cee}.{py,cc}
208 set -- 'a b' 'c'
209
210 argv.py "$@"
211
212 # This works!
213 argv.py star glob "$*"*.py
214 argv.py star extglob "$*"*@(.py|cc)
215
216 # Hm this actually still works! the first two parts are literal. And then
217 # there's something like the simple_word_eval algorithm on the rest. Gah.
218 argv.py at extglob "$@"*@(.py|cc)
219
220 ## STDOUT:
221 ['a b', 'c']
222 ['star', 'glob', 'a b c.py']
223 ['star', 'extglob', 'a b c.cc', 'a b c.py']
224 ['at', 'extglob', 'a b', 'cee.cc', 'cee.py']
225 ## END
226
227 #### In Array Literal and for loop
228 shopt -s extglob
229 mkdir -p eg11
230 cd eg11
231 touch {foo,bar,spam}.py
232 for x in @(fo*|bar).py; do
233 echo $x
234 done
235
236 echo ---
237 declare -a A
238 A=(zzz @(fo*|bar).py)
239 echo "${A[@]}"
240 ## STDOUT:
241 bar.py
242 foo.py
243 ---
244 zzz bar.py foo.py
245 ## END
246
247 # TODO: Also test with shopt --set simple_word_eval