* globbing in conditional expressions @ 2014-05-07 12:41 Roman Neuhauser 2014-05-07 14:44 ` Peter Stephenson ` (2 more replies) 0 siblings, 3 replies; 31+ messages in thread From: Roman Neuhauser @ 2014-05-07 12:41 UTC (permalink / raw) To: zsh-users hello, i was hoping i could combine conditional expressions with some globbing, but i'm struggling to find the proper syntax. : 0 > ls fubar snafu : 0 > f=fubar(/F) : 0 > echo $f fubar(/F) : 0 > echo $~f fubar : 0 > (set -x; [[ -z $~f ]]) +zsh:183> [[ -z 'fubar(/F)' ]] : 1 > i'm sure this is possible and documented, and i've just failed to put the pieces together. -- roman ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-07 12:41 globbing in conditional expressions Roman Neuhauser @ 2014-05-07 14:44 ` Peter Stephenson 2014-05-08 10:55 ` Daniel Shahaf 2014-05-07 14:57 ` Mikael Magnusson 2014-05-07 15:03 ` Bart Schaefer 2 siblings, 1 reply; 31+ messages in thread From: Peter Stephenson @ 2014-05-07 14:44 UTC (permalink / raw) To: zsh-users On Wed, 07 May 2014 14:41:01 +0200 Roman Neuhauser <neuhauser@sigpipe.cz> wrote: > i was hoping i could combine conditional expressions with > some globbing, but i'm struggling to find the proper syntax. You need to do the globbing first. In conditional expressions you get single-word expansion that doesn't do globbing. > : 0 > ls fubar > snafu > : 0 > f=fubar(/F) > : 0 > echo $f > fubar(/F) > : 0 > echo $~f > fubar > : 0 > (set -x; [[ -z $~f ]]) You'll need something like the following --- the (N) flag is needed if you want the string to be empty if there's no match (unless you have the NULL_GLOB option set, which isn't standard). matches=(fubar(N/F)) [[ -z $matches ]] pws ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-07 14:44 ` Peter Stephenson @ 2014-05-08 10:55 ` Daniel Shahaf 2014-05-08 11:20 ` Peter Stephenson 0 siblings, 1 reply; 31+ messages in thread From: Daniel Shahaf @ 2014-05-08 10:55 UTC (permalink / raw) To: zsh-users Peter Stephenson wrote on Wed, May 07, 2014 at 15:44:07 +0100: > matches=(fubar(N/F)) > [[ -z $matches ]] All three solutions first set an array to the result of the glob. Is there a solution that short-circuits, i.e., that returns as soon as a single matching filename is found? This matters when the glob is matched against a large number of files. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-08 10:55 ` Daniel Shahaf @ 2014-05-08 11:20 ` Peter Stephenson 2014-05-08 15:34 ` Bart Schaefer 0 siblings, 1 reply; 31+ messages in thread From: Peter Stephenson @ 2014-05-08 11:20 UTC (permalink / raw) To: zsh-users On Thu, 08 May 2014 10:55:22 +0000 Daniel Shahaf <d.s@daniel.shahaf.name> wrote: > Peter Stephenson wrote on Wed, May 07, 2014 at 15:44:07 +0100: > > matches=(fubar(N/F)) > > [[ -z $matches ]] > > All three solutions first set an array to the result of the glob. Is > there a solution that short-circuits, i.e., that returns as soon as a > single matching filename is found? > > This matters when the glob is matched against a large number of files. No, unless someone remembers I feature I don't know about, because internally the glob proceeds until it's finished --- so even if you use ([1]) to select the first match, the glob has already generated all matches at that point. If you have multiple different patterns, you can do it by writing a function to loop over them and return on the first match, but for a single pattern I think you're stuck. pws ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-08 11:20 ` Peter Stephenson @ 2014-05-08 15:34 ` Bart Schaefer 2014-05-08 20:19 ` Roman Neuhauser 0 siblings, 1 reply; 31+ messages in thread From: Bart Schaefer @ 2014-05-08 15:34 UTC (permalink / raw) To: zsh-users On May 8, 12:20pm, Peter Stephenson wrote: } } > All three solutions first set an array to the result of the glob. Is } > there a solution that short-circuits, i.e., that returns as soon as a } > single matching filename is found? } } No, unless someone remembers I feature I don't know about, because } internally the glob proceeds until it's finished Well ... you can do this horrible hack: found= # Stop after finding exactly one directory, for example: : **/*(e+'[[ -d $REPLY ]] && { found=$REPLY ; kill -INT $$ }'+) print $found Of course to make this "useful" all the stuff that you'd test in the glob qualifiers you instead need to test in the [[ ... ]] before you assign found=$REPLY, probably by using the zsh/stat module; and you can't avoid scanning an unknown number of potential NON-matches up to the point where $found gets set, so in the worst case it's no better (and probably much worse) than just letting the glob finish. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-08 15:34 ` Bart Schaefer @ 2014-05-08 20:19 ` Roman Neuhauser 2014-05-13 15:41 ` Bart Schaefer 0 siblings, 1 reply; 31+ messages in thread From: Roman Neuhauser @ 2014-05-08 20:19 UTC (permalink / raw) To: Bart Schaefer; +Cc: zsh-users # schaefer@brasslantern.com / 2014-05-08 08:34:18 -0700: > On May 8, 12:20pm, Peter Stephenson wrote: > } > } > All three solutions first set an array to the result of the glob. Is > } > there a solution that short-circuits, i.e., that returns as soon as a > } > single matching filename is found? > } > } No, unless someone remembers I feature I don't know about, because > } internally the glob proceeds until it's finished > > Well ... you can do this horrible hack: > > found= > # Stop after finding exactly one directory, for example: > : **/*(e+'[[ -d $REPLY ]] && { found=$REPLY ; kill -INT $$ }'+) > print $found > > Of course to make this "useful" all the stuff that you'd test in the > glob qualifiers you instead need to test in the [[ ... ]] before you > assign found=$REPLY, probably by using the zsh/stat module; and you > can't avoid scanning an unknown number of potential NON-matches up > to the point where $found gets set, so in the worst case it's no > better (and probably much worse) than just letting the glob finish. maybe a crazy idea... how about something like [[ -m pattern ]] which would succeed iff pattern matched at least one path? this could be somewhat more amenable to shortcircuiting. -- roman ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-08 20:19 ` Roman Neuhauser @ 2014-05-13 15:41 ` Bart Schaefer 2014-05-14 4:19 ` Daniel Shahaf 0 siblings, 1 reply; 31+ messages in thread From: Bart Schaefer @ 2014-05-13 15:41 UTC (permalink / raw) To: zsh-users On May 8, 10:19pm, Roman Neuhauser wrote: } } maybe a crazy idea... how about something like [[ -m pattern ]] which } would succeed iff pattern matched at least one path? this could be } somewhat more amenable to shortcircuiting. Returning to this after a bit of a detour through [[ ... ]] expression parsing: You could define (via zmodload) an operator that applies filename generation to its argument, but changes to the internals of globbing would be needed to make a short-circuit happen. Then those changes would have to be exposed somehow so that the operator could use them. As I mentioned before, in the case of the match failing this would be exactly as expensive as not short-circuiting. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-13 15:41 ` Bart Schaefer @ 2014-05-14 4:19 ` Daniel Shahaf 2014-05-14 7:18 ` Bart Schaefer ` (2 more replies) 0 siblings, 3 replies; 31+ messages in thread From: Daniel Shahaf @ 2014-05-14 4:19 UTC (permalink / raw) To: Bart Schaefer; +Cc: zsh-users [-- Attachment #1: Type: text/plain, Size: 1330 bytes --] Bart Schaefer wrote on Tue, May 13, 2014 at 08:41:17 -0700: > On May 8, 10:19pm, Roman Neuhauser wrote: > } > } maybe a crazy idea... how about something like [[ -m pattern ]] which > } would succeed iff pattern matched at least one path? this could be > } somewhat more amenable to shortcircuiting. > > Returning to this after a bit of a detour through [[ ... ]] expression > parsing: > > You could define (via zmodload) an operator that applies filename > generation to its argument, but changes to the internals of globbing > would be needed to make a short-circuit happen. Then those changes > would have to be exposed somehow so that the operator could use them. > I've taken a shot at making those changes, see attached. This version just adds a [[ -m $pattern ]] rather than a zmodload operator, but I'm sure it could be converted to using zmodload. (Aside: where is the documentation of internal functions in the C source? It isn't the *.c/*.h files where I looked for it.) > As I mentioned before, in the case of the match failing this would be > exactly as expensive as not short-circuiting. Right, so short-circuiting would only be useful for callers that want to know whether a pattern matches a large directory, but don't care about having a list of matching filenames. Does anyone have such a use-case? [-- Attachment #2: 0003-Add-m-pattern-that-short-circuits.patch --] [-- Type: text/x-patch, Size: 13275 bytes --] >From 95dfc45753601eeb1d406a640c03434fcbc61704 Mon Sep 17 00:00:00 2001 From: Daniel Shahaf <d.s@daniel.shahaf.name> Date: Tue, 13 May 2014 10:18:56 +0000 Subject: [PATCH 3/3] Add [[ -m pattern ]] that short-circuits. --- Doc/Zsh/cond.yo | 3 ++ Src/Zle/compctl.c | 8 ++--- Src/Zle/zle_tricky.c | 2 +- Src/cond.c | 13 +++++++++ Src/exec.c | 8 ++--- Src/glob.c | 79 +++++++++++++++++++++++++++++++------------------- Src/parse.c | 4 +-- Src/subst.c | 8 +++-- Test/C02cond.ztst | 3 ++ 9 files changed, 84 insertions(+), 44 deletions(-) diff --git a/Doc/Zsh/cond.yo b/Doc/Zsh/cond.yo index 9f8a7d8..ac07cf4 100644 --- a/Doc/Zsh/cond.yo +++ b/Doc/Zsh/cond.yo @@ -38,6 +38,9 @@ true if var(file) exists and is a symbolic link. item(tt(-k) var(file))( true if var(file) exists and has its sticky bit set. ) +item(tt(-m) var(pattern))( +true if var(pattern) has any matches. Short-circuits. +) item(tt(-n) var(string))( true if length of var(string) is non-zero. ) diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c index 2563095..7565142 100644 --- a/Src/Zle/compctl.c +++ b/Src/Zle/compctl.c @@ -2228,7 +2228,7 @@ gen_matches_files(int dirs, int execs, int all) /* Do the globbing... */ remnulargs(p); addlinknode(l, p); - globlist(l, 0); + globlist(l, 0, 0); /* And see if that produced a filename. */ tt = nonempty(l); while (ugetnode(l)); @@ -3384,7 +3384,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) tokenize(p); remnulargs(p); addlinknode(l, p); - globlist(l, 0); + globlist(l, 0, 0); if (nonempty(l)) { /* And add the resulting words. */ @@ -3533,7 +3533,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) /* Do the globbing. */ ng = opts[NULLGLOB]; opts[NULLGLOB] = 1; - globlist(l, 0); + globlist(l, 0, 0); opts[NULLGLOB] = ng; /* Get the results. */ if (nonempty(l) && peekfirst(l)) { @@ -3730,7 +3730,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) /* Fine, now do full expansion. */ prefork(foo, 0); if (!errflag) { - globlist(foo, 0); + globlist(foo, 0, 0); if (!errflag) /* And add the resulting words as matches. */ for (n = firstnode(foo); n; incnode(n)) diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c index 499c4ae..c4f97a8 100644 --- a/Src/Zle/zle_tricky.c +++ b/Src/Zle/zle_tricky.c @@ -2197,7 +2197,7 @@ doexpansion(char *s, int lst, int olst, int explincmd) int ng = opts[NULLGLOB]; opts[NULLGLOB] = 1; - globlist(vl, 1); + globlist(vl, 0, 1); opts[NULLGLOB] = ng; } if (errflag) diff --git a/Src/cond.c b/Src/cond.c index c673542..b5f162a 100644 --- a/Src/cond.c +++ b/Src/cond.c @@ -319,6 +319,19 @@ evalcond(Estate state, char *fromtest) return (!(dostat(left) & S_ISGID)); case 'k': return (!(dostat(left) & S_ISVTX)); + case 'm': + { + LinkList globber = newsizedlist(2); + tokenize(left); + setsizednode(globber, 0, ""); + setsizednode(globber, 1, left); +#if 1 + return (!globlist(globber, 1 /* short circuit */, 1 /* nountok */)); +#else + globlist(globber, 0 /* short circuit */, 1 /* nountok */); + return (!nextnode(firstnode(globber))); +#endif + } case 'n': return (!strlen(left)); case 'o': diff --git a/Src/exec.c b/Src/exec.c index 8249def..aa381e9 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -2237,7 +2237,7 @@ addvars(Estate state, Wordcode pc, int addflags) return; } if (isset(GLOBASSIGN) || !isstr) - globlist(vl, 0); + globlist(vl, 0, 0); if (errflag) { state->pc = opc; return; @@ -2348,7 +2348,7 @@ execsubst(LinkList strs) prefork(strs, esprefork); if (esglob && !errflag) { LinkList ostrs = strs; - globlist(strs, 0); + globlist(strs, 0, 0); strs = ostrs; } } @@ -2621,7 +2621,7 @@ execcmd(Estate state, int input, int output, int how, int last1) if (!(cflags & BINF_NOGLOB)) while (!checked && !errflag && args && nonempty(args) && has_token((char *) peekfirst(args))) - zglob(args, firstnode(args), 0); + zglob(args, firstnode(args), 0, 0); else if (!unglobbed) { for (node = firstnode(args); node; incnode(node)) untokenize((char *) getdata(node)); @@ -2942,7 +2942,7 @@ execcmd(Estate state, int input, int output, int how, int last1) if ((esglob = !(cflags & BINF_NOGLOB)) && args && htok) { LinkList oargs = args; - globlist(args, 0); + globlist(args, 0, 0); args = oargs; } if (errflag) { diff --git a/Src/glob.c b/Src/glob.c index 07dd7c2..1a521a5 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -452,8 +452,8 @@ insert(char *s, int checked) * tried all of it. */ /**/ -static void -scanner(Complist q) +static int +scanner(Complist q, int shortcircuit) { Patprog p; int closure; @@ -463,14 +463,15 @@ scanner(Complist q) init_dirsav(&ds); if (!q) - return; + return -1; if ((closure = q->closure)) { /* (foo/)# - match zero or more dirs */ if (q->closure == 2) /* (foo/)## - match one or more dirs */ q->closure = 1; else - scanner(q->next); + if (scanner(q->next, shortcircuit) == 1) + return 1; } p = q->pat; /* Now the actual matching for the current path section. */ @@ -485,13 +486,13 @@ scanner(Complist q) int err; if (l >= PATH_MAX) - return; + return -1; err = lchdir(pathbuf + pathbufcwd, &ds, 0); if (err == -1) - return; + return -1; if (err) { zerr("current directory lost during glob"); - return; + return -1; } pathbufcwd = pathpos; } @@ -516,14 +517,18 @@ scanner(Complist q) if (add) { addpath(str, l); if (!closure || !statfullpath("", NULL, 1)) - scanner((q->closure) ? q : q->next); + if (scanner((q->closure) ? q : q->next, shortcircuit) == 1) + return 1; pathbuf[pathpos = oppos] = '\0'; } } } else { if (str[l]) str = dupstrpfx(str, l); - insert(str, 0); + if (!shortcircuit) + insert(str, 0); + else + return 1; } } else { /* Do pattern matching on current path section. */ @@ -534,7 +539,7 @@ scanner(Complist q) int subdirlen = 0; if (lock == NULL) - return; + return -1; while ((fn = zreaddir(lock, 1)) && !errflag) { /* prefix and suffix are zle trickery */ if (!dirs && !colonmod && @@ -613,7 +618,10 @@ scanner(Complist q) subdirlen += sizeof(int); } else /* if the last filename component, just add it */ - insert(fn, 1); + if (!shortcircuit) + insert(fn, 1); + else + return 1; } } closedir(lock); @@ -626,7 +634,8 @@ scanner(Complist q) fn += l + 1; memcpy((char *)&errsfound, fn, sizeof(int)); fn += sizeof(int); - scanner((q->closure) ? q : q->next); /* scan next level */ + if (scanner((q->closure) ? q : q->next, shortcircuit) == 1) /* scan next level */ + return 1; pathbuf[pathpos = oppos] = '\0'; } hrealloc(subdirs, subdirlen, 0); @@ -640,6 +649,7 @@ scanner(Complist q) close(ds.dirfd); pathbufcwd = pbcwdsav; } + return 0; } /* This function tokenizes a zsh glob pattern */ @@ -1066,8 +1076,8 @@ insert_glob_match(LinkList list, LinkNode next, char *data) * into a series of nodes. */ /**/ -void -zglob(LinkList list, LinkNode np, int nountok) +int +zglob(LinkList list, LinkNode np, int shortcircuit, int nountok) { struct qual *qo, *qn, *ql; LinkNode node = prevnode(np); @@ -1084,7 +1094,7 @@ zglob(LinkList list, LinkNode np, int nountok) if (unset(GLOBOPT) || !haswilds(ostr) || unset(EXECOPT)) { if (!nountok) untokenize(ostr); - return; + return -1; } save_globstate(saved); @@ -1526,7 +1536,7 @@ zglob(LinkList list, LinkNode np, int nountok) if (gf_nsorts == MAX_SORTS) { zerr("too many glob sort specifiers"); restore_globstate(saved); - return; + return -1; } /* usually just one character */ @@ -1548,14 +1558,14 @@ zglob(LinkList list, LinkNode np, int nountok) glob_exec_string(&send)) == NULL) { restore_globstate(saved); - return; + return -1; } break; } default: zerr("unknown sort specifier"); restore_globstate(saved); - return; + return -1; } if (t != GS_EXEC) { if ((sense & 2) && !(t & (GS_NAME|GS_DEPTH))) @@ -1563,7 +1573,7 @@ zglob(LinkList list, LinkNode np, int nountok) if (gf_sorts & t) { zerr("doubled sort specifier"); restore_globstate(saved); - return; + return -1; } } gf_sorts |= t; @@ -1600,7 +1610,7 @@ zglob(LinkList list, LinkNode np, int nountok) if (getindex(&s, &v, 0) || s == os) { zerr("invalid subscript"); restore_globstate(saved); - return; + return -1; } first = v.start; end = v.end; @@ -1623,7 +1633,7 @@ zglob(LinkList list, LinkNode np, int nountok) untokenize(--s); zerr("unknown file attribute: %c", *s); restore_globstate(saved); - return; + return -1; } } if (func) { @@ -1648,7 +1658,7 @@ zglob(LinkList list, LinkNode np, int nountok) } if (errflag) { restore_globstate(saved); - return; + return -1; } } @@ -1710,11 +1720,11 @@ zglob(LinkList list, LinkNode np, int nountok) if (!nountok) untokenize(ostr); insertlinknode(list, node, ostr); - return; + return -1; } errflag = 0; zerr("bad pattern: %s", ostr); - return; + return -1; } if (!gf_nsorts) { gf_sortlist[0].tp = gf_sorts = GS_NAME; @@ -1722,14 +1732,22 @@ zglob(LinkList list, LinkNode np, int nountok) } /* Initialise receptacle for matched files, * * expanded by insert() where necessary. */ - matchptr = matchbuf = (Gmatch)zalloc((matchsz = 16) * - sizeof(struct gmatch)); - matchct = 0; + if (!shortcircuit) { + matchptr = matchbuf = (Gmatch)zalloc((matchsz = 16) * + sizeof(struct gmatch)); + matchct = 0; + } pattrystart(); /* The actual processing takes place here: matches go into * * matchbuf. This is the only top-level call to scanner(). */ - scanner(q); + if (scanner(q, shortcircuit) == 1) + return 1; + else if (shortcircuit) { + /* ### TODO: this returns 0 even if scanner() returned -1 */ + restore_globstate(saved); + return 0; + } /* Deal with failures to match depending on options */ if (matchct) @@ -1741,7 +1759,7 @@ zglob(LinkList list, LinkNode np, int nountok) zerr("no matches found: %s", ostr); free(matchbuf); restore_globstate(saved); - return; + return -1; } else { /* treat as an ordinary string */ untokenize(matchptr->name = dupstring(ostr)); @@ -1844,6 +1862,7 @@ zglob(LinkList list, LinkNode np, int nountok) free(matchbuf); restore_globstate(saved); + return 0; } /* Return the trailing character for marking file types */ @@ -1995,7 +2014,7 @@ xpandredir(struct redir *fn, LinkList redirtab) prefork(&fake, isset(MULTIOS) ? 0 : PREFORK_SINGLE); /* Globbing is only done for multios. */ if (!errflag && isset(MULTIOS)) - globlist(&fake, 0); + globlist(&fake, 0, 0); if (errflag) return 0; if (nonempty(&fake) && !nextnode(firstnode(&fake))) { diff --git a/Src/parse.c b/Src/parse.c index 530a070..52b3218 100644 --- a/Src/parse.c +++ b/Src/parse.c @@ -2124,7 +2124,7 @@ par_cond_2(void) } s1 = tokstr; if (condlex == testlex) - dble = (*s1 == '-' && strspn(s1+1, "abcdefghknoprstuwxzLONGS") == 1 + dble = (*s1 == '-' && strspn(s1+1, "abcdefghkmnoprstuwxzLONGS") == 1 && !s1[2]); condlex(); if (tok == INANG || tok == OUTANG) { @@ -2178,7 +2178,7 @@ par_cond_double(char *a, char *b) { if (a[0] != '-' || !a[1]) COND_ERROR("parse error: condition expected: %s", a); - else if (!a[2] && strspn(a+1, "abcdefgknoprstuwxzhLONGS") == 1) { + else if (!a[2] && strspn(a+1, "abcdefghkmnoprstuwxzLONGS") == 1) { ecadd(WCB_COND(a[1], 0)); ecstr(b); } else { diff --git a/Src/subst.c b/Src/subst.c index 4713502..9ab04b3 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -370,18 +370,20 @@ quotesubst(char *str) } /**/ -mod_export void -globlist(LinkList list, int nountok) +mod_export int +globlist(LinkList list, int shortcircuit, int nountok) { LinkNode node, next; badcshglob = 0; for (node = firstnode(list); !errflag && node; node = next) { next = nextnode(node); - zglob(list, node, nountok); + if (zglob(list, node, shortcircuit, nountok) == 1) + return 1; } if (badcshglob == 1) zerr("no match"); + return 0; } /* perform substitution on a single word */ diff --git a/Test/C02cond.ztst b/Test/C02cond.ztst index 94fca8b..0c6cfbf 100644 --- a/Test/C02cond.ztst +++ b/Test/C02cond.ztst @@ -70,6 +70,9 @@ [[ -k modish && ! -k zerolength ]] 0:-k cond + [[ -m zero* ]] && [[ ! -m *nonexistent* ]] +0:-m cond + foo=foo bar= [[ -n $foo && ! -n $bar && ! -n '' ]] -- 1.7.10.4 ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-14 4:19 ` Daniel Shahaf @ 2014-05-14 7:18 ` Bart Schaefer 2014-05-15 4:55 ` Bart Schaefer 2014-05-15 9:29 ` Daniel Shahaf 2014-05-14 7:42 ` Christoph (Stucki) von Stuckrad 2014-05-14 21:09 ` Roman Neuhauser 2 siblings, 2 replies; 31+ messages in thread From: Bart Schaefer @ 2014-05-14 7:18 UTC (permalink / raw) To: zsh-users On May 14, 4:19am, Daniel Shahaf wrote: } } > You could define (via zmodload) an operator that applies filename } > generation to its argument, but changes to the internals of globbing } > would be needed to make a short-circuit happen. Then those changes } > would have to be exposed somehow so that the operator could use them. } } I've taken a shot at making those changes, see attached. Interesting! A few thoughts just reading through the patch: I'm very curious why you chose to insert a new parameter in the middle of the globlist() and zglob() signatures, instead of appending it. The way we'd have typically done this in the past would be to keep the old call signature as it was and introduce a new entry point with the new signature, to isolate the changes to as few files as possible. This also makes it easier to match meaningful changes to the commits that made them, given that nearly all of the changes in the calls are not changes of semantics. (The main body of the old function would become the body for the new call signature, to which the old signature would then call through. On the other hand, that does lead to some clutter, so I don't really feel that strongly either way.) The strspn(..., "abcdefghkmnoprstuwxzLONGS") probably should NOT have the "m" added to them, because those are intended to be identifying operators that are defined by POSIX "test". It'd be a bit difficult to use [ -m ... ] anyway because the glob pattern would expand before the call to the test builtin. Do I read correctly that "shortcircuit" also means "don't return any file names, just return an indication of whether there is such a file" ?? (Since you don't allocate the matchbuf array when shortcircuit.) If so, there might be a more descriptive name for it, "search_only" or something. The test [[ -m zero* ]] && [[ ! -m *nonexistent* ]] could be written [[ -m zero* && ! -m *nonexistent* ]] -- if that doesn't parse (I see no reason to think it wouldn't) then something is wrong. Did you check path searches like **/zero* and whether glob flags work right? I may give this patch a try tomorrow sometime. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-14 7:18 ` Bart Schaefer @ 2014-05-15 4:55 ` Bart Schaefer 2014-05-15 9:35 ` Daniel Shahaf 2014-05-15 9:29 ` Daniel Shahaf 1 sibling, 1 reply; 31+ messages in thread From: Bart Schaefer @ 2014-05-15 4:55 UTC (permalink / raw) To: zsh-users On May 14, 12:18am, Bart Schaefer wrote: } Subject: Re: globbing in conditional expressions } } On May 14, 4:19am, Daniel Shahaf wrote: } } } } I've taken a shot at making those changes, see attached. } } The strspn(..., "abcdefghkmnoprstuwxzLONGS") probably should NOT have } the "m" added to them, because those are intended to be identifying } operators that are defined by POSIX "test". Upon testing, the strspn in par_cond_double() needs to have the "m". It can be left out of strspn() in par_cond_2(). } to use [ -m ... ] anyway because the glob pattern would expand before } the call to the test builtin. Playing with this, I find that you can quote the pattern [ -m 'pat' ]. However, quoting the pattern in [[ -m 'pat' ]] does not have the expected effect -- a glob is performed even though the pattern was quoted, so there's no way to make ? * [ ] match literally. Perhaps not a show-stopper. } check path searches like **/zero* and whether glob flags work right? I tried this and it appears glob patterns confuse things: torch% ls b* zsh: no matches found: b* torch% [[ -m b ]] && print OK torch% [[ -m b(/) ]] && print OK OK So, still a few issues to iron out. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-15 4:55 ` Bart Schaefer @ 2014-05-15 9:35 ` Daniel Shahaf 2014-05-15 14:33 ` Bart Schaefer 0 siblings, 1 reply; 31+ messages in thread From: Daniel Shahaf @ 2014-05-15 9:35 UTC (permalink / raw) To: Bart Schaefer; +Cc: zsh-users Bart Schaefer wrote on Wed, May 14, 2014 at 21:55:57 -0700: > On May 14, 12:18am, Bart Schaefer wrote: > } to use [ -m ... ] anyway because the glob pattern would expand before > } the call to the test builtin. > > Playing with this, I find that you can quote the pattern [ -m 'pat' ]. > However, quoting the pattern in [[ -m 'pat' ]] does not have the > expected effect -- a glob is performed even though the pattern was > quoted, so there's no way to make ? * [ ] match literally. Perhaps > not a show-stopper. > > } check path searches like **/zero* and whether glob flags work right? > > I tried this and it appears glob patterns confuse things: ... > So, still a few issues to iron out. Indeed. Thanks for taking the time to test drive this. I don't have much time for this; if I make progress I'll post a new iteration to the list, but in the meantime, perhaps someone else interested in the patch could pick it up and make a new version. Cheers, Daniel ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-15 9:35 ` Daniel Shahaf @ 2014-05-15 14:33 ` Bart Schaefer 0 siblings, 0 replies; 31+ messages in thread From: Bart Schaefer @ 2014-05-15 14:33 UTC (permalink / raw) To: zsh-users On May 15, 9:35am, Daniel Shahaf wrote: } } Bart Schaefer wrote on Wed, May 14, 2014 at 21:55:57 -0700: } > } > I tried this and it appears glob patterns confuse things: } } Indeed. Thanks for taking the time to test drive this. I'm 90% sure that the problem with the glob flags has to do with short- circuiting at the wrong place, but I haven't tried very hard to find which is wrong or where is right. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-14 7:18 ` Bart Schaefer 2014-05-15 4:55 ` Bart Schaefer @ 2014-05-15 9:29 ` Daniel Shahaf 2014-05-15 14:50 ` Bart Schaefer 1 sibling, 1 reply; 31+ messages in thread From: Daniel Shahaf @ 2014-05-15 9:29 UTC (permalink / raw) To: Bart Schaefer; +Cc: zsh-users Bart Schaefer wrote on Wed, May 14, 2014 at 00:18:19 -0700: > Do I read correctly that "shortcircuit" also means "don't return any > file names, just return an indication of whether there is such a file" > ?? (Since you don't allocate the matchbuf array when shortcircuit.) > Of course. As soon as you find a single matching filename you return to the caller, so there are two options: either return just a boolean, or return the first matching filename in readdir() order (via $REPLY, as in Roman's post). I'm not sure how idiomatic it would be for a [[ -x ]] condition to set $REPLY. > If so, there might be a more descriptive name for it, "search_only" > or something. I don't really how it's called. Some more options are "list_matches" (default TRUE) or "boolean_context" (default FALSE). I'm fine with any choice. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-15 9:29 ` Daniel Shahaf @ 2014-05-15 14:50 ` Bart Schaefer 2014-05-26 23:52 ` Daniel Shahaf 0 siblings, 1 reply; 31+ messages in thread From: Bart Schaefer @ 2014-05-15 14:50 UTC (permalink / raw) To: zsh-users On May 15, 9:29am, Daniel Shahaf wrote: } Subject: Re: globbing in conditional expressions } } Bart Schaefer wrote on Wed, May 14, 2014 at 00:18:19 -0700: } > Do I read correctly that "shortcircuit" also means "don't return any } > file names, just return an indication of whether there is such a file" } > ?? (Since you don't allocate the matchbuf array when shortcircuit.) } } Of course. As soon as you find a single matching filename you return to } the caller, so there are two options: either return just a boolean, or } return the first matching filename in readdir() order (via $REPLY, as in } Roman's post). I'm not sure how idiomatic it would be for a [[ -x ]] } condition to set $REPLY. I would not suggest that [[ -m ... ]] set $REPLY, but why does -m have to be the only place where short-circuiting occurs? I could imagine adding a globbing flag (#X) [where I'm using X as a placeholder rather than a suggestion for the actual flag] which means that the glob returns only the first matching file it stumbles upon. In fact if you had that flag you wouldn't need -m as a condition op, it would suffice to do [ ! -z pat(NX) ]. The implementation also would not need to change the call signatures of any of the C functions ... ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-15 14:50 ` Bart Schaefer @ 2014-05-26 23:52 ` Daniel Shahaf 2014-05-30 3:59 ` Bart Schaefer 0 siblings, 1 reply; 31+ messages in thread From: Daniel Shahaf @ 2014-05-26 23:52 UTC (permalink / raw) To: zsh-users [-- Attachment #1: Type: text/plain, Size: 1611 bytes --] Bart Schaefer wrote on Thu, May 15, 2014 at 07:50:03 -0700: > On May 15, 9:29am, Daniel Shahaf wrote: > } Subject: Re: globbing in conditional expressions > } > } Bart Schaefer wrote on Wed, May 14, 2014 at 00:18:19 -0700: > } > Do I read correctly that "shortcircuit" also means "don't return any > } > file names, just return an indication of whether there is such a file" > } > ?? (Since you don't allocate the matchbuf array when shortcircuit.) > } > } Of course. As soon as you find a single matching filename you return to > } the caller, so there are two options: either return just a boolean, or > } return the first matching filename in readdir() order (via $REPLY, as in > } Roman's post). I'm not sure how idiomatic it would be for a [[ -x ]] > } condition to set $REPLY. > > I would not suggest that [[ -m ... ]] set $REPLY, but why does -m have > to be the only place where short-circuiting occurs? I could imagine > adding a globbing flag (#X) [where I'm using X as a placeholder rather > than a suggestion for the actual flag] which means that the glob returns > only the first matching file it stumbles upon. > > In fact if you had that flag you wouldn't need -m as a condition op, it > would suffice to do [ ! -z pat(NX) ]. The implementation also would not > need to change the call signatures of any of the C functions ... That turned out to be much less code to implement. It's attached, if anyone finds it useful. I couldn't get it to work via [ -z ], but this works: % Src/zsh -fc '() { (( $#@ )) } *(NY); echo $?' 0 % Src/zsh -fc '() { (( $#@ )) } *foobar(NY); echo $?' 1 [-- Attachment #2: 0001-Add-Y-glob-qualifier-that-short-circuits.patch --] [-- Type: text/x-patch, Size: 5342 bytes --] >From 23eafe9265691c0ea6d705100c1ee9e84850efa5 Mon Sep 17 00:00:00 2001 From: Daniel Shahaf <d.s@daniel.shahaf.name> Date: Tue, 13 May 2014 10:18:56 +0000 Subject: [PATCH] Add (Y) glob qualifier that short-circuits. --- Doc/Zsh/expn.yo | 4 ++++ Src/glob.c | 35 ++++++++++++++++++++++++----------- Test/D02glob.ztst | 11 +++++++++++ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index de0f454..65374e8 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -2560,6 +2560,10 @@ item(tt(n))( sets the tt(NUMERIC_GLOB_SORT) option for the current pattern pindex(NUMERIC_GLOB_SORT, setting in pattern) ) +item(tt(Y))( +enables short-circuit mode: the pattern will expand to just the first +matching filename, if any. +) item(tt(o)var(c))( specifies how the names of the files should be sorted. If var(c) is tt(n) they are sorted by name (the default); if it is tt(L) they diff --git a/Src/glob.c b/Src/glob.c index 07dd7c2..eafb702 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -452,8 +452,8 @@ insert(char *s, int checked) * tried all of it. */ /**/ -static void -scanner(Complist q) +static int +scanner(Complist q, int shortcircuit) { Patprog p; int closure; @@ -463,14 +463,15 @@ scanner(Complist q) init_dirsav(&ds); if (!q) - return; + return -1; if ((closure = q->closure)) { /* (foo/)# - match zero or more dirs */ if (q->closure == 2) /* (foo/)## - match one or more dirs */ q->closure = 1; else - scanner(q->next); + if (scanner(q->next, shortcircuit) == 1) + return 1; } p = q->pat; /* Now the actual matching for the current path section. */ @@ -485,13 +486,13 @@ scanner(Complist q) int err; if (l >= PATH_MAX) - return; + return -1; err = lchdir(pathbuf + pathbufcwd, &ds, 0); if (err == -1) - return; + return -1; if (err) { zerr("current directory lost during glob"); - return; + return -1; } pathbufcwd = pathpos; } @@ -516,7 +517,8 @@ scanner(Complist q) if (add) { addpath(str, l); if (!closure || !statfullpath("", NULL, 1)) - scanner((q->closure) ? q : q->next); + if (scanner((q->closure) ? q : q->next, shortcircuit) == 1) + return 1; pathbuf[pathpos = oppos] = '\0'; } } @@ -524,6 +526,8 @@ scanner(Complist q) if (str[l]) str = dupstrpfx(str, l); insert(str, 0); + if (shortcircuit) + return 1; } } else { /* Do pattern matching on current path section. */ @@ -534,7 +538,7 @@ scanner(Complist q) int subdirlen = 0; if (lock == NULL) - return; + return -1; while ((fn = zreaddir(lock, 1)) && !errflag) { /* prefix and suffix are zle trickery */ if (!dirs && !colonmod && @@ -614,6 +618,8 @@ scanner(Complist q) } else /* if the last filename component, just add it */ insert(fn, 1); + if (shortcircuit) + return 1; } } closedir(lock); @@ -626,7 +632,8 @@ scanner(Complist q) fn += l + 1; memcpy((char *)&errsfound, fn, sizeof(int)); fn += sizeof(int); - scanner((q->closure) ? q : q->next); /* scan next level */ + if (scanner((q->closure) ? q : q->next, shortcircuit) == 1) /* scan next level */ + return 1; pathbuf[pathpos = oppos] = '\0'; } hrealloc(subdirs, subdirlen, 0); @@ -640,6 +647,7 @@ scanner(Complist q) close(ds.dirfd); pathbufcwd = pbcwdsav; } + return 0; } /* This function tokenizes a zsh glob pattern */ @@ -1080,6 +1088,7 @@ zglob(LinkList list, LinkNode np, int nountok) /* and index+1 of the last match */ struct globdata saved; /* saved glob state */ int nobareglob = !isset(BAREGLOBQUAL); + int shortcircuit = 0; if (unset(GLOBOPT) || !haswilds(ostr) || unset(EXECOPT)) { if (!nountok) @@ -1461,6 +1470,10 @@ zglob(LinkList list, LinkNode np, int nountok) /* Numeric glob sort */ gf_numsort = !(sense & 1); break; + case 'Y': + /* Short circuit: just check if there are any matches */ + shortcircuit = !(sense & 1); + break; case 'a': /* Access time in given range */ g_amc = 0; @@ -1729,7 +1742,7 @@ zglob(LinkList list, LinkNode np, int nountok) /* The actual processing takes place here: matches go into * * matchbuf. This is the only top-level call to scanner(). */ - scanner(q); + scanner(q, shortcircuit); /* Deal with failures to match depending on options */ if (matchct) diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst index 1f8f652..0ff088a 100644 --- a/Test/D02glob.ztst +++ b/Test/D02glob.ztst @@ -431,6 +431,7 @@ mkdir glob.tmp/dir5 touch glob.tmp/dir5/N123 print glob.tmp/dir5/N<->(N) + rm -rf glob.tmp/dir5 0:Numeric glob is not usurped by process substitution. >glob.tmp/dir5/N123 @@ -526,3 +527,13 @@ >+bus+bus matches +(+bus|-car) >@sinhats matches @(@sinhats|wrensinfens) >!kerror matches !(!somethingelse) + + (){ print $#@ } glob.tmp/dir*(Y) + (){ print $#@ } glob.tmp/file*(NY) + (){ [[ $1 = glob.tmp/dir? ]] && echo "(Y) returns a matching filename" } glob.tmp/dir*(Y) + (){ print $@:t } glob.tmp/dir*(Y^Y) +0:short-circuit modifier +>1 +>0 +>(Y) returns a matching filename +>dir1 dir2 dir3 dir4 -- 1.7.10.4 ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-26 23:52 ` Daniel Shahaf @ 2014-05-30 3:59 ` Bart Schaefer 2014-05-30 8:47 ` Peter Stephenson 2014-05-30 14:45 ` Daniel Shahaf 0 siblings, 2 replies; 31+ messages in thread From: Bart Schaefer @ 2014-05-30 3:59 UTC (permalink / raw) To: zsh-users On May 26, 11:52pm, Daniel Shahaf wrote: } } } Bart Schaefer wrote on Thu, May 15, 2014 at 07:50:03 -0700: } > adding a globbing flag (#X) [where I'm using X as a placeholder rather } > than a suggestion for the actual flag] which means that the glob returns } > only the first matching file it stumbles upon. } > } > In fact if you had that flag you wouldn't need -m as a condition op, it } > would suffice to do [ ! -z pat(NX) ]. } } That turned out to be much less code to implement. It's attached, if } anyone finds it useful. I like this one. Is there a mnemonic for your choice of (Y) ? } I couldn't get it to work via [ -z ] Hmm, seems to work for me: torch% print co* config.h config.log config.modules config.modules.sh config.status torch% print co*(Y) config.h torch% print ba* zsh: no matches found: ba* torch% [ -z ba*(N) ] && echo OK OK torch% [ ! -z ba*(NY) ] && echo OK torch% [ ! -z co*(NY) ] && echo OK OK To zsh-workers: Any reason not to commit/push this? ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-30 3:59 ` Bart Schaefer @ 2014-05-30 8:47 ` Peter Stephenson 2014-05-30 15:55 ` Bart Schaefer 2014-05-30 14:45 ` Daniel Shahaf 1 sibling, 1 reply; 31+ messages in thread From: Peter Stephenson @ 2014-05-30 8:47 UTC (permalink / raw) To: zsh-users On Thu, 29 May 2014 20:59:56 -0700 Bart Schaefer <schaefer@brasslantern.com> wrote: > } I couldn't get it to work via [ -z ] > > Hmm, seems to work for me: > > torch% [ -z ba*(N) ] && echo OK > OK > torch% [ ! -z ba*(NY) ] && echo OK > torch% [ ! -z co*(NY) ] && echo OK > OK Is this null glob safe with [ ? I get true from both "[ -n ]" and "[ -z ]" suggesting the case of an elided argument isn't properly handled. But this is specific to zsh --- why are we using [ rather than [[ anyway? > To zsh-workers: Any reason not to commit/push this? Not that I know of. pws ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-30 8:47 ` Peter Stephenson @ 2014-05-30 15:55 ` Bart Schaefer 2014-05-31 4:43 ` Bart Schaefer 0 siblings, 1 reply; 31+ messages in thread From: Bart Schaefer @ 2014-05-30 15:55 UTC (permalink / raw) To: zsh-users On May 30, 9:47am, Peter Stephenson wrote: } } On Thu, 29 May 2014 20:59:56 -0700 } Bart Schaefer <schaefer@brasslantern.com> wrote: } > } I couldn't get it to work via [ -z ] } > } > Hmm, seems to work for me: } > } > torch% [ -z ba*(N) ] && echo OK } > OK } > torch% [ ! -z ba*(NY) ] && echo OK } > torch% [ ! -z co*(NY) ] && echo OK } > OK } } Is this null glob safe with [ ? I get true from both "[ -n ]" and } "[ -z ]" suggesting the case of an elided argument isn't properly } handled. Hm. This is a side-effect of treating [ thing ] as [ -n thing ] : [ -n ] becomes [ -n -n ] and [ -z ] becomes [ -n -z ]. Is there a non-convoluted way to have the special case of a glob with BOTH of the (NY) qualifiers return empty string rather than nularg when it finds no match? Empty string is an illegal file name so for most purposes it wouldn't otherwise affect usage. } But this is specific to zsh --- why are we using [ rather than [[ anyway? Because [[ ]] doesn't do globbing on its arguments, and this is a filename generation rather than a pattern. We messed around a few weeks ago with trying to make [[ ]] do globbing and ran into tokenization conflicts. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-30 15:55 ` Bart Schaefer @ 2014-05-31 4:43 ` Bart Schaefer 2014-05-31 17:34 ` Daniel Shahaf 0 siblings, 1 reply; 31+ messages in thread From: Bart Schaefer @ 2014-05-31 4:43 UTC (permalink / raw) To: zsh-users On May 30, 8:55am, Bart Schaefer wrote: } } Is there a non-convoluted way to have the special case of a glob with } BOTH of the (NY) qualifiers return empty string rather than nularg when } it finds no match? Empty string is an illegal file name so for most } purposes it wouldn't otherwise affect usage. This turns out to be pretty simple. It'd additionally be nice if the CSH_NULL_GLOB option caused any such empty strings to be removed when some other pattern succeeds, but that seems rather far-reaching. (The following will require a tweak to Daniel's test in D02glob.) diff --git a/Src/glob.c b/Src/glob.c index 07dd7c2..5aa306b 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -1748,6 +1761,11 @@ zglob(LinkList list, LinkNode np, int nountok) matchptr++; matchct = 1; } + } else if (shortcircuit) { + /* treat as an empty string */ + matchptr->name = dupstring(""); + matchptr++; + matchct = 1; } if (!(gf_sortlist[0].tp & GS_NONE)) { ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-31 4:43 ` Bart Schaefer @ 2014-05-31 17:34 ` Daniel Shahaf 2014-05-31 17:59 ` Bart Schaefer 0 siblings, 1 reply; 31+ messages in thread From: Daniel Shahaf @ 2014-05-31 17:34 UTC (permalink / raw) To: Bart Schaefer; +Cc: zsh-users Bart Schaefer wrote on Fri, May 30, 2014 at 21:43:20 -0700: > On May 30, 8:55am, Bart Schaefer wrote: > } > } Is there a non-convoluted way to have the special case of a glob with > } BOTH of the (NY) qualifiers return empty string rather than nularg when > } it finds no match? Empty string is an illegal file name so for most > } purposes it wouldn't otherwise affect usage. I beg to differ. Making *(NY) return empty strings upon no match is (by design) convenient for the [ -n *(NY) ] case, but it breaks other use-cases. For example, frob *.foo(NY) *.bar(NY) would suddenly need to handle "" files in the argument list. For testing whether the pattern expands to anything, this is already possible: if (){(($#@))} *(NY) and after naming the anon func, it becomes: if has_any_matches *(NY) which seems to me as readable as [ -n *(NY) ]. (For that matter, we could define a unary test operator [ --is-not-null ] that acts like -n except that it returns False rather than True in the nularg case, and write [ --is-not-null *(NY) ].) Perhaps an assignment-ish syntax would be useful? Something like if [ -n ${(X)fname:=*(NY)} ] as shorthand for: if _tmp=(*(NY)) && (( $#_tmp )) && fname=$_tmp[1] && [ -n $fname ] ? That way also makes the matching filename available for the if's body. (The X stands for an expansion modifier that would make the RHS be treated as a glob rather than as a literal string.) Cheers, Daniel > This turns out to be pretty simple. It'd additionally be nice if the > CSH_NULL_GLOB option caused any such empty strings to be removed when > some other pattern succeeds, but that seems rather far-reaching. (The > following will require a tweak to Daniel's test in D02glob.) > > diff --git a/Src/glob.c b/Src/glob.c > index 07dd7c2..5aa306b 100644 > --- a/Src/glob.c > +++ b/Src/glob.c > @@ -1748,6 +1761,11 @@ zglob(LinkList list, LinkNode np, int nountok) > matchptr++; > matchct = 1; > } > + } else if (shortcircuit) { > + /* treat as an empty string */ > + matchptr->name = dupstring(""); > + matchptr++; > + matchct = 1; > } > > if (!(gf_sortlist[0].tp & GS_NONE)) { ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-31 17:34 ` Daniel Shahaf @ 2014-05-31 17:59 ` Bart Schaefer 2014-06-02 9:40 ` Peter Stephenson 0 siblings, 1 reply; 31+ messages in thread From: Bart Schaefer @ 2014-05-31 17:59 UTC (permalink / raw) To: zsh-users On May 31, 5:34pm, Daniel Shahaf wrote: } Subject: Re: globbing in conditional expressions } } > } Is there a non-convoluted way to have the special case of a glob with } > } BOTH of the (NY) qualifiers return empty string rather than nularg when } > } it finds no match? Empty string is an illegal file name so for most } > } purposes it wouldn't otherwise affect usage. } } I beg to differ. Making *(NY) return empty strings upon no match is } (by design) convenient for the [ -n *(NY) ] case, but it breaks other } use-cases. Yeah, and it's unnecessary given [[ -n *(#qNY) ]] works per PWS's patches on the zsh-workers list, although that requires extendedglob. I won't commit users/18866. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-31 17:59 ` Bart Schaefer @ 2014-06-02 9:40 ` Peter Stephenson 2014-06-02 12:57 ` Daniel Shahaf 0 siblings, 1 reply; 31+ messages in thread From: Peter Stephenson @ 2014-06-02 9:40 UTC (permalink / raw) To: zsh-users I think there's a problem with the Y qualifier with subdirectories. I expect print */*(NY/) to find a subdirectory of a subdirectory. Typically I get nothing. My first guess was it was short-circuiting too early (on the first segment), but I can't see any evidence that it works at all if there's a glob before the last path segment. For example % print Completio?/*(NY/) % print Completion/*(NY/) Completion/Darwin pws ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-06-02 9:40 ` Peter Stephenson @ 2014-06-02 12:57 ` Daniel Shahaf 2014-06-02 14:15 ` Peter Stephenson 0 siblings, 1 reply; 31+ messages in thread From: Daniel Shahaf @ 2014-06-02 12:57 UTC (permalink / raw) To: Peter Stephenson; +Cc: zsh-users [-- Attachment #1: Type: text/plain, Size: 245 bytes --] Peter Stephenson wrote on Mon, Jun 02, 2014 at 10:40:50 +0100: > % print Completio?/*(NY/) > > % print Completion/*(NY/) > Completion/Darwin scanner() wrongly assumed that any call to insert() increases matchct. Test and fix attached. Daniel [-- Attachment #2: 0001-After-10ae77c-Fix-users-18869.patch --] [-- Type: text/x-patch, Size: 3326 bytes --] >From 72770405cdd21bc75fc8266cd0fa39fb179b7118 Mon Sep 17 00:00:00 2001 From: Daniel Shahaf <d.s@daniel.shahaf.name> Date: Mon, 2 Jun 2014 11:55:18 +0000 Subject: [PATCH] After 10ae77c: Fix users/18869 --- Src/glob.c | 21 +++++++++++---------- Test/D02glob.ztst | 5 +++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Src/glob.c b/Src/glob.c index 1420ac7..0ca63fc 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -297,15 +297,16 @@ statfullpath(const char *s, struct stat *st, int l) char **inserts; -/* add a match to the list */ +/* add a match to the list. Return 1 if it was inserted, 0 otherwise. */ /**/ -static void +static int insert(char *s, int checked) { struct stat buf, buf2, *bp; char *news = s; int statted = 0; + int inserted = 0; queue_signals(); inserts = NULL; @@ -316,7 +317,7 @@ insert(char *s, int checked) checked = statted = 1; if (statfullpath(s, &buf, 1)) { unqueue_signals(); - return; + return inserted; } mode = buf.st_mode; if (gf_follow) { @@ -340,7 +341,7 @@ insert(char *s, int checked) if (!statted && statfullpath(s, &buf, 1)) { unqueue_signals(); - return; + return inserted; } news = dyncat(pathbuf, news); @@ -365,7 +366,7 @@ insert(char *s, int checked) /* Try next alternative, or return if there are no more */ if (!(qo = qo->or)) { unqueue_signals(); - return; + return inserted; } qn = qo; continue; @@ -375,7 +376,7 @@ insert(char *s, int checked) } else if (!checked) { if (statfullpath(s, NULL, 1)) { unqueue_signals(); - return; + return inserted; } statted = 1; news = dyncat(pathbuf, news); @@ -435,6 +436,7 @@ insert(char *s, int checked) } matchptr++; + inserted = 1; if (++matchct == matchsz) { matchbuf = (Gmatch )realloc((char *)matchbuf, sizeof(struct gmatch) * (matchsz *= 2)); @@ -445,6 +447,7 @@ insert(char *s, int checked) break; } unqueue_signals(); + return inserted; } /* Do the globbing: scanner is called recursively * @@ -525,8 +528,7 @@ scanner(Complist q, int shortcircuit) } else { if (str[l]) str = dupstrpfx(str, l); - insert(str, 0); - if (shortcircuit) + if (insert(str, 0) == 1 && shortcircuit) return 1; } } else { @@ -617,8 +619,7 @@ scanner(Complist q, int shortcircuit) subdirlen += sizeof(int); } else /* if the last filename component, just add it */ - insert(fn, 1); - if (shortcircuit) + if (insert(fn, 1) == 1 && shortcircuit) return 1; } } diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst index d197098..9e29de2 100644 --- a/Test/D02glob.ztst +++ b/Test/D02glob.ztst @@ -546,9 +546,14 @@ (){ print $#@ } glob.tmp/dir*(Y) (){ print $#@ } glob.tmp/file*(NY) (){ [[ $1 = glob.tmp/dir? ]] && echo "(Y) returns a matching filename" } glob.tmp/dir*(Y) + # Can be negated (){ print $@:t } glob.tmp/dir*(Y^Y) + (){ [[ $#@ -eq 1 ]] && print Globs before last path component } glob.tmp/dir?/subdir(NY) + (){ [[ $#@ -eq 0 ]] && print Respects qualifiers } glob.tmp/dir?/subdir(NY.) 0:short-circuit modifier >1 >0 >(Y) returns a matching filename >dir1 dir2 dir3 dir4 +>Globs before last path component +>Respects qualifiers -- 1.7.10.4 ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-06-02 12:57 ` Daniel Shahaf @ 2014-06-02 14:15 ` Peter Stephenson 0 siblings, 0 replies; 31+ messages in thread From: Peter Stephenson @ 2014-06-02 14:15 UTC (permalink / raw) To: zsh-users On Mon, 02 Jun 2014 12:57:23 +0000 Daniel Shahaf <d.s@daniel.shahaf.name> wrote: > Peter Stephenson wrote on Mon, Jun 02, 2014 at 10:40:50 +0100: > > % print Completio?/*(NY/) > > > > % print Completion/*(NY/) > > Completion/Darwin > > scanner() wrongly assumed that any call to insert() increases matchct. > Test and fix attached. Thanks, I've pushed that up. pws ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-30 3:59 ` Bart Schaefer 2014-05-30 8:47 ` Peter Stephenson @ 2014-05-30 14:45 ` Daniel Shahaf 1 sibling, 0 replies; 31+ messages in thread From: Daniel Shahaf @ 2014-05-30 14:45 UTC (permalink / raw) To: Bart Schaefer; +Cc: zsh-users Bart Schaefer wrote on Thu, May 29, 2014 at 20:59:56 -0700: > On May 26, 11:52pm, Daniel Shahaf wrote: > } > } > } Bart Schaefer wrote on Thu, May 15, 2014 at 07:50:03 -0700: > } > adding a globbing flag (#X) [where I'm using X as a placeholder rather > } > than a suggestion for the actual flag] which means that the glob returns > } > only the first matching file it stumbles upon. > } > > } > In fact if you had that flag you wouldn't need -m as a condition op, it > } > would suffice to do [ ! -z pat(NX) ]. > } > } That turned out to be much less code to implement. It's attached, if > } anyone finds it useful. > > I like this one. Is there a mnemonic for your choice of (Y) ? No. That said, if it's to be commonly used with (N), then (NY) does serve as a memorable syntax. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-14 4:19 ` Daniel Shahaf 2014-05-14 7:18 ` Bart Schaefer @ 2014-05-14 7:42 ` Christoph (Stucki) von Stuckrad 2014-05-14 21:09 ` Roman Neuhauser 2 siblings, 0 replies; 31+ messages in thread From: Christoph (Stucki) von Stuckrad @ 2014-05-14 7:42 UTC (permalink / raw) To: zsh-users On Wed, 14 May 2014, Daniel Shahaf wrote: > Bart Schaefer wrote on Tue, May 13, 2014 at 08:41:17 -0700: ... > Right, so short-circuiting would only be useful for callers that want to > know whether a pattern matches a large directory, but don't care about > having a list of matching filenames. Does anyone have such a use-case? I often thought about 'this kind of check' while writing a cron script to analyze logs. This often starts with 'do a lot of things, but if and only if there is a log present in /var/log/..some_pattern... Of course setting null_glob and doing "for name in /var/log/...pattern..." will do nearly the same, but enclosing all preliminaries for a set of files AND their loop together in a clear question for the need to run, seems preferable to me - so I second creating '-m' ;-) Stucki -- Christoph von Stuckrad * * |nickname |Mail <stucki@mi.fu-berlin.de> \ Freie Universitaet Berlin |/_*|'stucki' |Tel(Mo.,Mi.):+49 30 838-75 459| Mathematik & Informatik EDV |\ *|if online| (Di,Do,Fr):+49 30 77 39 6600| Takustr. 9 / 14195 Berlin * * |on IRCnet|Fax(home): +49 30 77 39 6601/ ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-14 4:19 ` Daniel Shahaf 2014-05-14 7:18 ` Bart Schaefer 2014-05-14 7:42 ` Christoph (Stucki) von Stuckrad @ 2014-05-14 21:09 ` Roman Neuhauser 2014-05-15 9:14 ` Daniel Shahaf 2 siblings, 1 reply; 31+ messages in thread From: Roman Neuhauser @ 2014-05-14 21:09 UTC (permalink / raw) To: Daniel Shahaf; +Cc: Bart Schaefer, zsh-users # d.s@daniel.shahaf.name / 2014-05-14 04:19:08 +0000: > Bart Schaefer wrote on Tue, May 13, 2014 at 08:41:17 -0700: > > On May 8, 10:19pm, Roman Neuhauser wrote: > > } > > } maybe a crazy idea... how about something like [[ -m pattern ]] which > > } would succeed iff pattern matched at least one path? this could be > > } somewhat more amenable to shortcircuiting. > > > > Returning to this after a bit of a detour through [[ ... ]] expression > > parsing: > > > > You could define (via zmodload) an operator that applies filename > > generation to its argument, but changes to the internals of globbing > > would be needed to make a short-circuit happen. Then those changes > > would have to be exposed somehow so that the operator could use them. > > > > I've taken a shot at making those changes, see attached. wonderful, thank you! > > As I mentioned before, in the case of the match failing this would be > > exactly as expensive as not short-circuiting. > > Right, so short-circuiting would only be useful for callers that want to > know whether a pattern matches a large directory, but don't care about > having a list of matching filenames. Does anyone have such a use-case? s/a large directory// and you have exactly my use case: i only care about simple and succint code. (lack of) improved efficiency was Bart's concern. my original use case was along these lines: if ! is-mount $dst; then if ! is-empty $dst; then printf "%s: garbage in %s\n" $0 $dst return 1 fi run sudo mount --bind $src $dst fi where is-empty is now forced to function is-empty # {{{ { local -a d d=(${1:?}(N/F)) (( $#d == 0 )) } # }}} whereas i expected to be able to write sth like if ! is-mount $dst; then if [[ -m $dst/*(D) ]]; then or if ! is-mount $dst; then if [[ -m $dst(/F) ]]; then this piece of code deals with directories typically under 10 entries so i don't care about the performance impact of shortcircuting. if i cared about the matched filenames, the code would be structured differently and proably wouldn't call for a condexpr at all. i'm trying to think of a situation where [[ -m $pat ]] && mangle $REPLY would be useful for something other than foot-shooting... any ideas? -- roman ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-14 21:09 ` Roman Neuhauser @ 2014-05-15 9:14 ` Daniel Shahaf 2014-05-15 10:05 ` Roman Neuhauser 0 siblings, 1 reply; 31+ messages in thread From: Daniel Shahaf @ 2014-05-15 9:14 UTC (permalink / raw) To: Roman Neuhauser; +Cc: Bart Schaefer, zsh-users Roman Neuhauser wrote on Wed, May 14, 2014 at 23:09:23 +0200: > # d.s@daniel.shahaf.name / 2014-05-14 04:19:08 +0000: > > Bart Schaefer wrote on Tue, May 13, 2014 at 08:41:17 -0700: > > > On May 8, 10:19pm, Roman Neuhauser wrote: > > > } > > > } maybe a crazy idea... how about something like [[ -m pattern ]] which > > > } would succeed iff pattern matched at least one path? this could be > > > } somewhat more amenable to shortcircuiting. > > > > > > Returning to this after a bit of a detour through [[ ... ]] expression > > > parsing: > > > > > > You could define (via zmodload) an operator that applies filename > > > generation to its argument, but changes to the internals of globbing > > > would be needed to make a short-circuit happen. Then those changes > > > would have to be exposed somehow so that the operator could use them. > > > > > > > I've taken a shot at making those changes, see attached. > > wonderful, thank you! > You're welcome, but beware: the code doesn't work properly in all cases, e.g., as Bart suspected, [[ -m **/*attr.yo(/) ]] (in a built zsh source tree) false positives. > i'm trying to think of a situation where [[ -m $pat ]] && mangle $REPLY > would be useful for something other than foot-shooting... any ideas? Well, there is: while [[ -m $pat ]] ; # Suppose -m implicitly sets $REPLY to a matching filename ... () { mangle $1 && rm $1 } $REPLY Since the filename returned would be the first in readdir() order, this could result in starvation (some files never reaching mangle()) unless the underlying filesystem's readdir() provides more guarantees than the POSIX readdir() does. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-15 9:14 ` Daniel Shahaf @ 2014-05-15 10:05 ` Roman Neuhauser 0 siblings, 0 replies; 31+ messages in thread From: Roman Neuhauser @ 2014-05-15 10:05 UTC (permalink / raw) To: Daniel Shahaf; +Cc: zsh-users # d.s@daniel.shahaf.name / 2014-05-15 09:14:58 +0000: > Roman Neuhauser wrote on Wed, May 14, 2014 at 23:09:23 +0200: > > wonderful, thank you! > > You're welcome, but beware: the code doesn't work properly in all cases, > e.g., as Bart suspected, [[ -m **/*attr.yo(/) ]] (in a built zsh source > tree) false positives. understood, still, thanks a lot for kickstarting it. > > i'm trying to think of a situation where [[ -m $pat ]] && mangle $REPLY > > would be useful for something other than foot-shooting... any ideas? > > Well, there is: > > while [[ -m $pat ]] ; > # Suppose -m implicitly sets $REPLY to a matching filename ... > () { mangle $1 && rm $1 } $REPLY > > Since the filename returned would be the first in readdir() order, this > could result in starvation (some files never reaching mangle()) unless > the underlying filesystem's readdir() provides more guarantees than the > POSIX readdir() does. that's the foot-shooting part. :) the above imo screams for change to for f in $~pat; do mangle $f && rm $f done globbing can provide for ordering (like if you needed the oldest or largest file). if you need a random file, shortcircuiting readdir() is not the answer. -- roman ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-07 12:41 globbing in conditional expressions Roman Neuhauser 2014-05-07 14:44 ` Peter Stephenson @ 2014-05-07 14:57 ` Mikael Magnusson 2014-05-07 15:03 ` Bart Schaefer 2 siblings, 0 replies; 31+ messages in thread From: Mikael Magnusson @ 2014-05-07 14:57 UTC (permalink / raw) To: Zsh Users On 7 May 2014 14:41, Roman Neuhauser <neuhauser@sigpipe.cz> wrote: > hello, > > i was hoping i could combine conditional expressions with > some globbing, but i'm struggling to find the proper syntax. > > : 0 > ls fubar > snafu > : 0 > f=fubar(/F) > : 0 > echo $f > fubar(/F) > : 0 > echo $~f > fubar > : 0 > (set -x; [[ -z $~f ]]) > +zsh:183> [[ -z 'fubar(/F)' ]] > : 1 > > > i'm sure this is possible and documented, and i've just failed > to put the pieces together. a=( myglob*(/FN[1]) ) (( $#a )) && yay files -- Mikael Magnusson ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: globbing in conditional expressions 2014-05-07 12:41 globbing in conditional expressions Roman Neuhauser 2014-05-07 14:44 ` Peter Stephenson 2014-05-07 14:57 ` Mikael Magnusson @ 2014-05-07 15:03 ` Bart Schaefer 2 siblings, 0 replies; 31+ messages in thread From: Bart Schaefer @ 2014-05-07 15:03 UTC (permalink / raw) To: zsh-users On May 7, 2:41pm, Roman Neuhauser wrote: } } i was hoping i could combine conditional expressions with } some globbing, but i'm struggling to find the proper syntax. Conditional expressions only do pattern matching against strings, they do not do globbing against file names. So glob qualifiers like (/F) have no special meaning in a conditional expression. The way to handle this is to perform the glob first and then test the result of globbing, e.g. if function { set - foobar(/FN) && [[ -z $1 ]] } then print not a directory or empty else print directory contains files fi Use a subshell if the version of zsh is old enough not to support anonymous functions. ^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2014-06-02 14:15 UTC | newest] Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2014-05-07 12:41 globbing in conditional expressions Roman Neuhauser 2014-05-07 14:44 ` Peter Stephenson 2014-05-08 10:55 ` Daniel Shahaf 2014-05-08 11:20 ` Peter Stephenson 2014-05-08 15:34 ` Bart Schaefer 2014-05-08 20:19 ` Roman Neuhauser 2014-05-13 15:41 ` Bart Schaefer 2014-05-14 4:19 ` Daniel Shahaf 2014-05-14 7:18 ` Bart Schaefer 2014-05-15 4:55 ` Bart Schaefer 2014-05-15 9:35 ` Daniel Shahaf 2014-05-15 14:33 ` Bart Schaefer 2014-05-15 9:29 ` Daniel Shahaf 2014-05-15 14:50 ` Bart Schaefer 2014-05-26 23:52 ` Daniel Shahaf 2014-05-30 3:59 ` Bart Schaefer 2014-05-30 8:47 ` Peter Stephenson 2014-05-30 15:55 ` Bart Schaefer 2014-05-31 4:43 ` Bart Schaefer 2014-05-31 17:34 ` Daniel Shahaf 2014-05-31 17:59 ` Bart Schaefer 2014-06-02 9:40 ` Peter Stephenson 2014-06-02 12:57 ` Daniel Shahaf 2014-06-02 14:15 ` Peter Stephenson 2014-05-30 14:45 ` Daniel Shahaf 2014-05-14 7:42 ` Christoph (Stucki) von Stuckrad 2014-05-14 21:09 ` Roman Neuhauser 2014-05-15 9:14 ` Daniel Shahaf 2014-05-15 10:05 ` Roman Neuhauser 2014-05-07 14:57 ` Mikael Magnusson 2014-05-07 15:03 ` Bart Schaefer
Code repositories for project(s) associated with this public inbox https://git.vuxu.org/mirror/zsh/ This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).