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