Bash-4.3 distribution sources and documentation
[platform/upstream/bash.git] / builtins / printf.def
index e447633..7f29126 100644 (file)
@@ -1,7 +1,7 @@
 This file is printf.def, from which is created printf.c.
 It implements the builtin "printf" in Bash.
 
-Copyright (C) 1997-2009 Free Software Foundation, Inc.
+Copyright (C) 1997-2010 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -35,11 +35,18 @@ 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 format specifications described in printf(1)
-and printf(3), printf interprets:
+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
@@ -72,9 +79,12 @@ $END
 #  include <inttypes.h>
 #endif
 
+#include "posixtime.h"
 #include "../bashansi.h"
 #include "../bashintl.h"
 
+#define NEED_STRFTIME_DECL
+
 #include "../shell.h"
 #include "shmbutil.h"
 #include "stdc.h"
@@ -117,7 +127,7 @@ extern int errno;
     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, fieldwidth, func); \
+      nw = vflag ? vbprintf (f, precision, func) : printf (f, precision, func); \
     else \
       nw = vflag ? vbprintf (f, func) : printf (f, func); \
     tw += nw; \
@@ -153,7 +163,8 @@ extern int errno;
       else if (vbuf) \
        vbuf[0] = 0; \
       terminate_immediately--; \
-      fflush (stdout); \
+      if (ferror (stdout) == 0) \
+       fflush (stdout); \
       if (ferror (stdout)) \
        { \
          sh_wrerror (); \
@@ -167,17 +178,19 @@ extern int errno;
 #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
 
 #if !HAVE_VSNPRINTF
-extern int vsnprintf __P((char *, size_t, const char *, ...)) __attribute__((__format__ (printf, 3, 4)));
+extern int vsnprintf __P((char *, size_t, const char *, va_list)) __attribute__((__format__ (printf, 3, 0)));
 #endif
 
 static void printf_erange __P((char *));
 static int printstr __P((char *, char *, int, int, int));
-static int tescape __P((char *, char *, int *));
+static int tescape __P((char *, char *, int *, int *));
 static char *bexpand __P((char *, int, int *, int *));
 static char *vbadd __P((char *, int));
 static int vbprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2)));
@@ -224,6 +237,10 @@ printf_builtin (list)
   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;
@@ -244,6 +261,8 @@ printf_builtin (list)
 #endif
            {
              vflag = 1;
+             if (vbsize == 0)
+               vbuf = xmalloc (vbsize = 16);
              vblen = 0;
              if (vbuf)
                vbuf[0] = 0;
@@ -301,8 +320,17 @@ printf_builtin (list)
              fmt++;
              /* A NULL third argument to tescape means to bypass the
                 special processing for arguments to %b. */
-             fmt += tescape (fmt, &nextch, (int *)NULL);
+#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;
            }
@@ -401,6 +429,82 @@ 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;
@@ -435,8 +539,11 @@ printf_builtin (list)
                    r = printstr (start, xp, rlen, fieldwidth, precision);
                    if (r < 0)
                      {
-                       sh_wrerror ();
-                       clearerr (stdout);
+                       if (ferror (stdout) == 0)
+                         {
+                           sh_wrerror ();
+                           clearerr (stdout);
+                         }
                        retval = EXECUTION_FAILURE;
                      }
                    free (xp);
@@ -459,7 +566,7 @@ printf_builtin (list)
                else if (ansic_shouldquote (p))
                  xp = ansic_quote (p, 0, (int *)0);
                else
-                 xp = sh_backslash_quote (p);
+                 xp = sh_backslash_quote (p, 0, 1);
                if (xp)
                  {
                    /* Use printstr to get fieldwidth and precision right. */
@@ -558,8 +665,7 @@ printf_builtin (list)
 
       if (ferror (stdout))
        {
-         sh_wrerror ();
-         clearerr (stdout);
+         /* PRETURN will print error message. */
          PRETURN (EXECUTION_FAILURE);
        }
     }
@@ -592,12 +698,9 @@ printstr (fmt, string, len, fieldwidth, precision)
 #endif
   int padlen, nc, ljust, i;
   int fw, pr;                  /* fieldwidth and precision */
+  intmax_t mfw, mpr;
 
-#if 0
-  if (string == 0 || *string == '\0')
-#else
   if (string == 0 || len == 0)
-#endif
     return 0;
 
 #if 0
@@ -608,6 +711,8 @@ printstr (fmt, string, len, fieldwidth, precision)
 
   ljust = fw = 0;
   pr = -1;
+  mfw = 0;
+  mpr = -1;
 
   /* skip flags */
   while (strchr (SKIP1, *fmt))
@@ -617,7 +722,7 @@ printstr (fmt, string, len, fieldwidth, precision)
       fmt++;
     }
 
-  /* get fieldwidth, if present */
+  /* get fieldwidth, if present.  rely on caller to clamp fieldwidth at INT_MAX */
   if (*fmt == '*')
     {
       fmt++;
@@ -630,9 +735,11 @@ printstr (fmt, string, len, fieldwidth, precision)
     }
   else if (DIGIT (*fmt))
     {
-      fw = *fmt++ - '0';
+      mfw = *fmt++ - '0';
       while (DIGIT (*fmt))
-       fw = (fw * 10) + (*fmt++ - '0');
+       mfw = (mfw * 10) + (*fmt++ - '0');
+      /* Error if fieldwidth > INT_MAX here? */
+      fw = (mfw < 0 || mfw > INT_MAX) ? INT_MAX : mfw;
     }
 
   /* get precision, if present */
@@ -646,9 +753,11 @@ printstr (fmt, string, len, fieldwidth, precision)
        }
       else if (DIGIT (*fmt))
        {
-         pr = *fmt++ - '0';
+         mpr = *fmt++ - '0';
          while (DIGIT (*fmt))
-           pr = (pr * 10) + (*fmt++ - '0');
+           mpr = (mpr * 10) + (*fmt++ - '0');
+         /* Error if precision > INT_MAX here? */
+         pr = (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr;
        }
     }
 
@@ -656,7 +765,7 @@ 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
@@ -699,15 +808,18 @@ printstr (fmt, string, len, fieldwidth, precision)
    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, sawc)
+tescape (estart, cp, lenp, sawc)
      char *estart;
      char *cp;
-     int *sawc;
+     int *lenp, *sawc;
 {
   register char *p;
   int temp, c, evalue;
+  unsigned long uvalue;
 
   p = estart;
+  if (lenp)
+    *lenp = 1;
 
   switch (c = *p++)
     {
@@ -743,14 +855,10 @@ tescape (estart, cp, sawc)
        *cp = evalue & 0xFF;
        break;
 
-      /* And, as another extension, we allow \xNNN, where each N is a
+      /* And, as another extension, we allow \xNN, where each N is a
         hex digit. */
       case 'x':
-#if 0
-       for (evalue = 0; ISXDIGIT ((unsigned char)*p); p++)
-#else
        for (temp = 2, evalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++)
-#endif
          evalue = (evalue * 16) + HEXVALUE (*p);
        if (p == estart + 1)
          {
@@ -761,6 +869,30 @@ tescape (estart, cp, sawc)
        *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;
@@ -799,12 +931,12 @@ bexpand (string, len, sawc, lenp)
 {
   int temp;
   char *ret, *r, *s, c;
+#if defined (HANDLE_MULTIBYTE)
+  char mbch[25];
+  int mbind, mblen;
+#endif
 
-#if 0
-  if (string == 0 || *string == '\0')
-#else
   if (string == 0 || len == 0)
-#endif
     {
       if (sawc)
        *sawc = 0;
@@ -823,7 +955,12 @@ bexpand (string, len, sawc, lenp)
          continue;
        }
       temp = 0;
-      s += tescape (s, &c, &temp);
+#if defined (HANDLE_MULTIBYTE)
+      memset (mbch, '\0', sizeof (mbch));
+      s += tescape (s, mbch, &mblen, &temp);
+#else
+      s += tescape (s, &c, (int *)NULL, &temp);
+#endif
       if (temp)
        {
          if (sawc)
@@ -831,7 +968,12 @@ bexpand (string, len, sawc, lenp)
          break;
        }
 
+#if defined (HANDLE_MULTIBYTE)
+      for (mbind = 0; mbind < mblen; mbind++)
+       *r++ = mbch[mbind];
+#else
       *r++ = c;
+#endif      
     }
 
   *r = '\0';
@@ -967,6 +1109,9 @@ getint ()
 
   ret = getintmax ();
 
+  if (garglist == 0)
+    return ret;
+
   if (ret > INT_MAX)
     {
       printf_erange (garglist->word->word);
@@ -1107,12 +1252,19 @@ bind_printf_variable (name, value, flags)
      char *value;
      int flags;
 {
+  SHELL_VAR *v;
+
 #if defined (ARRAY_VARS)
   if (valid_array_reference (name) == 0)
-    return (bind_variable (name, value, flags));
+    v = bind_variable (name, value, flags);
   else
-    return (assign_array_element (name, value, flags));
+    v = assign_array_element (name, value, flags);
 #else /* !ARRAY_VARS */
-  return bind_variable (name, value, flags);
+  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;
 }