Bash-4.3 distribution sources and documentation
[platform/upstream/bash.git] / builtins / printf.def
index 43ccb56..7f29126 100644 (file)
@@ -1,37 +1,56 @@
 This file is printf.def, from which is created printf.c.
 It implements the builtin "printf" in Bash.
 
-Copyright (C) 1997 Free Software Foundation, Inc.
+Copyright (C) 1997-2010 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
-Bash is free software; you can redistribute it and/or modify it under
-the terms of the GNU General Public License as published by the Free
-Software Foundation; either version 1, or (at your option) any later
-version.
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
 
-Bash is distributed in the hope that it will be useful, but WITHOUT ANY
-WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
 
-You should have received a copy of the GNU General Public License along
-with Bash; see the file COPYING.  If not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+You should have received a copy of the GNU General Public License
+along with Bash.  If not, see <http://www.gnu.org/licenses/>.
 
 $PRODUCES printf.c
 
 $BUILTIN printf
 $FUNCTION printf_builtin
-$SHORT_DOC printf format [arguments]
-printf formats and prints ARGUMENTS under control of the FORMAT. FORMAT
-is a character string which contains three types of objects: plain
-characters, which are simply copied to standard output, character escape
-sequences which are converted and copied to the standard output, and
+$SHORT_DOC printf [-v var] format [arguments]
+Formats and prints ARGUMENTS under control of the FORMAT.
+
+Options:
+  -v var       assign the output to shell variable VAR rather than
+               display it on the standard output
+
+FORMAT is a character string which contains three types of objects: plain
+characters, which are simply copied to standard output; character escape
+sequences, which are converted and copied to the standard output; and
 format specifications, each of which causes printing of the next successive
-argument.  In addition to the standard printf(1) formats, %b means to
-expand backslash escape sequences in the corresponding argument, and %q
-means to quote the argument in a way that can be reused as shell input.
+argument.
+
+In addition to the standard format specifications described in printf(1),
+printf interprets:
+
+  %b   expand backslash escape sequences in the corresponding argument
+  %q   quote the argument in a way that can be reused as shell input
+  %(fmt)T output the date-time string resulting from using FMT as a format
+        string for strftime(3)
+
+The format is re-used as necessary to consume all of the arguments.  If
+there are fewer arguments than the format requires,  extra format
+specifications behave as if a zero value or null string, as appropriate,
+had been supplied.
+
+Exit Status:
+Returns success unless an invalid option is given or a write or assignment
+error occurs.
 $END
 
 #include <config.h>
@@ -42,79 +61,224 @@ $END
 #if defined (HAVE_LIMITS_H)
 #  include <limits.h>
 #else
-   /* Assume 32-bit ints and longs. */
-#  define LONG_MAX             2147483647L
-#  define LONG_MIN             (-2147483647L-1)
+   /* Assume 32-bit ints. */
 #  define INT_MAX              2147483647
 #  define INT_MIN              (-2147483647-1)
 #endif
 
+#if defined (PREFER_STDARG)
+#  include <stdarg.h>
+#else
+#  include <varargs.h>
+#endif
+
 #include <stdio.h>
-#include <ctype.h>
+#include <chartypes.h>
+
+#ifdef HAVE_INTTYPES_H
+#  include <inttypes.h>
+#endif
 
+#include "posixtime.h"
 #include "../bashansi.h"
+#include "../bashintl.h"
+
+#define NEED_STRFTIME_DECL
+
 #include "../shell.h"
-#include "../stdc.h"
+#include "shmbutil.h"
+#include "stdc.h"
 #include "bashgetopt.h"
+#include "common.h"
+
+#if defined (PRI_MACROS_BROKEN)
+#  undef PRIdMAX
+#endif
+
+#if !defined (PRIdMAX)
+#  if HAVE_LONG_LONG
+#    define PRIdMAX    "lld"
+#  else
+#    define PRIdMAX    "ld"
+#  endif
+#endif
 
 #if !defined (errno)
 extern int errno;
 #endif
 
+#define PC(c) \
+  do { \
+    char b[2]; \
+    tw++; \
+    b[0] = c; b[1] = '\0'; \
+    if (vflag) \
+      vbadd (b, 1); \
+    else \
+      putchar (c); \
+  } while (0)
+
 #define PF(f, func) \
   do { \
-    if (fieldwidth && precision) \
-      (void)printf(f, fieldwidth, precision, func); \
-    else if (fieldwidth && precision == 0) \
-      (void)printf(f, fieldwidth, func); \
-    else if (precision) \
-      (void)printf(f, precision, func); \
+    int nw; \
+    clearerr (stdout); \
+    if (have_fieldwidth && have_precision) \
+      nw = vflag ? vbprintf (f, fieldwidth, precision, func) : printf (f, fieldwidth, precision, func); \
+    else if (have_fieldwidth) \
+      nw = vflag ? vbprintf (f, fieldwidth, func) : printf (f, fieldwidth, func); \
+    else if (have_precision) \
+      nw = vflag ? vbprintf (f, precision, func) : printf (f, precision, func); \
     else \
-      (void)printf(f, func); \
+      nw = vflag ? vbprintf (f, func) : printf (f, func); \
+    tw += nw; \
+    if (ferror (stdout)) \
+      { \
+       sh_wrerror (); \
+       clearerr (stdout); \
+       return (EXECUTION_FAILURE); \
+      } \
   } while (0)
 
+/* We free the buffer used by mklong() if it's `too big'. */
 #define PRETURN(value) \
-  do { free (format); fflush (stdout); return (value); } while (0)
+  do \
+    { \
+      if (vflag) \
+       { \
+         bind_printf_variable  (vname, vbuf, 0); \
+         stupidly_hack_special_variables (vname); \
+       } \
+      if (conv_bufsize > 4096 ) \
+       { \
+         free (conv_buf); \
+         conv_bufsize = 0; \
+         conv_buf = 0; \
+       } \
+      if (vbsize > 4096) \
+       { \
+         free (vbuf); \
+         vbsize = 0; \
+         vbuf = 0; \
+       } \
+      else if (vbuf) \
+       vbuf[0] = 0; \
+      terminate_immediately--; \
+      if (ferror (stdout) == 0) \
+       fflush (stdout); \
+      if (ferror (stdout)) \
+       { \
+         sh_wrerror (); \
+         clearerr (stdout); \
+         return (EXECUTION_FAILURE); \
+       } \
+      return (value); \
+    } \
+  while (0)
+
+#define SKIP1 "#'-+ 0"
+#define LENMODS "hjlLtz"
+
+extern time_t shell_start_time;
+
+#if !HAVE_ASPRINTF
+extern int asprintf __P((char **, const char *, ...)) __attribute__((__format__ (printf, 2, 3)));
+#endif
 
-#define  SKIP1 "#-+ 0"
-#define  SKIP2 "*0123456789"
+#if !HAVE_VSNPRINTF
+extern int vsnprintf __P((char *, size_t, const char *, va_list)) __attribute__((__format__ (printf, 3, 0)));
+#endif
 
-static void printstr __P((char *, char *, int, int, int));
+static void printf_erange __P((char *));
+static int printstr __P((char *, char *, int, int, int));
+static int tescape __P((char *, char *, int *, int *));
 static char *bexpand __P((char *, int, int *, int *));
-static char *mklong __P((char *, int));
+static char *vbadd __P((char *, int));
+static int vbprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2)));
+static char *mklong __P((char *, char *, size_t));
 static int getchr __P((void));
 static char *getstr __P((void));
 static int  getint __P((void));
-static int getlong __P((long *));
-static int getulong __P((unsigned long *));
-static double getdouble __P((void));
-static int asciicode __P((void));
+static intmax_t getintmax __P((void));
+static uintmax_t getuintmax __P((void));
+static SHELL_VAR *bind_printf_variable __P((char *, char *, int));
+
+#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD && !defined(STRTOLD_BROKEN)
+typedef long double floatmax_t;
+#  define FLOATMAX_CONV        "L"
+#  define strtofltmax  strtold
+#else
+typedef double floatmax_t;
+#  define FLOATMAX_CONV        ""
+#  define strtofltmax  strtod
+#endif
+static floatmax_t getfloatmax __P((void));
+
+static intmax_t asciicode __P((void));
 
 static WORD_LIST *garglist;
 static int retval;
+static int conversion_error;
+
+/* printf -v var support */
+static int vflag = 0;
+static char *vbuf, *vname;
+static size_t vbsize;
+static int vblen;
+
+static intmax_t tw;
 
-extern char *backslash_quote ();
+static char *conv_buf;
+static size_t conv_bufsize;
 
 int
 printf_builtin (list)
      WORD_LIST *list;
 {
-  int ch, end, fieldwidth, precision, foundmod;
-  char convch, nextch, *format, *fmt, *start;
+  int ch, fieldwidth, precision;
+  int have_fieldwidth, have_precision;
+  char convch, thisch, nextch, *format, *modstart, *fmt, *start;
+#if defined (HANDLE_MULTIBYTE)
+  char mbch[25];               /* 25 > MB_LEN_MAX, plus can handle 4-byte UTF-8 and large Unicode characters*/
+  int mbind, mblen;
+#endif
 
+  conversion_error = 0;
   retval = EXECUTION_SUCCESS;
+
+  vflag = 0;
+
   reset_internal_getopt ();
-  while ((ch = internal_getopt (list, "")) != -1)
+  while ((ch = internal_getopt (list, "v:")) != -1)
     {
       switch (ch)
        {
-       case '?':
+       case 'v':
+         vname = list_optarg;
+#if defined (ARRAY_VARS)
+         if (legal_identifier (vname) || valid_array_reference (vname))
+#else
+         if (legal_identifier (vname))
+#endif
+           {
+             vflag = 1;
+             if (vbsize == 0)
+               vbuf = xmalloc (vbsize = 16);
+             vblen = 0;
+             if (vbuf)
+               vbuf[0] = 0;
+           }
+         else
+           {
+             sh_invalidid (vname);
+             return (EX_USAGE);
+           }
+         break;
        default:
-         builtin_usage();
+         builtin_usage ();
          return (EX_USAGE);
        }
     }
-  list = loptend;
+  list = loptend;      /* skip over possible `--' */
 
   if (list == 0)
     {
@@ -125,27 +289,55 @@ printf_builtin (list)
   if (list->word->word == 0 || list->word->word[0] == '\0')
     return (EXECUTION_SUCCESS);
 
-  format = ansicstr (list->word->word, strlen (list->word->word), (int *)NULL, (int *)NULL);
+  format = list->word->word;
+  tw = 0;
 
   garglist = list->next;
 
+  /* If the format string is empty after preprocessing, return immediately. */
+  if (format == 0 || *format == 0)
+    return (EXECUTION_SUCCESS);
+
+  terminate_immediately++;
+         
   /* Basic algorithm is to scan the format string for conversion
      specifications -- once one is found, find out if the field
      width or precision is a '*'; if it is, gather up value.  Note,
      format strings are reused as necessary to use up the provided
      arguments, arguments of zero/null string are provided to use
      up the format string. */
-
   do
     {
+      tw = 0;
       /* find next format specification */
       for (fmt = format; *fmt; fmt++)
        {
-         precision = fieldwidth = foundmod = 0;
+         precision = fieldwidth = 0;
+         have_fieldwidth = have_precision = 0;
+
+         if (*fmt == '\\')
+           {
+             fmt++;
+             /* A NULL third argument to tescape means to bypass the
+                special processing for arguments to %b. */
+#if defined (HANDLE_MULTIBYTE)
+             /* Accommodate possible use of \u or \U, which can result in
+                multibyte characters */
+             memset (mbch, '\0', sizeof (mbch));
+             fmt += tescape (fmt, mbch, &mblen, (int *)NULL);
+             for (mbind = 0; mbind < mblen; mbind++)
+               PC (mbch[mbind]);
+#else
+             fmt += tescape (fmt, &nextch, (int *)NULL, (int *)NULL);
+             PC (nextch);
+#endif
+             fmt--;    /* for loop will increment it for us again */
+             continue;
+           }
 
          if (*fmt != '%')
            {
-             putchar (*fmt);
+             PC (*fmt);
              continue;
            }
 
@@ -154,43 +346,69 @@ printf_builtin (list)
 
          if (*fmt == '%')              /* %% prints a % */
            {
-             putchar ('%');
+             PC ('%');
              continue;
            }
 
          /* found format specification, skip to field width */
          for (; *fmt && strchr(SKIP1, *fmt); ++fmt)
            ;
-         fieldwidth = (*fmt == '*') ? getint () : 0;
 
-         /* skip to possible '.', get following precision */
-         for (; *fmt && strchr(SKIP2, *fmt); ++fmt)
-           ;
+         /* Skip optional field width. */
+         if (*fmt == '*')
+           {
+             fmt++;
+             have_fieldwidth = 1;
+             fieldwidth = getint ();
+           }
+         else
+           while (DIGIT (*fmt))
+             fmt++;
+
+         /* Skip optional '.' and precision */
          if (*fmt == '.')
            {
              ++fmt;
-             precision = (*fmt == '*') ? getint () : 0;
+             if (*fmt == '*')
+               {
+                 fmt++;
+                 have_precision = 1;
+                 precision = getint ();
+               }
+             else
+               {
+                 /* Negative precisions are allowed but treated as if the
+                    precision were missing; I would like to allow a leading
+                    `+' in the precision number as an extension, but lots
+                    of asprintf/fprintf implementations get this wrong. */
+#if 0
+                 if (*fmt == '-' || *fmt == '+')
+#else
+                 if (*fmt == '-')
+#endif
+                   fmt++;
+                 while (DIGIT (*fmt))
+                   fmt++;
+               }
            }
 
-         /* skip to conversion char */
-         for (; *fmt && strchr(SKIP2, *fmt); ++fmt)
-           ;
+         /* skip possible format modifiers */
+         modstart = fmt;
+         while (*fmt && strchr (LENMODS, *fmt))
+           fmt++;
+           
          if (*fmt == 0)
            {
-             builtin_error ("`%s': missing format character", start);
+             builtin_error (_("`%s': missing format character"), start);
              PRETURN (EXECUTION_FAILURE);
            }
 
-         /* skip possible format modifiers */
-         if (*fmt == 'l' || *fmt == 'L' || *fmt == 'h')
-           {
-             fmt++;
-             foundmod = 1;
-           }
-           
          convch = *fmt;
-         nextch = fmt[1];
-         fmt[1] = '\0';
+         thisch = modstart[0];
+         nextch = modstart[1];
+         modstart[0] = convch;
+         modstart[1] = '\0';
+
          switch(convch)
            {
            case 'c':
@@ -211,24 +429,127 @@ printf_builtin (list)
                break;
              }
 
+           case '(':
+             {
+               char *timefmt, timebuf[128], *t;
+               int n;
+               intmax_t arg;
+               time_t secs;
+               struct tm *tm;
+
+               modstart[1] = nextch;   /* restore char after left paren */
+               timefmt = xmalloc (strlen (fmt) + 3);
+               fmt++;  /* skip over left paren */
+               for (t = timefmt, n = 1; *fmt; )
+                 {
+                   if (*fmt == '(')
+                     n++;
+                   else if (*fmt == ')')
+                     n--;
+                   if (n == 0)
+                     break;
+                   *t++ = *fmt++;
+                 }
+               *t = '\0';
+               if (*++fmt != 'T')
+                 {
+                   builtin_warning (_("`%c': invalid time format specification"), *fmt);
+                   fmt = start;
+                   free (timefmt);
+                   PC (*fmt);
+                   continue;
+                 }
+               if (timefmt[0] == '\0')
+                 {
+                   timefmt[0] = '%';
+                   timefmt[1] = 'X';   /* locale-specific current time - should we use `+'? */
+                   timefmt[2] = '\0';
+                 }
+               /* argument is seconds since the epoch with special -1 and -2 */
+               /* default argument is equivalent to -1; special case */
+               arg = garglist ? getintmax () : -1;
+               if (arg == -1)
+                 secs = NOW;           /* roughly date +%s */
+               else if (arg == -2)
+                 secs = shell_start_time;      /* roughly $SECONDS */
+               else
+                 secs = arg;
+#if defined (HAVE_TZSET)
+               sv_tz ("TZ");           /* XXX -- just make sure */
+#endif
+               tm = localtime (&secs);
+               if (tm == 0)
+                 {
+                   secs = 0;
+                   tm = localtime (&secs);
+                 }
+               n = tm ? strftime (timebuf, sizeof (timebuf), timefmt, tm) : 0;
+               free (timefmt);
+               if (n == 0)
+                 timebuf[0] = '\0';
+               else
+                 timebuf[sizeof(timebuf) - 1] = '\0';
+               /* convert to %s format that preserves fieldwidth and precision */
+               modstart[0] = 's';
+               modstart[1] = '\0';
+               n = printstr (start, timebuf, strlen (timebuf), fieldwidth, precision); /* XXX - %s for now */
+               if (n < 0)
+                 {
+                   if (ferror (stdout) == 0)
+                     {
+                       sh_wrerror ();
+                       clearerr (stdout);
+                     }
+                   PRETURN (EXECUTION_FAILURE);
+                 }
+               break;
+             }
+
+           case 'n':
+             {
+               char *var;
+
+               var = getstr ();
+               if (var && *var)
+                 {
+                   if (legal_identifier (var))
+                     bind_var_to_int (var, tw);
+                   else
+                     {
+                       sh_invalidid (var);
+                       PRETURN (EXECUTION_FAILURE);
+                     }
+                 }
+               break;
+             }
+
            case 'b':           /* expand escapes in argument */
              {
                char *p, *xp;
-               int rlen;
+               int rlen, r;
 
                p = getstr ();
-               ch = rlen = 0;
+               ch = rlen = r = 0;
                xp = bexpand (p, strlen (p), &ch, &rlen);
 
                if (xp)
                  {
                    /* Have to use printstr because of possible NUL bytes
                       in XP -- printf does not handle that well. */
-                   printstr (start, xp, rlen, fieldwidth, precision);
+                   r = printstr (start, xp, rlen, fieldwidth, precision);
+                   if (r < 0)
+                     {
+                       if (ferror (stdout) == 0)
+                         {
+                           sh_wrerror ();
+                           clearerr (stdout);
+                         }
+                       retval = EXECUTION_FAILURE;
+                     }
                    free (xp);
                  }
 
-               if (ch)
+               if (ch || r < 0)
                  PRETURN (retval);
                break;
              }
@@ -236,31 +557,55 @@ printf_builtin (list)
            case 'q':           /* print with shell quoting */
              {
                char *p, *xp;
+               int r;
 
+               r = 0;
                p = getstr ();
-               xp = backslash_quote (p);
+               if (p && *p == 0)       /* XXX - getstr never returns null */
+                 xp = savestring ("''");
+               else if (ansic_shouldquote (p))
+                 xp = ansic_quote (p, 0, (int *)0);
+               else
+                 xp = sh_backslash_quote (p, 0, 1);
                if (xp)
                  {
                    /* Use printstr to get fieldwidth and precision right. */
-                   printstr (start, xp, strlen (xp), fieldwidth, precision);
+                   r = printstr (start, xp, strlen (xp), fieldwidth, precision);
+                   if (r < 0)
+                     {
+                       sh_wrerror ();
+                       clearerr (stdout);
+                     }
                    free (xp);
                  }
+
+               if (r < 0)
+                 PRETURN (EXECUTION_FAILURE);
                break;
              }
 
            case 'd':
            case 'i':
              {
-               long p;
                char *f;
+               long p;
+               intmax_t pp;
 
-               if (foundmod == 0 && ((f = mklong (start, convch)) == NULL))
-                 PRETURN (EXECUTION_FAILURE);
+               p = pp = getintmax ();
+               if (p != pp)
+                 {
+                   f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2);
+                   PF (f, pp);
+                 }
                else
-                 f = start;
-               if (getlong (&p))
-                 PRETURN (EXECUTION_FAILURE);
-               PF(f, p);
+                 {
+                   /* Optimize the common case where the integer fits
+                      in "long".  This also works around some long
+                      long and/or intmax_t library bugs in the common
+                      case, e.g. glibc 2.2 x86.  */
+                   f = mklong (start, "l", 1);
+                   PF (f, p);
+                 }
                break;
              }
 
@@ -269,49 +614,78 @@ printf_builtin (list)
            case 'x':
            case 'X':
              {
-               unsigned long p;
                char *f;
+               unsigned long p;
+               uintmax_t pp;
 
-               if (foundmod == 0 && ((f = mklong (start, convch)) == NULL))
-                 PRETURN (EXECUTION_FAILURE);
+               p = pp = getuintmax ();
+               if (p != pp)
+                 {
+                   f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2);
+                   PF (f, pp);
+                 }
                else
-                 f = start;
-               if (getulong (&p))
-                 PRETURN (EXECUTION_FAILURE);
-               PF (f, p);
+                 {
+                   f = mklong (start, "l", 1);
+                   PF (f, p);
+                 }
                break;
              }
 
            case 'e':
            case 'E':
            case 'f':
+           case 'F':
            case 'g':
            case 'G':
+#if defined (HAVE_PRINTF_A_FORMAT)
+           case 'a':
+           case 'A':
+#endif
              {
-               double p;
+               char *f;
+               floatmax_t p;
 
-               p = getdouble ();
-               PF(start, p);
+               p = getfloatmax ();
+               f = mklong (start, FLOATMAX_CONV, sizeof(FLOATMAX_CONV) - 1);
+               PF (f, p);
                break;
              }
 
-           /* We output unrecognized format characters, but we print a
-              warning message and return a failure exit status. */
+           /* We don't output unrecognized format characters; we print an
+              error message and return a failure exit status. */
            default:
-             builtin_error ("`%c': illegal format character", convch);
+             builtin_error (_("`%c': invalid format character"), convch);
              PRETURN (EXECUTION_FAILURE);
            }
 
-         fmt[1] = nextch;
+         modstart[0] = thisch;
+         modstart[1] = nextch;
+       }
+
+      if (ferror (stdout))
+       {
+         /* PRETURN will print error message. */
+         PRETURN (EXECUTION_FAILURE);
        }
     }
-  while (garglist);
+  while (garglist && garglist != list->next);
+
+  if (conversion_error)
+    retval = EXECUTION_FAILURE;
 
   PRETURN (retval);
 }
 
-/* We duplicate a lot of what printf(3) does here. */
 static void
+printf_erange (s)
+     char *s;
+{
+  builtin_error (_("warning: %s: %s"), s, strerror(ERANGE));
+}
+
+/* We duplicate a lot of what printf(3) does here. */
+static int
 printstr (fmt, string, len, fieldwidth, precision)
      char *fmt;                        /* format */
      char *string;             /* expanded string argument */
@@ -324,9 +698,10 @@ printstr (fmt, string, len, fieldwidth, precision)
 #endif
   int padlen, nc, ljust, i;
   int fw, pr;                  /* fieldwidth and precision */
+  intmax_t mfw, mpr;
 
-  if (string == 0 || *string == '\0')
-    return;
+  if (string == 0 || len == 0)
+    return 0;
 
 #if 0
   s = fmt;
@@ -334,27 +709,37 @@ printstr (fmt, string, len, fieldwidth, precision)
   if (*fmt == '%')
     fmt++;
 
-  ljust = fw = pr = 0;
+  ljust = fw = 0;
+  pr = -1;
+  mfw = 0;
+  mpr = -1;
 
   /* skip flags */
-  while (*fmt == '#' || *fmt == '-' || *fmt == '+' || *fmt == ' ' || *fmt == '0')
+  while (strchr (SKIP1, *fmt))
     {
       if (*fmt == '-')
        ljust = 1;
       fmt++;
     }
 
-  /* get fieldwidth, if present */
+  /* get fieldwidth, if present.  rely on caller to clamp fieldwidth at INT_MAX */
   if (*fmt == '*')
     {
       fmt++;
       fw = fieldwidth;
+      if (fw < 0)
+       {
+         fw = -fw;
+         ljust = 1;
+       }
     }
-  else if (isdigit (*fmt))
+  else if (DIGIT (*fmt))
     {
-      fw = *fmt++ - '0';
-      while (isdigit (*fmt))
-       fw = (fw * 10) + (*fmt++ - '0');
+      mfw = *fmt++ - '0';
+      while (DIGIT (*fmt))
+       mfw = (mfw * 10) + (*fmt++ - '0');
+      /* Error if fieldwidth > INT_MAX here? */
+      fw = (mfw < 0 || mfw > INT_MAX) ? INT_MAX : mfw;
     }
 
   /* get precision, if present */
@@ -366,11 +751,13 @@ printstr (fmt, string, len, fieldwidth, precision)
          fmt++;
          pr = precision;
        }
-      else if (isdigit (*fmt))
+      else if (DIGIT (*fmt))
        {
-         pr = *fmt++ - '0';
-         while (isdigit (*fmt))
-           pr = (pr * 10) + (*fmt++ - '0');
+         mpr = *fmt++ - '0';
+         while (DIGIT (*fmt))
+           mpr = (mpr * 10) + (*fmt++ - '0');
+         /* Error if precision > INT_MAX here? */
+         pr = (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr;
        }
     }
 
@@ -378,13 +765,13 @@ printstr (fmt, string, len, fieldwidth, precision)
   /* If we remove this, get rid of `s'. */
   if (*fmt != 'b' && *fmt != 'q')
     {
-      internal_error ("format parsing problem: %s", s);
+      internal_error (_("format parsing problem: %s"), s);
       fw = pr = 0;
     }
 #endif
 
   /* chars from string to print */
-  nc = (pr > 0 && pr <= len) ? pr : len;
+  nc = (pr >= 0 && pr <= len) ? pr : len;
 
   padlen = fw - nc;
   if (padlen < 0)
@@ -394,46 +781,162 @@ printstr (fmt, string, len, fieldwidth, precision)
 
   /* leading pad characters */
   for (; padlen > 0; padlen--)
-    putchar (' ');
+    PC (' ');
 
   /* output NC characters from STRING */
   for (i = 0; i < nc; i++)
-    putchar (string[i]);
+    PC (string[i]);
 
   /* output any necessary trailing padding */
   for (; padlen < 0; padlen++)
-    putchar (' ');
+    PC (' ');
+
+  return (ferror (stdout) ? -1 : 0);
 }
   
 /* Convert STRING by expanding the escape sequences specified by the
    POSIX standard for printf's `%b' format string.  If SAWC is non-null,
+   perform the processing appropriate for %b arguments.  In particular,
    recognize `\c' and use that as a string terminator.  If we see \c, set
    *SAWC to 1 before returning.  LEN is the length of STRING. */
 
-#ifdef isoctal
-#undef isoctal
-#endif
+/* Translate a single backslash-escape sequence starting at ESTART (the
+   character after the backslash) and return the number of characters
+   consumed by the sequence.  CP is the place to return the translated
+   value.  *SAWC is set to 1 if the escape sequence was \c, since that means
+   to short-circuit the rest of the processing.  If SAWC is null, we don't
+   do the \c short-circuiting, and \c is treated as an unrecognized escape
+   sequence; we also bypass the other processing specific to %b arguments.  */
+static int
+tescape (estart, cp, lenp, sawc)
+     char *estart;
+     char *cp;
+     int *lenp, *sawc;
+{
+  register char *p;
+  int temp, c, evalue;
+  unsigned long uvalue;
 
-#define isoctal(c)     ((c) >= '0' && (c) <= '7')
+  p = estart;
+  if (lenp)
+    *lenp = 1;
 
-#define OCTVALUE(c)    ((c) - '0')
+  switch (c = *p++)
+    {
+#if defined (__STDC__)
+      case 'a': *cp = '\a'; break;
+#else
+      case 'a': *cp = '\007'; break;
+#endif
 
-#ifndef isxdigit
-#  define isxdigit(c)  (isdigit((c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
+      case 'b': *cp = '\b'; break;
+
+      case 'e':
+      case 'E': *cp = '\033'; break;   /* ESC -- non-ANSI */
+
+      case 'f': *cp = '\f'; break;
+
+      case 'n': *cp = '\n'; break;
+
+      case 'r': *cp = '\r'; break;
+
+      case 't': *cp = '\t'; break;
+
+      case 'v': *cp = '\v'; break;
+
+      /* The octal escape sequences are `\0' followed by up to three octal
+        digits (if SAWC), or `\' followed by up to three octal digits (if
+        !SAWC).  As an extension, we allow the latter form even if SAWC. */
+      case '0': case '1': case '2': case '3':
+      case '4': case '5': case '6': case '7':
+       evalue = OCTVALUE (c);
+       for (temp = 2 + (!evalue && !!sawc); ISOCTAL (*p) && temp--; p++)
+         evalue = (evalue * 8) + OCTVALUE (*p);
+       *cp = evalue & 0xFF;
+       break;
+
+      /* And, as another extension, we allow \xNN, where each N is a
+        hex digit. */
+      case 'x':
+       for (temp = 2, evalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++)
+         evalue = (evalue * 16) + HEXVALUE (*p);
+       if (p == estart + 1)
+         {
+           builtin_error (_("missing hex digit for \\x"));
+           *cp = '\\';
+           return 0;
+         }
+       *cp = evalue & 0xFF;
+       break;
+
+#if defined (HANDLE_MULTIBYTE)
+      case 'u':
+      case 'U':
+       temp = (c == 'u') ? 4 : 8;      /* \uNNNN \UNNNNNNNN */
+       for (uvalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++)
+         uvalue = (uvalue * 16) + HEXVALUE (*p);
+       if (p == estart + 1)
+         {
+           builtin_error (_("missing unicode digit for \\%c"), c);
+           *cp = '\\';
+           return 0;
+         }
+       if (uvalue <= 0x7f)             /* <= 0x7f translates directly */
+         *cp = uvalue;
+       else
+         {
+           temp = u32cconv (uvalue, cp);
+           cp[temp] = '\0';
+           if (lenp)
+             *lenp = temp;
+         }
+       break;
 #endif
+       
+      case '\\':       /* \\ -> \ */
+       *cp = c;
+       break;
+
+      /* SAWC == 0 means that \', \", and \? are recognized as escape
+        sequences, though the only processing performed is backslash
+        removal. */
+      case '\'': case '"': case '?':
+       if (!sawc)
+         *cp = c;
+       else
+         {
+           *cp = '\\';
+           return 0;
+         }
+       break;
+
+      case 'c':
+       if (sawc)
+         {
+           *sawc = 1;
+           break;
+         }
+      /* other backslash escapes are passed through unaltered */
+      default:
+       *cp = '\\';
+       return 0;
+      }
+  return (p - estart);
+}
 
-#define HEXVALUE(c) \
-  ((c) >= 'a' && (c) <= 'f' ? (c)-'a'+10 : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0')
-  
 static char *
 bexpand (string, len, sawc, lenp)
      char *string;
      int len, *sawc, *lenp;
 {
-  int c, temp;
-  char *ret, *r, *s;
+  int temp;
+  char *ret, *r, *s, c;
+#if defined (HANDLE_MULTIBYTE)
+  char mbch[25];
+  int mbind, mblen;
+#endif
 
-  if (string == 0 || *string == '\0')
+  if (string == 0 || len == 0)
     {
       if (sawc)
        *sawc = 0;
@@ -442,7 +945,7 @@ bexpand (string, len, sawc, lenp)
       return ((char *)NULL);
     }
 
-  ret = xmalloc (len + 1);
+  ret = (char *)xmalloc (len + 1);
   for (r = ret, s = string; s && *s; )
     {
       c = *s++;
@@ -451,99 +954,126 @@ bexpand (string, len, sawc, lenp)
          *r++ = c;
          continue;
        }
-
-      switch (c = *s++)
-       {
-#if defined (__STDC__)
-       case 'a': c = '\a'; break;
+      temp = 0;
+#if defined (HANDLE_MULTIBYTE)
+      memset (mbch, '\0', sizeof (mbch));
+      s += tescape (s, mbch, &mblen, &temp);
 #else
-       case 'a': c = '\007'; break;
+      s += tescape (s, &c, (int *)NULL, &temp);
 #endif
+      if (temp)
+       {
+         if (sawc)
+           *sawc = 1;
+         break;
+       }
 
-       case 'b': c = '\b'; break;
-
-       case 'e': c = '\033'; break;    /* ESC -- non-ANSI */
-
-       case 'f': c = '\f'; break;
-
-       case 'n': c = '\n'; break;
+#if defined (HANDLE_MULTIBYTE)
+      for (mbind = 0; mbind < mblen; mbind++)
+       *r++ = mbch[mbind];
+#else
+      *r++ = c;
+#endif      
+    }
 
-       case 'r': c = '\r'; break;
+  *r = '\0';
+  if (lenp)
+    *lenp = r - ret;
+  return ret;
+}
 
-       case 't': c = '\t'; break;
+static char *
+vbadd (buf, blen)
+     char *buf;
+     int blen;
+{
+  size_t nlen;
 
-       case 'v': c = '\v'; break;
+  nlen = vblen + blen + 1;
+  if (nlen >= vbsize)
+    {
+      vbsize = ((nlen + 63) >> 6) << 6;
+      vbuf = (char *)xrealloc (vbuf, vbsize);
+    }
 
-       /* %b octal constants are `\0' followed by one, two, or three
-          octal digits... */
-       case '0':
-         for (temp = 3, c = 0; isoctal (*s) && temp--; s++)
-           c = (c * 8) + OCTVALUE (*s);
-         break;
+  if (blen == 1)
+    vbuf[vblen++] = buf[0];
+  else if (blen > 1)
+    {
+      FASTCOPY (buf, vbuf  + vblen, blen);
+      vblen += blen;
+    }
+  vbuf[vblen] = '\0';
 
-       /* but, as an extension, the other echo-like octal escape
-          sequences are supported as well. */
-       case '1': case '2': case '3': case '4':
-       case '5': case '6': case '7':
-         for (temp = 2, c -= '0'; isoctal (*s) && temp--; s++)
-           c = (c * 8) + OCTVALUE (*s);
-         break;
+#ifdef DEBUG
+  if  (strlen (vbuf) != vblen)
+    internal_error  ("printf:vbadd: vblen (%d) != strlen (vbuf) (%d)", vblen, (int)strlen (vbuf));
+#endif
 
-       /* And, as another extension, we allow \xNNN, where each N is a
-          hex digit. */
-       case 'x':
-         for (temp = 3, c = 0; isxdigit (*s) && temp--; s++)
-           c = (c * 16) + HEXVALUE (*s);
-         if (temp == 3)
-           {
-             builtin_error ("missing hex digit for \\x");
-             *r++ = '\\';
-             c = 'x';
-           }
-         break;
+  return vbuf;
+}
 
-       case '\\':
-#if 0
-       case '\'':              /* XXX */
-       case '"':               /* XXX */
+static int
+#if defined (PREFER_STDARG)
+vbprintf (const char *format, ...)
+#else
+vbprintf (format, va_alist)
+  const char *format;
+  va_dcl
 #endif
-         break;
-
-       case 'c':
-         if (sawc)
-           *sawc = 1;
-         *r = '\0';
-         if (lenp)
-           *lenp = r - ret;
-         return ret;
+{
+  va_list args;
+  size_t nlen;
+  int blen;
 
-       /* other backslash escapes are passed through unaltered */
-       default: *r++ = '\\'; break;
-       }
+  SH_VA_START (args, format);
+  blen = vsnprintf (vbuf + vblen, vbsize - vblen, format, args);
+  va_end (args);
 
-      *r++ = c;
+  nlen = vblen + blen + 1;
+  if (nlen >= vbsize)
+    {
+      vbsize = ((nlen + 63) >> 6) << 6;
+      vbuf = (char *)xrealloc (vbuf, vbsize);
+      SH_VA_START (args, format);
+      blen = vsnprintf (vbuf + vblen, vbsize - vblen, format, args);
+      va_end (args);
     }
 
-  *r = '\0';
-  if (lenp)
-    *lenp = r - ret;
-  return ret;
+  vblen += blen;
+  vbuf[vblen] = '\0';
+
+#ifdef DEBUG
+  if  (strlen (vbuf) != vblen)
+    internal_error  ("printf:vbadd: vblen (%d) != strlen (vbuf) (%d)", vblen, (int)strlen (vbuf));
+#endif
+  
+  return (blen);
 }
 
 static char *
-mklong (str, ch)
+mklong (str, modifiers, mlen)
      char *str;
-     int ch;
+     char *modifiers;
+     size_t mlen;
 {
-  static char copy[64];
-  int len;
-
-  len = strlen (str) + 2;
-  FASTCOPY (str, copy, len - 3);
-  copy[len - 3] = 'l';
-  copy[len - 2] = ch;
-  copy[len - 1] = '\0';
-  return (copy);
+  size_t len, slen;
+
+  slen = strlen (str);
+  len = slen + mlen + 1;
+
+  if (len > conv_bufsize)
+    {
+      conv_bufsize = (((len + 1023) >> 10) << 10);
+      conv_buf = (char *)xrealloc (conv_buf, conv_bufsize);
+    }
+
+  FASTCOPY (str, conv_buf, slen - 1);
+  FASTCOPY (modifiers, conv_buf + slen - 1, mlen);
+
+  conv_buf[len - 2] = str[slen - 1];
+  conv_buf[len - 1] = '\0';
+  return (conv_buf);
 }
 
 static int
@@ -575,119 +1105,166 @@ getstr ()
 static int
 getint ()
 {
-  long ret;
+  intmax_t ret;
 
-  if (getlong (&ret))
-    return (0);
+  ret = getintmax ();
+
+  if (garglist == 0)
+    return ret;
 
   if (ret > INT_MAX)
     {
-      builtin_error ("%s: %s", garglist->word->word, strerror(ERANGE));
-      return (0);
+      printf_erange (garglist->word->word);
+      ret = INT_MAX;
+    }
+  else if (ret < INT_MIN)
+    {
+      printf_erange (garglist->word->word);
+      ret = INT_MIN;
     }
 
   return ((int)ret);
 }
 
-static int
-getlong (lp)
-     long *lp;
+static intmax_t
+getintmax ()
 {
-  long ret;
+  intmax_t ret;
+  char *ep;
 
   if (garglist == 0)
-    {
-      *lp = 0L;
-      return (0);
-    }
+    return (0);
 
   if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
-    {
-      *lp =  (long)asciicode ();
-      return (0);
-    }
+    return asciicode ();
 
   errno = 0;
-  /* legal_number does not currently detect overflow, but it should.
-     Someday it will. */
-  if (legal_number (garglist->word->word, &ret) == 0)
+  ret = strtoimax (garglist->word->word, &ep, 0);
+
+  if (*ep)
     {
-      builtin_error ("%s: illegal number", garglist->word->word);
-      return (1);
+      sh_invalidnum (garglist->word->word);
+      /* POSIX.2 says ``...a diagnostic message shall be written to standard
+        error, and the utility shall not exit with a zero exit status, but
+        shall continue processing any remaining operands and shall write the
+         value accumulated at the time the error was detected to standard
+        output.''  Yecch. */
+#if 0
+      ret = 0;         /* return partially-converted value from strtoimax */
+#endif
+      conversion_error = 1;
     }
   else if (errno == ERANGE)
-    {
-      builtin_error ("%s: %s", garglist->word->word, strerror(ERANGE));
-      return (1);
-    }
+    printf_erange (garglist->word->word);
 
-  *lp = ret;
   garglist = garglist->next;
-  return (0);
+  return (ret);
 }
 
-static int
-getulong (ulp)
-     unsigned long *ulp;
+static uintmax_t
+getuintmax ()
 {
-  unsigned long ret;
+  uintmax_t ret;
   char *ep;
 
   if (garglist == 0)
-    {
-      *ulp = (unsigned long)0;
-      return (0);
-    }
+    return (0);
 
   if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
-    {
-      *ulp =  (unsigned long)asciicode ();
-      return (0);
-    }
+    return asciicode ();
 
   errno = 0;
-  ret = strtoul (garglist->word->word, &ep, 0);
+  ret = strtoumax (garglist->word->word, &ep, 0);
   
   if (*ep)
     {
-      builtin_error ("%s: illegal number", garglist->word->word);
-      return (1);
+      sh_invalidnum (garglist->word->word);
+      /* Same POSIX.2 conversion error requirements as getintmax(). */
+      ret = 0;
+      conversion_error = 1;
     }
   else if (errno == ERANGE)
-    {
-      builtin_error ("%s: %s", garglist->word->word, strerror(ERANGE));
-      return (1);
-    }
+    printf_erange (garglist->word->word);
 
-  *ulp = ret;
   garglist = garglist->next;
-  return (0);
+  return (ret);
 }
 
-static double
-getdouble ()
+static floatmax_t
+getfloatmax ()
 {
-  double ret;
+  floatmax_t ret;
+  char *ep;
 
   if (garglist == 0)
-    return ((double)0);
+    return (0);
 
   if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
-    return ((double)asciicode ());
+    return asciicode ();
+
+  errno = 0;
+  ret = strtofltmax (garglist->word->word, &ep);
+
+  if (*ep)
+    {
+      sh_invalidnum (garglist->word->word);
+      /* Same thing about POSIX.2 conversion error requirements. */
+      ret = 0;
+      conversion_error = 1;
+    }
+  else if (errno == ERANGE)
+    printf_erange (garglist->word->word);
 
-  /* This should use strtod if it is available. */
-  ret = atof (garglist->word->word);
   garglist = garglist->next;
   return (ret);
 }
 
 /* NO check is needed for garglist here. */
-static int
+static intmax_t
 asciicode ()
 {
-  register int ch;
+  register intmax_t ch;
+#if defined (HANDLE_MULTIBYTE)
+  wchar_t wc;
+  size_t mblength, slen;
+#endif
+  DECLARE_MBSTATE;
+
+#if defined (HANDLE_MULTIBYTE)
+  slen = strlen (garglist->word->word+1);
+  mblength = MBLEN (garglist->word->word+1, slen);
+  if (mblength > 1)
+    {
+      mblength = mbtowc (&wc, garglist->word->word+1, slen);
+      ch = wc;         /* XXX */
+    }
+  else
+#endif
+    ch = (unsigned char)garglist->word->word[1];
 
-  ch = garglist->word->word[1];
   garglist = garglist->next;
   return (ch);
 }
+
+static SHELL_VAR *
+bind_printf_variable (name, value, flags)
+     char *name;
+     char *value;
+     int flags;
+{
+  SHELL_VAR *v;
+
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (name) == 0)
+    v = bind_variable (name, value, flags);
+  else
+    v = assign_array_element (name, value, flags);
+#else /* !ARRAY_VARS */
+  v = bind_variable (name, value, flags);
+#endif /* !ARRAY_VARS */
+
+  if (v && readonly_p (v) == 0 && noassign_p (v) == 0)
+    VUNSETATTR (v, att_invisible);
+
+  return v;
+}