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