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