Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
Re: [PATCH] docs: clarify glob-qualifier syntax
On Wed 28 May 2025, at 10:27, dana wrote:
> i just realised you can put the delimiters in the quotes as well. of
> course. the 'signature' is estring, not e:string:
revision based on irc discussion
also some tests
peter's patch has been ok for me so far but i haven't tried to abuse it.
i can add a test for that too when/if we merge it
dana
diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index 70212dbc8..2dc69eb1e 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -2800,8 +2800,8 @@ will be inserted in the argument list.
pindex(BARE_GLOB_QUAL, use of)
If the option tt(BARE_GLOB_QUAL) is set, then a trailing set of parentheses
-containing no `tt(|)' or `tt(LPAR())' characters (or `tt(~)' if it is special)
-is taken as a set of
+containing no unquoted `tt(|)' or `tt(LPAR())' characters (or `tt(~)' if
+it is special due to tt(EXTENDED_GLOB)) is taken as a set of
glob qualifiers. A glob subexpression that would normally be taken as glob
qualifiers, for example `tt((^x))', can be forced to be treated as part of
the glob pattern by doubling the parentheses, in this case producing
@@ -2828,6 +2828,10 @@ it is also valid if it is simply tt(LPAR()#q+RPAR()). This does
not apply to the right hand side of pattern match operators as the
syntax already has special significance.
+With either syntax, normal quoting and expansion rules apply to the list
+of qualifiers, so that the forms `tt(*(.))', `tt(*(#q"."))', and
+`tt(qual=.;) var(...) tt(*(#q$qual))' are generally equivalent.
+
A qualifier may be any one of the following:
startitem()
@@ -2945,16 +2949,23 @@ permission.
)
xitem(tt(e)var(string))
item(tt(PLUS())var(cmd))(
-The var(string) will be executed as shell code. The filename will be
+The var(string) or var(cmd) will be executed as shell code. The
+filename will be
included in the list if and only if the code returns a zero status (usually
the status of the last command).
In the first form, the first character after the `tt(e)'
will be used as a separator and anything up to the next matching separator
-will be taken as the var(string); `tt([)', `tt({)', and `tt(<)' match
-`tt(])', `tt(})', and `tt(>)', respectively, while any other character
-matches itself. Note that expansions must be quoted in the var(string)
-to prevent them from being expanded before globbing is done.
+will be taken as the var(string); `tt(LPAR())', `tt([)', `tt({)', and
+`tt(<)' match `tt(RPAR())', `tt(])', `tt(})', and `tt(>)', respectively,
+while any other character matches itself.
+As with glob qualifiers in general, normal quoting and expansion rules
+apply to the entire var(string). Thus expansions must be quoted to
+prevent them from being expanded before globbing is done, and quoting
+does not normally protect the end separator if it appears in the code.
+However, if an unquoted metacharacter is used as the separator, its
+quoted/expanded form is protected.
+
var(string) is then executed as shell code. The string tt(globqual)
is appended to the array tt(zsh_eval_context) the duration of
execution.
diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo
index bf62241d8..aabd80d08 100644
--- a/Doc/Zsh/options.yo
+++ b/Doc/Zsh/options.yo
@@ -440,7 +440,7 @@ cindex(globbing qualifiers, enable)
cindex(enable globbing qualifiers)
item(tt(BARE_GLOB_QUAL) <Z>)(
In a glob pattern, treat a trailing set of parentheses as a qualifier
-list, if it contains no `tt(|)', `tt(LPAR())' or (if special) `tt(~)'
+list, if it contains no unquoted `tt(|)', `tt(LPAR())' or (if special) `tt(~)'
characters. See noderef(Filename Generation).
)
pindex(BRACE_CCL)
diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst
index 4d88e5c27..24494d274 100644
--- a/Test/D02glob.ztst
+++ b/Test/D02glob.ztst
@@ -541,6 +541,19 @@
>No file beginning with z
>Normal string if nullglob not set
+ (
+ setopt extended_glob no_nomatch
+ cd glob.tmp
+ [[ a(#q.) == a ]] && print lhs 1
+ [[ a(#q/) == a ]] && print lhs 2
+ [[ z == *(#q.) ]] && print rhs 1 # (#q...) ignored
+ [[ z == *(#q/) ]] && print rhs 2 # (#q...) ignored
+ )
+0:(#q) glob expansion only on lhs of pattern-match conditions
+>lhs 1
+>rhs 1
+>rhs 2
+
(){ print $#@ } glob.tmp/dir*(Y1)
(){ print $#@ } glob.tmp/file*(NY1)
(){ [[ "$*" == */dir?\ */dir? ]] && print Returns matching filenames } glob.tmp/dir*(Y2)
@@ -841,6 +854,53 @@
[[ abc = (b*|a*)~^(*b) ]]
1:Regression test for exclusion after branches: failure case 3
+ (
+ setopt extended_glob
+ cd glob.tmp
+ qual=.
+ echo [a](.) [a]('.') [a](".") [a](\.) [a]($qual) [a]("$qual")
+ echo [a](#q.) [a](#q'.') [a](#q".") [a](#q\.) [a](#q$qual) [a](#q"$qual")
+ qual='(#q.)'; echo [a]$~qual
+ qual='(#'; echo [a]${~qual}q.${:-)}
+ qual='(#q'; echo [a]${~qual}.${:-)}
+ )
+0:general glob qualifier quoting and expansion
+>a a a a a a
+>a a a a a a
+>a
+>a
+>a
+
+ (
+ setopt extended_glob
+ cd glob.tmp
+ s='*' quals=(
+ '(#qe:"REPLY=*":)' '(#qe":REPLY=*:")' '(#qe:REPLY=\*:)'
+ '(#qe:"reply=( \* )":)' '(#qe":reply=( \* ):")' '(#qe:reply=( \\\* ):)'
+ '(#qe|"reply=( \* )"|)' '(#qe"|reply=( \* )|")' '(#qe|reply=( \\\* )|)'
+ '(#qe?"reply=( \* )"?)' '(#qe"?reply=( \* )?")' '(#qe?reply=( \\\* )?)'
+ '(#qe["reply=( \* )"])' '(#qe"[reply=( \* )]")' '(#qe[reply=( \\\* )])'
+ '(#qe("reply=( \* )"))' '(#qe"(reply=( \* ))")' '(#qe(reply=( \\\* )))'
+ '(#qe*"reply=( \* )"*)' '(#qe"*reply=( \* )*")' '(#qe*reply=( \\\* )*)'
+ '(#qe*"reply=( \$s )"*)' '(#qe"*reply=( \$s )*")' '(#qe*reply=( \$s )*)'
+ )
+ for 1 2 3 in $quals; do
+ print -r - \
+ ${ eval "echo [a]$1" 2>&1 } \
+ ${ eval "echo [a]$2" 2>&1 } \
+ ${ eval "echo [a]$3" 2>&1 }
+ done
+ )
+0:e glob qualifier delimiters, quoting, and expansion
+>* * *
+>* * *
+>* * *
+>* * *
+>* * *
+>* (eval):1: unknown file attribute: ) (eval):1: unknown file attribute: )
+>* (eval):1: unknown file attribute: *
+>* * *
+
# Careful: extendedglob off from this point.
unsetopt extendedglob
Messages sorted by:
Reverse Date,
Date,
Thread,
Author