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

PATCH: print -f



This merges bin_printf into bin_print, enabling ksh's print -f option.
At the moment, it can't be combined with print's -s and -z options.
They will probably have to wait until we aren't using printf(3) for the
formatting. Also new here is support for the %n format specifier.

Any views on whether the count (as returned with %n) should be zeroed
when reusing the format string?

One special case I've added is that if -m is used with -f and after
removal of non-matching arguments, there are none left, then nothing
will be printed. If you imagine using:
    print -f "value: %s\n" -m '[0-9]*' "${arr[@]}"
it would be annoying to have `value: ' printed if arr is empty or had
no numeric values. This could result in the currently pointless -m '*'
being used. It also does the same when -o or -O are used for sorting. I
can't see these options being used other than with the format reuse
feature so this seems sensible to me.

Note that %q doesn't yet work as it should - I didn't look closely at
what bslashquote() was doing. Also, if you try out %n, keep in mind
that it won't appear to work when printf is run in a subshell such as
if you pipe its output. And don't use a negative width or precision
specified in an argument (using `*' in the format specifier).

Test and Completion changes are also below.

Oliver

Index: Completion/Zsh/Command/_print
===================================================================
RCS file: /cvsroot/zsh/zsh/Completion/Zsh/Command/_print,v
retrieving revision 1.1
diff -u -r1.1 _print
--- Completion/Zsh/Command/_print	2001/04/02 11:29:22	1.1
+++ Completion/Zsh/Command/_print	2001/10/08 10:21:20
@@ -10,18 +10,19 @@
   pflag='(-s -u -z)-p[print arguments to input of coprocess]'
 
 _arguments -C -s -A "-*" -S \
-  '-r[ignore escape conventions of echo]' \
-  '(-r -b -m -s -l -N -o -O -i -c -u -p -z -D -P)-R[emulate BSD echo (no escapes, -n & -e flags only)]' \
+  '(-f)-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' \
   '(-u -p -z)-s[place results in the history list]' \
-  '(-N -c)-n[do not add a newline to the result]' \
-  '(-N -c)-l[print arguments separated by newlines]' \
-  '(-n -l -c)-N[print arguments separated and terminated by nulls]' \
+  '(-c -f)-n[do not add a newline to the result]' \
+  '(-N -c -f)-l[print arguments separated by newlines]' \
+  '(-n -l -c -f)-N[print arguments separated and terminated by nulls]' \
   '(-O)-o[sort arguments in ascending order]' \
   '(-o)-O[sort arguments in descending order]' \
   '-i[case-insensitive sorting]' \
-  '(-n -l -N)-c[print arguments in columns]' \
+  '(-n -l -N -f)-c[print arguments in columns]' \
   '(-s -p -z)-u+[specify file-descriptor to print arguments to]:file-descriptor:_file_descriptors' \
   '(-s -p -u)-z[push arguments onto editing buffer stack]' \
   '-D[substitute any arguments which are named directories using ~ notation]' \
Index: Doc/Zsh/builtins.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/builtins.yo,v
retrieving revision 1.37
diff -u -r1.37 builtins.yo
--- Doc/Zsh/builtins.yo	2001/10/05 15:02:18	1.37
+++ Doc/Zsh/builtins.yo	2001/10/08 10:21:20
@@ -638,7 +638,8 @@
 `tt(-)' in this context are swapped.
 )
 findex(print)
-item(tt(print) [ tt(-bnrslzpNDPoOicm) ] [ tt(-u)var(n) ] [ tt(-R) [ tt(-en) ]] [ var(arg) ... ])(
+item(tt(print) [ tt(-bnrslzpNDPoOicm) ] [ tt(-u)var(n) ] [ tt(-f) var(format) ] [ tt(-R) [ tt(-en) ]] [ var(arg) ... ])(
+With the `tt(-f)' option the arguments are printed as described by tt(printf).
 With no flags or with flag `tt(-)', the arguments are printed on
 the standard output as described by tt(echo), with the following differences:
 the escape sequence `tt(\M-)var(x)' metafies the character
@@ -715,19 +716,24 @@
 ).
 )
 enditem()
+
+If any of `tt(-m)', `tt(-o)' or `tt(-O)' are used in combination with
+`tt(-f)' and there are no arguments (after the removal process in the
+case of `tt(-m)') then nothing is printed.
 )
 findex(printf)
 item(tt(printf) var(format) [ var(arg) ... ])(
 Print the arguments according to the format specification. Formatting
 rules are the same as used in C. The same escape sequences as for tt(echo)
 are recognised in the format. All C format specifications ending in one of
-csdiouxXeEfgG are handled. In addition to this, `tt(%b)' can be used
+csdiouxXeEfgGn are handled. In addition to this, `tt(%b)' can be used
 instead of `tt(%s)' to cause escape sequences in the argument to be
 recognised and `tt(%q)' can be used to quote the argument in such a way
 that allows it to be reused as shell input. With the numeric format
 specifiers, if the corresponding argument starts with a quote character,
 the numeric value of the following character is used as the number to
-print.
+print. With `tt(%n)', the corresponding argument is taken as an identifier
+which is created as an integer parameter.
 
 If arguments remain unused after formatting, the format string is reused
 until all arguments have been consumed. If more arguments are required by
Index: Src/builtin.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/builtin.c,v
retrieving revision 1.56
diff -u -r1.56 builtin.c
--- Src/builtin.c	2001/10/08 08:13:11	1.56
+++ Src/builtin.c	2001/10/08 10:21:20
@@ -91,8 +91,8 @@
 #endif
 
     BUILTIN("popd", 0, bin_cd, 0, 2, BIN_POPD, NULL, NULL),
-    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPbnrslzNu0123456789pioOcm-", NULL),
-    BUILTIN("printf", 0, bin_printf, 1, -1, 0, NULL, NULL),
+    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPbnrsflzNu0123456789pioOcm-", NULL),
+    BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, NULL, NULL),
     BUILTIN("pushd", 0, bin_cd, 0, 2, BIN_PUSHD, NULL, NULL),
     BUILTIN("pushln", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
     BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
@@ -296,10 +296,19 @@
 	    }
 	    arg = (char *) ugetnode(args);
 	    /* for the "print" builtin, the options after -R are treated as
-	    options to "echo" */
-	    if ((flags & BINF_PRINTOPTS) && ops['R']) {
-		optstr = "ne";
-		flags |= BINF_ECHOPTS;
+	    options to "echo" and -f takes an extra argument */
+	    if (flags & BINF_PRINTOPTS) {
+		if (ops['R'] && !ops['f']) {
+		    optstr = "ne";
+		    flags |= BINF_ECHOPTS;
+		} else if (execop == 'f') {
+		    if (!arg) {
+			zwarnnam(name, "-f: format argument expected", NULL, 0);
+			return 1;
+		    }
+		    auxdata = arg;
+		    arg = (char *) ugetnode(args);
+		}
 	    }
 	    /* the option -- indicates the end of the options */
 	    if (ops['-'])
@@ -2870,18 +2879,43 @@
  
 /**/
 mod_export LinkList bufstack;
+
+/* echo, print, printf, pushln */
 
-/* echo, print, pushln */
+#define print_val(VAL) \
+  if (width >= 0) { \
+      if (prec >= 0) \
+	  count += fprintf(fout, start, width, prec, VAL); \
+      else \
+	  count += fprintf(fout, start, width, VAL); \
+  } else { \
+      if (prec >= 0) \
+	  count += fprintf(fout, start, prec, VAL); \
+      else \
+	  count += fprintf(fout, start, VAL); \
+  }
 
 /**/
 int
 bin_print(char *name, char **args, char *ops, int func)
 {
-    int nnl = 0, fd, argc, n;
+    int flen, width, prec, type, argc, n, nnl = 0, ret = 0;
     int *len;
-    Histent ent;
+    char *start, *endptr, *c, *fmt = NULL;
+    char **first, nullstr = '\0', save = '\0';
+    zlong count = 0;
     FILE *fout = stdout;
 
+    double doubleval;
+    int intval;
+    unsigned int uintval;
+    char *stringval;
+
+    if (func == BIN_PRINTF) auxdata = *args++;
+    if (auxdata)
+	fmt = getkeystring(auxdata, &flen, ops['b'] ? 2 : 0, &nnl);
+    first = args;
+
     /* -m option -- treat the first argument as a pattern and remove
      * arguments not matching */
     if (ops['m']) {
@@ -2894,16 +2928,19 @@
 	    zwarnnam(name, "bad pattern : %s", *args, 0);
 	    return 1;
 	}
-	for (p = ++args; *p; p++)
-	    if (!pattry(pprog, *p))
-		for (t = p--; (*t = t[1]); t++);
+	for (t = p = ++args; *p; p++)
+	    if (pattry(pprog, *p))
+		*t++ = *p;
+	*t = NULL;
+	first = args;
+	if (fmt && !*args) return 0;
     }
     /* compute lengths, and interpret according to -P, -D, -e, etc. */
     argc = arrlen(args);
     len = (int *) hcalloc(argc * sizeof(int));
     for(n = 0; n < argc; n++) {
 	/* first \ sequences */
-	if (!ops['e'] && (ops['R'] || ops['r'] || ops['E']))
+	if (fmt || !ops['e'] && (ops['R'] || ops['r'] || ops['E']))
 	    unmetafy(args[n], &len[n]);
 	else
 	    args[n] = getkeystring(args[n], &len[n], ops['b'] ? 2 :
@@ -2948,6 +2985,7 @@
     if (ops['s']) {
 	int nwords = 0, nlen, iwords;
 	char **pargs = args;
+	Histent ent;
 
 	queue_signals();
 	ent = prepnexthistent();
@@ -2971,8 +3009,11 @@
 	unqueue_signals();
 	return 0;
     }
+
     /* -u and -p -- output to other than standard output */
     if (ops['u'] || ops['p']) {
+	int fd;
+
 	if (ops['u']) {
 	    for (fd = 0; fd < 10; fd++)
 		if (ops[fd + '0'])
@@ -2993,15 +3034,15 @@
 
     /* -o and -O -- sort the arguments */
     if (ops['o']) {
+	if (fmt && !*args) return 0;
 	if (ops['i'])
 	    qsort(args, arrlen(args), sizeof(char *), cstrpcmp);
-
 	else
 	    qsort(args, arrlen(args), sizeof(char *), strpcmp);
     } else if (ops['O']) {
+	if (fmt && !*args) return 0;
 	if (ops['i'])
 	    qsort(args, arrlen(args), sizeof(char *), invcstrpcmp);
-
 	else
 	    qsort(args, arrlen(args), sizeof(char *), invstrpcmp);
     }
@@ -3041,62 +3082,37 @@
 	    fclose(fout);
 	return 0;
     }
+    
     /* normal output */
-    for (; *args; args++, len++) {
-	fwrite(*args, *len, 1, fout);
-	if (args[1])
-	    fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout);
+    if (!fmt) {
+	for (; *args; args++, len++) {
+	    fwrite(*args, *len, 1, fout);
+	    if (args[1])
+		fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout);
+	}
+	if (!(ops['n'] || nnl))
+	    fputc(ops['N'] ? '\0' : '\n', fout);
+	if (fout != stdout)
+	    fclose(fout);
+	return 0;
     }
-    if (!(ops['n'] || nnl))
-	fputc(ops['N'] ? '\0' : '\n', fout);
-    if (fout != stdout)
-	fclose(fout);
-    return 0;
-}
-
-/* printf */
-
-#define print_val(VAL) \
-  if (width >= 0) { \
-      if (prec >= 0) \
-	  printf(start, width, prec, VAL); \
-      else \
-	  printf(start, width, VAL); \
-  } else { \
-      if (prec >= 0) \
-	  printf(start, prec, VAL); \
-      else \
-	  printf(start, VAL); \
-  }
-
-/**/
-int
-bin_printf(char *name, char **args, char *ops, int func)
-{
-    int len, nnl, width, prec, type, ret = 0;
-    char *start, *endptr, *c, *fmt = getkeystring(*args, &len, 0, &nnl);
-    char **first = ++args, nullstr = '\0', save = '\0';
-
-    double doubleval;
-    int intval;
-    unsigned int uintval;
-    char *stringval;
-
+    
+    /* printf style output */
     do {
-
-	for (c = fmt;c-fmt < len;c++) {
-	    type = prec = width = -1;
-
+	for (c = fmt;c-fmt < flen;c++) {
 	    if (*c != '%') {
-		putchar(*c);
+		putc(*c, fout);
+		++count;
 		continue;
 	    }
 
 	    start = c++;
 	    if (*c == '%') {
 		putchar('%');
+		++count;
 		continue;
 	    }
+	    type = prec = width = -1;
 
 	    if (strchr("+- #", *c)) c++;
 
@@ -3126,34 +3142,26 @@
 	    switch (*c) {
 	    case 'c':
 		if (*args) {
-		    if (**args == Meta)
-			intval = (*args)[1] ^ 32;
-		    else
-			intval = **args;
+		    intval = **args;
 		    args++;
 		} else
 		    intval = 0;
 		print_val(intval);
 		break;
 	    case 's':
-		if (*args)
-		    stringval = unmetafy(*args++, NULL);
-		else
-		    stringval = &nullstr;
+		stringval = *args ? *args++ : &nullstr;
 		print_val(stringval);
 		break;
 	    case 'b':
 		if (*args) {
 		    int l;
-		    char *b = getkeystring(*args++, &l, 0, &nnl);
-		    fwrite(b, l, 1, stdout);
+		    char *b = getkeystring(*args++, &l, ops['b'] ? 2 : 0, &nnl);
+		    fwrite(b, l, 1, fout);
+		    count += l;
 		}
-		continue;
+		break;
 	    case 'q':
-		if (*args)
-		    stringval = bslashquote(unmetafy(*args++, NULL), NULL, 0);
-		else
-		    stringval = &nullstr;
+		stringval = *args ? bslashquote(*args++, NULL, 0) : &nullstr;
 		*c = 's';
 		print_val(stringval);
 		break;
@@ -3174,6 +3182,9 @@
 	    case 'X':
 		type=3;
 		break;
+	    case 'n':
+		if (*args) setiparam(*args++, count);
+		break;
 	    default:
 		zerrnam(name, "%s: invalid directive", start, 0);
 		ret = 1;
@@ -3185,12 +3196,12 @@
 			doubleval = (*args)[1];
 			print_val(doubleval);
 		    } else {
-		    	intval = (*args)[1];
+			intval = (*args)[1];
 			print_val(intval);
 		    }
 		    args++;
 		} else {
-	    	    switch (type) {
+		    switch (type) {
 		    case 1:
 			intval = (*args) ? strtol(*args, &endptr, 0) : 0;
 			print_val(intval);
@@ -3224,6 +3235,8 @@
     /* if there are remaining args, reuse format string */
     } while (*args && args != first);
 
+    if (fout != stdout)
+	fclose(fout);
     return ret;
 }
 
@@ -3429,16 +3442,12 @@
 	}
 	/*FALLTHROUGH*/
     case BIN_EXIT:
-	if (locallevel > forklevel) {
+	if (locallevel) {
 	    /*
 	     * We don't exit directly from functions to allow tidying
 	     * up, in particular EXIT traps.  We still need to perform
 	     * the usual interactive tests to see if we can exit at
 	     * all, however.
-	     *
-	     * The forklevel test means we *do* exit from a subshell
-	     * inside a function when we reach the level of the
-	     * function itself.
 	     */
 	    if (stopmsg || (zexit(0,2), !stopmsg)) {
 		retflag = 1;
Index: Src/hashtable.h
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/hashtable.h,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 hashtable.h
--- Src/hashtable.h	1999/04/15 18:05:38	1.1.1.1
+++ Src/hashtable.h	2001/10/08 10:21:20
@@ -56,6 +56,7 @@
 #define BIN_ECHO     22
 #define BIN_DISABLE  23
 #define BIN_ENABLE   24
+#define BIN_PRINTF   25
 
 /* These currently depend on being 0 and 1. */
 #define BIN_SETOPT    0
Index: Test/.distfiles
===================================================================
RCS file: /cvsroot/zsh/zsh/Test/.distfiles,v
retrieving revision 1.12
diff -u -r1.12 .distfiles
--- Test/.distfiles	2001/09/27 12:03:04	1.12
+++ Test/.distfiles	2001/10/08 10:21:20
@@ -8,6 +8,6 @@
 A04redirect.ztst     D01prompt.ztst       V02zregexparse.ztst
 A05execution.ztst    D02glob.ztst         Y01completion.ztst
 D06subscript.ztst    V01zmodload.ztst     E01options.ztst
-B02typeset.ztst
+B02typeset.ztst      B03print.ztst
 README
 '
Index: Test/B03print.ztst
===================================================================
RCS file: B03print.ztst
diff -N B03print.ztst
--- /dev/null	Thu May 24 22:33:05 2001
+++ B03print.ztst	Mon Oct  8 03:21:20 2001
@@ -0,0 +1,135 @@
+# Tests for the echo, print, printf and pushln builtins
+
+# Tested elsewhere:
+#  Use of print -p to output to coprocess	A01grammar
+#  Prompt expansion with print -P		D01prompt
+#  -l, -r, -R and -n indirectly tested in various places
+
+# Not yet tested:
+#  echo and pushln
+#  print's -b -c -s -z -N options
+
+
+%test
+
+ print -D "${HOME:-~}"
+0:replace directory name
+>~
+
+ print -u2 'error message'
+0:output to file-descriptor
+?error message
+
+ print -o foo bar Baz
+0:argument sorting
+>Baz bar foo
+
+ print -f
+1:print -f needs a format specified
+?(eval):print:1: -f: format argument expected
+
+ print -Of '%s\n' foo bar baz
+0:reverse argument sorting
+>foo
+>baz
+>bar
+
+ print -io a B c
+0:case-insensitive argument sorting
+>a B c
+
+ print -m '[0-9]' one 2 three 4 five 6
+0:removal of non-matching arguments
+>2 4 6
+
+ printf '%s\n' string
+0:test s format specifier
+>string
+
+ printf '%b' '\t\\\n'
+0:test b format specifier
+>	\
+
+# test %q here - it doesn't quite work yet
+
+ printf '%c\n' char
+0:test c format specifier
+>c
+
+ printf '%.10e%n\n' 1 count >/dev/null
+ printf '%d\n' $count
+0:test n format specifier
+>16
+
+ printf '%d\n' 123
+0:test d format specifier
+>123
+
+ printf '%g\n' 123.45
+0:test g format specifier
+>123.45
+
+# Is anyone not using ASCII
+ printf '%d\n' \'A
+0:initial quote to get numeric value of character with int
+>65
+
+ printf '%.1E\n' \'B
+0:initial quote to get numeric value of character with double
+>6.6E+01
+
+# code will probably be changed to print the literal `%s' in this case
+ printf '\x25s\n' arg
+0:using \x25 to introduce a format specifier
+>arg
+
+ printf '%3c\n' c
+0:width specified in format specifier
+>  c
+
+ printf '%.4s\n' chopped
+0:precision specified in format specifier
+>chop
+
+ printf '%*.*f\n' 6 2 10.2
+0:width/precision specified in arguments
+> 10.20
+
+ printf '%d\n' 3000000000
+1d:out of range numeric result
+?(eval):printf:1: `3000000000' arithmetic overflow
+
+ printf '%G\n' letters
+1:non numeric argument
+?(eval):printf:1: `letters' expected numeric value
+>0
+
+ print -f '%d\n' 2e4
+1:letters in numeric argument
+?(eval):print:1: `2e4' not completely converted
+>2
+
+ printf '%z'
+1:test invalid directive
+?(eval):printf:1: %z: invalid directive
+
+ print -m -f 'format - %s.\n' 'z' a b c
+0:format not printed if no arguments left after -m removal
+
+ print -f 'format - %s.\n'
+0:format printed despite lack of arguments
+>format - .
+
+ printf 'x%4sx\n'
+0:with no arguments empty string where string needed
+>x    x
+
+ printf '%d\n'
+0:with no arguments, zero used where number needed
+>0
+
+ printf '%s\t%c:%#x%%\n' one a 1 two b 2 three c 3
+0:multiple arguments with format reused
+>one	a:0x1%
+>two	b:0x2%
+>three	c:0x3%

_____________________________________________________________________
This message has been checked for all known viruses by the 
MessageLabs Virus Scanning Service. For further information visit
http://www.messagelabs.com/stats.asp



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