From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 29317 invoked by alias); 31 May 2014 19:38:09 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: X-Seq: 32640 Received: (qmail 8871 invoked from network); 31 May 2014 19:38:03 -0000 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on f.primenet.com.au X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.2 X-Originating-IP: [86.6.157.246] X-Spam: 0 X-Authority: v=2.1 cv=TM7LSjVa c=1 sm=1 tr=0 a=BvYiZ/UW0Fmn8Wufq9dPrg==:117 a=BvYiZ/UW0Fmn8Wufq9dPrg==:17 a=NLZqzBF-AAAA:8 a=pRTl07La5iUA:10 a=uObrxnre4hsA:10 a=kj9zAlcOel0A:10 a=pE0XoPZZ1Y_zuasFhvAA:9 a=9oV0FMDhDp9oXQ2B:21 a=PQF0sX32qn_BZ0M7:21 a=CjuIK1q_8ugA:10 a=_dQi-Dcv4p4A:10 Date: Sat, 31 May 2014 20:38:00 +0100 From: Peter Stephenson To: "Zsh Hackers' List" Subject: Re: globbing in conditional expressions Message-ID: <20140531203800.154fc92d@pws-pc.ntlworld.com> In-Reply-To: <20140530201942.580abc43@pws-pc.ntlworld.com> References: <20140507154407.660eb500@pwslap01u.europe.root.pri> <20140508105522.GE2052@tarsus.local2> <20140508122045.3c68c3fa@pwslap01u.europe.root.pri> <140508083418.ZM14713@torch.brasslantern.com> <20140508201936.GB53652@isis.sigpipe.cz> <140513084117.ZM22925@torch.brasslantern.com> <20140514041908.GF2471@tarsus.local2> <140514001819.ZM23478@torch.brasslantern.com> <20140515092901.GC2174@tarsus.local2> <140515075003.ZM28035@torch.brasslantern.com> <20140526235216.GC1920@tarsus.local2> <140529205956.ZM17410@torch.brasslantern.com> <20140530094752.4a116629@pwslap01u.europe.root.pri> <140530085542.ZM18304@torch.brasslantern.com> <20140530195734.1d9c5310@pws-pc.ntlworld.com> <20140530201942.580abc43@pws-pc.ntlworld.com> X-Mailer: Claws Mail 3.8.0 (GTK+ 2.24.7; x86_64-redhat-linux-gnu) Mime-Version: 1.0 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit On Fri, 30 May 2014 20:19:42 +0100 Peter Stephenson wrote: > On Fri, 30 May 2014 19:57:34 +0100 > Peter Stephenson wrote: > > One thing we could do is make [[ ... = ... ]] expand its right hand > > argument the same way as normal command arguments when told to do so, so > > there's no retokenization. > > No reason not to do this for all string arguments, I suppose --- there's > nothing here that's specific to pattern matching. Here's a finalish patch with test and documentation, if anyone wants to comment. Haven't thought of a good reason not to do this, myself. diff --git a/Doc/Zsh/cond.yo b/Doc/Zsh/cond.yo index 26c0eaa..d04ceb2 100644 --- a/Doc/Zsh/cond.yo +++ b/Doc/Zsh/cond.yo @@ -196,8 +196,34 @@ where possible. Normal shell expansion is performed on the var(file), var(string) and var(pattern) arguments, but the result of each expansion is constrained to be a single word, similar to the effect of double quotes. -Filename generation is not performed on any form of argument to conditions. -However, pattern metacharacters are active for the var(pattern) arguments; + +Filename generation is not performed on any form of argument to +conditions. However, it can be forced in any case where normal shell +expansion is valid and when the option tt(EXTENDED_GLOB) is in effect by +using an explicit glob qualifier of the form tt(LPAR()#q+RPAR()) at the +end of the string. A normal glob qualifier expression may appear +between the `tt(q)' and the closing parenthesis; if none appears the +expression has no effect beyond causing filename generation. The +results of filename generation are joined together to form a single +word, as with the results of other forms of expansion. + +This special use of filename generation is only available with the +tt([[) syntax. If the condition occurs within the tt([) or tt(test) +builtin commands then globbing occurs instead as part of normal command +line expansion before the condition is evaluated. In this case it may +generate multiple words which are likely to confuse the syntax of the +test command. + +For example, + +tt([[ -n file*(#qN) ]]) + +produces status zero if and only if there is at least one file in the +current directory beginning with the string `tt(file)'. The globbing +qualifier tt(N) ensures that the expression is empty if there is +no matching file. + +Pattern metacharacters are active for the var(pattern) arguments; the patterns are the same as those used for filename generation, see ifzman(\ zmanref(zshexpn)\ diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index de0f454..6de73ea 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -2305,7 +2305,11 @@ contained within it are balanced; appearance of `tt(|)', `tt(LPAR())' or recognised in this form even if a bare glob qualifier exists at the end of the pattern, for example `tt(*(#q*)(.))' will recognise executable regular files if both options are set; however, mixed syntax should probably be -avoided for the sake of clarity. +avoided for the sake of clarity. Note that within conditions using the +`tt([[)' form the presence of a parenthesised expression +tt(LPAR()#q...+RPAR()) at the end of a string indicates that globbing +should be performed; the expression may include glob qualifiers, but +it is also valid if it is simply tt(LPAR()#q+RPAR()). A qualifier may be any one of the following: diff --git a/NEWS b/NEWS index e4d747e..87e67fd 100644 --- a/NEWS +++ b/NEWS @@ -58,6 +58,13 @@ between the right hand side of the screen (this causes problems with some terminals). It is not special and is not set by default; the effect in that case is as if it was 1, as in previous versions. +If the option EXTENDED_GLOB is in effect, it is possible to force +globbing within conditional code using the [[ ... ]] syntax by flagging +that a certain string is a glob using the (#q) glob qualifier syntax. +The resulting glob is treated as a single argument. For example, +[[ -n *.c(#qN) ]] tests whether there are any .c files in the current +directory. + Changes between 4.2 and 5.0.0 ----------------------------- diff --git a/Src/cond.c b/Src/cond.c index c673542..6e9b558 100644 --- a/Src/cond.c +++ b/Src/cond.c @@ -37,6 +37,21 @@ static char *condstr[COND_MOD] = { "-ne", "-lt", "-gt", "-le", "-ge", "=~" }; +static void cond_subst(char **strp, int glob_ok) +{ + if (glob_ok && + checkglobqual(*strp, strlen(*strp), 1, NULL)) { + LinkList args = newlinklist(); + addlinknode(args, *strp); + prefork(args, 0); + while (!errflag && args && nonempty(args) && + has_token((char *)peekfirst(args))) + zglob(args, firstnode(args), 0); + *strp = sepjoin(hlinklist2array(args, 0), NULL, 1); + } else + singsub(strp); +} + /* * Evaluate a conditional expression given the arguments. * If fromtest is set, the caller is the test or [ builtin; @@ -177,13 +192,13 @@ evalcond(Estate state, char *fromtest) } left = ecgetstr(state, EC_DUPTOK, &htok); if (htok) { - singsub(&left); + cond_subst(&left, !fromtest); untokenize(left); } if (ctype <= COND_GE && ctype != COND_STREQ && ctype != COND_STRNEQ) { right = ecgetstr(state, EC_DUPTOK, &htok); if (htok) { - singsub(&right); + cond_subst(&right, !fromtest); untokenize(right); } } @@ -194,7 +209,7 @@ evalcond(Estate state, char *fromtest) fprintf(xtrerr, " %s ", condstr[ctype]); if (ctype == COND_STREQ || ctype == COND_STRNEQ) { char *rt = dupstring(ecrawstr(state->prog, state->pc, NULL)); - singsub(&rt); + cond_subst(&rt, !fromtest); quote_tokenized_output(rt, xtrerr); } else @@ -283,7 +298,7 @@ evalcond(Estate state, char *fromtest) right = dupstring(opat = ecrawstr(state->prog, state->pc, &htok)); if (htok) - singsub(&right); + cond_subst(&right, !fromtest); save = (!(state->prog->flags & EF_HEAP) && !strcmp(opat, right) && pprog != dummy_patprog2); @@ -517,17 +532,6 @@ cond_val(char **args, int num) } /**/ -mod_export int -cond_match(char **args, int num, char *str) -{ - char *s = args[num]; - - singsub(&s); - - return matchpat(str, s); -} - -/**/ static void tracemodcond(char *name, char **args, int inf) { diff --git a/Src/glob.c b/Src/glob.c index 07dd7c2..57d7f99 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -1061,6 +1061,65 @@ insert_glob_match(LinkList list, LinkNode next, char *data) insertlinknode(list, next, data); } +/* + * Return + * 1 if str ends in bare glob qualifiers + * 2 if str ends in non-bare glob qualifiers (#q) + * 0 otherwise. + * + * str is the string to check. + * sl is it's length (to avoid recalculation). + * nobareglob is 1 if bare glob qualifiers are not allowed. + * *sp, if sp is not null, will be a pointer to the opening parenthesis. + */ + +/**/ +int +checkglobqual(char *str, int sl, int nobareglob, char **sp) +{ + char *s; + int paren, ret = 1; + + if (str[sl - 1] != Outpar) + return 0; + + /* Check these are really qualifiers, not a set of * + * alternatives or exclusions. We can be more * + * lenient with an explicit (#q) than with a bare * + * set of qualifiers. */ + paren = 0; + for (s = str + sl - 2; *s && (*s != Inpar || paren); s--) { + switch (*s) { + case Outpar: + paren++; /*FALLTHROUGH*/ + case Bar: + if (!zpc_disables[ZPC_BAR]) + nobareglob = 1; + break; + case Tilde: + if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_TILDE]) + nobareglob = 1; + break; + case Inpar: + paren--; + break; + } + } + if (*s != Inpar) + return 0; + if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH] && s[1] == Pound) { + if (s[2] != 'q') + return 0; + ret = 2; + } else if (nobareglob) + return 0; + + if (sp) + *sp = s; + + return ret; +} + /* Main entry point to the globbing code for filename globbing. * * np points to a node in the list list which will be expanded * * into a series of nodes. */ @@ -1118,7 +1177,7 @@ zglob(LinkList list, LinkNode np, int nountok) (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH])) { struct qual *newquals; char *s; - int sense, paren; + int sense, qualsfound; off_t data; char *sdata, *newcolonmod; int (*func) _((char *, Statptr, off_t, char *)); @@ -1148,40 +1207,7 @@ zglob(LinkList list, LinkNode np, int nountok) newquals = qo = qn = ql = NULL; sl = strlen(str); - if (str[sl - 1] != Outpar) - break; - - /* Check these are really qualifiers, not a set of * - * alternatives or exclusions. We can be more * - * lenient with an explicit (#q) than with a bare * - * set of qualifiers. */ - paren = 0; - for (s = str + sl - 2; *s && (*s != Inpar || paren); s--) { - switch (*s) { - case Outpar: - paren++; /*FALLTHROUGH*/ - case Bar: - if (!zpc_disables[ZPC_BAR]) - nobareglob = 1; - break; - case Tilde: - if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_TILDE]) - nobareglob = 1; - break; - case Inpar: - paren--; - break; - } - } - if (*s != Inpar) - break; - if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH] && s[1] == Pound) { - if (s[2] == 'q') { - *s = 0; - s += 2; - } else - break; - } else if (nobareglob) + if (!(qualsfound = checkglobqual(str, sl, nobareglob, &s))) break; /* Real qualifiers found. */ @@ -1194,6 +1220,8 @@ zglob(LinkList list, LinkNode np, int nountok) str[sl-1] = 0; *s++ = 0; + if (qualsfound == 2) + s += 2; while (*s && !newcolonmod) { func = (int (*) _((char *, Statptr, off_t, char *)))0; if (idigit(*s)) { diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst index 1f8f652..3e1ea82 100644 --- a/Test/D02glob.ztst +++ b/Test/D02glob.ztst @@ -526,3 +526,18 @@ >+bus+bus matches +(+bus|-car) >@sinhats matches @(@sinhats|wrensinfens) >!kerror matches !(!somethingelse) + + ( + setopt extendedglob + cd glob.tmp + [[ -n a*(#qN) ]] && print File beginning with a + [[ -z z*(#qN) ]] && print No file beginning with z + [[ "a b c" = ?(#q) ]] && print Multiple files matched + setopt nonomatch + [[ -n z*(#q) ]] && print Normal string if nullglob not set + ) +0:Force glob expansion in conditions using (#q) +>File beginning with a +>No file beginning with z +>Multiple files matched +>Normal string if nullglob not set -- Peter Stephenson Web page now at http://homepage.ntlworld.com/p.w.stephenson/