Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: print -v with an array



A feature of the shell printf implementations is that the format
string will be reused if additional arguments remain. So you can
do things like print out an associative array:
  printf '%s - %s\n' ${(kv)_comps}

This patch makes print -v check if the variable is an array and if
so, each reuse of the format string will correspond to a separate
array element. So you can combine or skip elements: perhaps matches
and descriptions in a completion function. In general, I find any
case where I can apply an operation to all elements of an array to
be very useful.

This is also something that can't be done directly with command
substitution. Ksh93 has the right basic idea in not forking for
builtins and -v otherwise feels somehow not right.

This is not fully bash compatible. Neither actually was our previous
implementation because bash assigns to the first element of the
array leaving subsequent elements unchanged. printf -v was added
after zsh 5.2 was released so we've no compatibility concern there.
Alternatives to checking if the variable is an array first would
be a -A option or perhaps making the feature specific to print
(which bash lacks) and not to printf.

With a bit of rearrangment of bin_print it would now be possible
to use this to allow more mixing of print options: -f with -c, for
example.

Back in January, when I wasn't paying attention, Bart wrote:
> - Should "print -v foo bar" write the trailing newline into $foo ?  In the
>   patch below I've chosen to make -n implicit with -v.  This does not
>   involve "printf" which always needs an explicit newline.

I'd suggest we include the trailing newline if the -l option was
specified. Seems it does get included with -c.

With print -v the open_memstream stuff might see more usage. Should
we be concerned with more efficient alternatives to the temporary
file fallback?

It appears to work to use shm_open followed by fdopen, do the writes,
a final fflush and then mmap the file descriptor to get at what was
written. That's fairly guaranteed to be in memory and shared memory
limits shouldn't be a problem. pipe() also works but it tends to have
a limited buffer size in the region of 16-64K.

A memfd_create system call was recently added to Linux. Combined
with fdopen, that would provide an alternative on Linux where libc
is something other than glibc. FreeBSD appears to have open_memstream.
On Solaris a tmpfs /tmp has been standard for a very long time so
the temporary file is unlikely to ever touch a disc anyway.

Oliver

diff --git a/Completion/Zsh/Command/_print b/Completion/Zsh/Command/_print
index 38f17ab..8df0941 100644
--- a/Completion/Zsh/Command/_print
+++ b/Completion/Zsh/Command/_print
@@ -1,15 +1,18 @@
 #compdef print printf
 
-local state expl line eflag pflag rest ret=1
+local state expl line eflag pflag rflag rest ret=1
 
 if [[ $service = print ]]; then
   # -e flag available only after -R 
   eflag="${words[1,CURRENT-1][(r)-*R*]:+-e[enable escapes]}"
+  rflag='-r[ignore escape conventions of echo]'
 
   # -p flag only relevant if we have a coprocess
   (:>&p) 2>/dev/null &&
     pflag='(-s -u -z)-p[print arguments to input of coprocess]'
 
+  [[ -n ${words[(r)-*f]} ]] && rflag='-r[disable reuse of format string]'
+
   if [[ -n ${words[1,CURRENT][(r)-*P*]} ]]; then
     rest='*: :->prompt'
   else
@@ -17,11 +20,11 @@ if [[ $service = print ]]; then
   fi
 
   _arguments -C -s -A "-*" -S \
-    '(-f)-r[ignore escape conventions of echo]' \
+    '-r[ignore escape conventions of echo]' \
     '(-r -b -f -m -s -l -N -o -O -i -c -u -p -z -D -P)-R[emulate BSD echo (no escapes, -n & -e flags only)]' \
     '-b[recognise bindkey escape sequences]' \
-    '-m[remove arguments matching specified pattern]' \
-    '(-r -n -R -l -N -c)-f+[print arguments as for the printf builtin]:format:->printfformat' \
+    '-m[remove arguments not matching specified pattern]:pattern' \
+    '(-n -R -l -N -c)-f+[print arguments as for the printf builtin]:format:->printfformat' \
     '(-u -p -z)-s[place results in the history list]' \
     '(-c -f)-n[do not add a newline to the result]' \
     '(-N -c -f)-l[print arguments separated by newlines]' \
@@ -33,11 +36,13 @@ if [[ $service = print ]]; then
     '(-n -l -N -f -C -s -z)-c[print arguments in columns]' \
     '(-n -l -N -f -c -s -z)-C+[print arguments in specified number of columns]:columns' \
     '(-s -p -z)-u+[specify file descriptor to print arguments to]:file descriptor:_file_descriptors' \
-    '-v[store output in named parameter]:parameter:_parameters' \
+    '(-s -z -p -u)-v[store output in named parameter]:parameter:_parameters' \
     '(-s -p -u)-z[push arguments onto editing buffer stack]' \
     '-D[substitute any arguments which are named directories using ~ notation]' \
     '-P[perform prompt expansion]' \
-    $pflag $eflag $rest && ret=0
+    '(-X -f -a -C -c -z)-x+[expand leading tabs]:tab width' \
+    '(-x -f -a -C -c -z)-X+[expand all tabs]:tab width' \
+    $pflag $eflag $rflag $rest && ret=0
 elif [[ $service = printf ]]; then
   state=printf
 fi
diff --git a/Completion/Zsh/Type/_globquals b/Completion/Zsh/Type/_globquals
index 6eef168..a904bdf 100644
--- a/Completion/Zsh/Type/_globquals
+++ b/Completion/Zsh/Type/_globquals
@@ -121,8 +121,7 @@ while [[ -n $PREFIX ]]; do
 	tmatch=( s m h d w M )
 	if zstyle -t ":completion:${curcontext}:time-specifiers" verbose; then
 	  zstyle -s ":completion:${curcontext}:time-specifiers" list-separator sep || sep=--
-          print -v tdisp -f "%s ${sep//(#m)[%\\]/$MATCH$MATCH} %s\0" ${tmatch:^^tdisp}
-	  tdisp=( ${(0)tdisp} )
+          print -v tdisp -f "%s ${sep//(#m)[%\\]/$MATCH$MATCH} %s" ${tmatch:^^tdisp}
 	fi
 	alts+=( "time-specifiers:time specifier:compadd -E 0 -d tdisp -S '' -a tmatch" )
       fi
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index ac4441c..1676c25 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -1289,7 +1289,9 @@ required by the format than have been specified, the behaviour is as if
 zero or an empty string had been specified as the argument.
 
 The tt(-v) option causes the output to be stored as the value of the
-parameter var(name), instead of printed.
+parameter var(name), instead of printed. If var(name) is an array and
+the format string is reused when consuming arguments then one
+array element will be used for each use of the format string.
 )
 findex(pushd)
 pindex(PUSHD_TO_HOME, use of)
diff --git a/Src/builtin.c b/Src/builtin.c
index 248f929..47dcc4c 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -4051,10 +4051,11 @@ bin_print(char *name, char **args, Options ops, int func)
 {
     int flen, width, prec, type, argc, n, narg, curlen = 0;
     int nnl = 0, fmttrunc = 0, ret = 0, maxarg = 0, nc = 0;
-    int flags[6], *len;
+    int flags[6], *len, visarr = 0;
     char *start, *endptr, *c, *d, *flag, *buf = NULL, spec[14], *fmt = NULL;
     char **first, **argp, *curarg, *flagch = "'0+- #", save = '\0', nullstr = '\0';
     size_t rcount = 0, count = 0;
+    size_t *cursplit, *splits = 0;
     FILE *fout = stdout;
 #ifdef HAVE_OPEN_MEMSTREAM
     size_t mcount;
@@ -4086,7 +4087,7 @@ bin_print(char *name, char **args, Options ops, int func)
             return 1; \
         } \
         unlink(tmpf); \
-        if ((fout = fdopen(tempfd, "w+")) == NULL) { \
+        if ((FOUT = fdopen(tempfd, "w+")) == NULL) { \
             close(tempfd); \
             zwarnnam(name, "can't open temp file: %e", errno); \
             return 1; \
@@ -4611,11 +4612,23 @@ bin_print(char *name, char **args, Options ops, int func)
      * special cases of printing to a ZLE buffer or the history, however.
      */
 
+    if (OPT_ISSET(ops,'v')) {
+	struct value vbuf;
+	char* s = OPT_ARG(ops,'v');
+	Value v = getvalue(&vbuf, &s, 0);
+	visarr = v && PM_TYPE(v->pm->node.flags) == PM_ARRAY;
+    }
     /* printf style output */
     *spec = '%';
     argp = args;
     do {
     	rcount = count;
+	if (argp > args && visarr) { /* reusing format string */
+	    if (!splits)
+		cursplit = splits = (size_t *)zhalloc(sizeof(size_t) *
+			(arrlen(args) / (argp - args) + 1));
+	    *cursplit++ = count;
+	}
     	if (maxarg) {
 	    first += maxarg;
 	    argc -= maxarg;
@@ -4982,18 +4995,30 @@ bin_print(char *name, char **args, Options ops, int func)
 	    if (buf)
 		free(buf);
 	} else {
-	    stringval = metafy(buf, rcount, META_REALLOC);
-	    if (OPT_ISSET(ops,'z')) {
-		zpushnode(bufstack, stringval);
-	    } else if (OPT_ISSET(ops,'v')) {
-		setsparam(OPT_ARG(ops, 'v'), stringval);
+	    if (visarr) {
+		char **arrayval = zshcalloc((cursplit - splits + 2) * sizeof(char *));
+		for (;cursplit >= splits; cursplit--) {
+		    int start = cursplit == splits ? 0 : cursplit[-1];
+		    arrayval[cursplit - splits] =
+			    metafy(buf + start, count - start, META_DUP);
+		    count = start;
+		}
+		setaparam(OPT_ARG(ops, 'v'), arrayval);
+		free(buf);
 	    } else {
-		ent = prepnexthistent();
-		ent->node.nam = stringval;
-		ent->stim = ent->ftim = time(NULL);
-		ent->node.flags = 0;
-		ent->words = (short *)NULL;
-		addhistnode(histtab, ent->node.nam, ent);
+		stringval = metafy(buf, rcount, META_REALLOC);
+		if (OPT_ISSET(ops,'z')) {
+		    zpushnode(bufstack, stringval);
+		} else if (OPT_ISSET(ops,'v')) {
+		    setsparam(OPT_ARG(ops, 'v'), stringval);
+		} else {
+		    ent = prepnexthistent();
+		    ent->node.nam = stringval;
+		    ent->stim = ent->ftim = time(NULL);
+		    ent->node.flags = 0;
+		    ent->words = (short *)NULL;
+		    addhistnode(histtab, ent->node.nam, ent);
+		}
 	    }
 	}
 	unqueue_signals();
diff --git a/Test/B03print.ztst b/Test/B03print.ztst
index befe2f2..74e34f3 100644
--- a/Test/B03print.ztst
+++ b/Test/B03print.ztst
@@ -310,3 +310,9 @@
 0:print and printf into a variable
 >typeset foo='once more'
 >typeset foo=$'into\C-@the-breach\C-@-'
+
+ foo=()
+ print -f '%2$d %4s' -v foo one 1 two 2 three 3
+ typeset -p foo
+0:printf into an array variable
+>typeset -a foo=( '1  one' '2  two' '3 three' )



Messages sorted by: Reverse Date, Date, Thread, Author