I'm writing a zsh program that takes glob patterns as command-line arguments:
tss files --tags 'a*' # lists files bearing a tag that starts with an 'a'
In order to validate input, is there a way to check whether a string is a syntactically valid glob pattern?
Ideally I would be able to validate both extended and non-extended patterns, leaving to the user the choice to use either, but I'd appreciate a solution that works with only one of the two.
One possibility is to attempt to use the glob pattern with a ${~...}
expansion. This can be wrapped in a try-block to prevent the shell from exiting if there is an error:
checkGlob() {
setopt nullglob
{ : ${~1} } always { TRY_BLOCK_ERROR=0 }
}
checkGlob 'aa*'; print $?
# => 0
checkGlob 'aa['; print $?
# => 1
Some of the pieces
setopt nullglob
- no error if there isn't a match for the glob pattern.{ <try-block> } always { <always-block> }
- even if there's an error in the try-block, the code in the always-block will always be executed.:
- no-op. The expansion that follows will be attempted, but no command will be executed.${~1}
- expands the first positional parameter with GLOB_SUBST
set, so glob patterns in the value will be processed.TRY_BLOCK_ERROR=0
- resets the error status. Normally a runtime error like an incorrect glob pattern results in the shell exiting. This prevents that.~
expansion.The extended glob setting will determine which glob patterns are validated. This version of the function sets that option:
checkGlob() {
setopt nullglob extendedglob localoptions
pat=APREFIX${1:?}
{
: ${~pat}
} always {
TRY_BLOCK_ERROR=0
} &> /dev/null
}
This also has:
localoptions
- changes from setopt
will only apply to this function.pat=APREFIX...
- the ~
expansion is going to try to find actual files that match the pattern in the current directory - this is one way to make that list shorter. Another option is to change into an empty directory created with mktemp
.&> /dev/null
- block any output from the expansion.You could run a similar check with extended glob turned off to try with both types, but note that many zsh
utilities, such as zmv
, simply turn extended globs on by default and call it a day.
Testing:
for gl in '*' 'a*' 'a[bc]' 'a[' 'a(.)' 'a(' 'a(#z)' '/tmp'; do
if checkGlob $gl; then
print "valid: $gl"
else
print "invalid: $gl"
fi
done
# => valid: *
# => valid: a*
# => valid: a[bc]
# => invalid: a[
# => valid: a(.)
# => invalid: a(
# => invalid: a(#z)
# => valid: /tmp