]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - builtins/printf.def
Imported from ../bash-3.2.tar.gz.
[thirdparty/bash.git] / builtins / printf.def
index bcf625c79fe689aea1c4f0601f9cd0a71bb726f9..0e1d4aa929e6343f3a7ed80cc26f32f3771f003e 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 Free Software Foundation, Inc.
+Copyright (C) 1997-2005 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -23,7 +23,7 @@ $PRODUCES printf.c
 
 $BUILTIN printf
 $FUNCTION printf_builtin
-$SHORT_DOC printf format [arguments]
+$SHORT_DOC printf [-v var] 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
@@ -32,6 +32,8 @@ 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.
+If the -v option is supplied, the output is placed into the value of the
+shell variable VAR rather than being sent to the standard output.
 $END
 
 #include <config.h>
@@ -55,48 +57,93 @@ $END
 #endif
 
 #include "../bashansi.h"
-
-#define NEED_STRTOIMAX_DECL
+#include "../bashintl.h"
 
 #include "../shell.h"
 #include "stdc.h"
 #include "bashgetopt.h"
 #include "common.h"
 
-/* This should use the ISO C constant format strings; I'll do that later. */
-#if SIZEOF_LONG < SIZEOF_LONG_LONG
-#  define INTMAX_CONV  "ll"
-#else
-#  define INTMAX_CONV  "l"
+#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 { \
+    char *b = 0; \
+    int nw; \
+    clearerr (stdout); \
     if (have_fieldwidth && have_precision) \
-      tw += printf(f, fieldwidth, precision, func); \
+      nw = asprintf(&b, f, fieldwidth, precision, func); \
     else if (have_fieldwidth) \
-      tw += printf(f, fieldwidth, func); \
+      nw = asprintf(&b, f, fieldwidth, func); \
     else if (have_precision) \
-      tw += printf(f, precision, func); \
+      nw = asprintf(&b, f, precision, func); \
     else \
-      tw += printf(f, func); \
+      nw = asprintf(&b, f, func); \
+    tw += nw; \
+    if (b) \
+      { \
+       if (vflag) \
+         (void)vbadd (b, nw); \
+       else \
+         (void)fputs (b, stdout); \
+       if (ferror (stdout)) \
+         { \
+           sh_wrerror (); \
+           clearerr (stdout); \
+           return (EXECUTION_FAILURE); \
+         } \
+       free (b); \
+      } \
   } while (0)
 
 /* We free the buffer used by mklong() if it's `too big'. */
 #define PRETURN(value) \
   do \
     { \
+      if (vflag) \
+       { \
+         bind_variable  (vname, vbuf, 0); \
+         stupidly_hack_special_variables (vname); \
+       } \
       if (conv_bufsize > 4096 ) \
        { \
-         free(conv_buf); \
+         free (conv_buf); \
          conv_bufsize = 0; \
          conv_buf = 0; \
        } \
+      if (vbsize > 4096) \
+       { \
+         free (vbuf); \
+         vbsize = 0; \
+         vbuf = 0; \
+       } \
       fflush (stdout); \
+      if (ferror (stdout)) \
+       { \
+         clearerr (stdout); \
+         return (EXECUTION_FAILURE); \
+       } \
       return (value); \
     } \
   while (0)
@@ -104,31 +151,43 @@ extern int errno;
 #define SKIP1 "#'-+ 0"
 #define LENMODS "hjlLtz"
 
-static void printstr __P((char *, char *, int, int, int));
-static int tescape __P((char *, int, char *, int *));
+static void printf_erange __P((char *));
+static int printstr __P((char *, char *, int, int, int));
+static int tescape __P((char *, char *, int *));
 static char *bexpand __P((char *, int, int *, int *));
-static char *mklong __P((char *, char *));
+static char *vbadd __P((char *, int));
+static char *mklong __P((char *, char *, size_t));
 static int getchr __P((void));
 static char *getstr __P((void));
 static int  getint __P((void));
-static long getlong __P((void));
-static unsigned long getulong __P((void));
-#if defined (HAVE_LONG_LONG)
-static long long getllong __P((void));
-static unsigned long long getullong __P((void));
-#endif
 static intmax_t getintmax __P((void));
 static uintmax_t getuintmax __P((void));
-static double getdouble __P((void));
-#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD
-static long double getldouble __P((void));
+
+#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 int 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;
+
 static char *conv_buf;
 static size_t conv_bufsize;
 
@@ -138,18 +197,36 @@ printf_builtin (list)
 {
   int ch, fieldwidth, precision;
   int have_fieldwidth, have_precision;
-  long tw;
   char convch, thisch, nextch, *format, *modstart, *fmt, *start;
 
   conversion_error = 0;
   retval = EXECUTION_SUCCESS;
+
+  vflag = 0;
+
   reset_internal_getopt ();
-  if (internal_getopt (list, "") != -1)
+  while ((ch = internal_getopt (list, "v:")) != -1)
     {
-      builtin_usage();
-      return (EX_USAGE);
+      switch (ch)
+       {
+       case 'v':
+         if (legal_identifier (vname = list_optarg))
+           {
+             vflag = 1;
+             vblen = 0;
+           }
+         else
+           {
+             sh_invalidid (vname);
+             return (EX_USAGE);
+           }
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
     }
-  list = loptend;
+  list = loptend;      /* skip over possible `--' */
 
   if (list == 0)
     {
@@ -161,6 +238,7 @@ printf_builtin (list)
     return (EXECUTION_SUCCESS);
 
   format = list->word->word;
+  tw = 0;
 
   garglist = list->next;
 
@@ -183,21 +261,20 @@ printf_builtin (list)
          precision = fieldwidth = 0;
          have_fieldwidth = have_precision = 0;
 
-
          if (*fmt == '\\')
            {
              fmt++;
-             /* A NULL fourth argument to tescape means to not do special
-                processing for \c. */
-             fmt += tescape (fmt, 1, &nextch, (int *)NULL);
-             putchar (nextch);
+             /* A NULL third argument to tescape means to bypass the
+                special processing for arguments to %b. */
+             fmt += tescape (fmt, &nextch, (int *)NULL);
+             PC (nextch);
              fmt--;    /* for loop will increment it for us again */
              continue;
            }
 
          if (*fmt != '%')
            {
-             putchar (*fmt);
+             PC (*fmt);
              continue;
            }
 
@@ -206,7 +283,7 @@ printf_builtin (list)
 
          if (*fmt == '%')              /* %% prints a % */
            {
-             putchar ('%');
+             PC ('%');
              continue;
            }
 
@@ -236,8 +313,20 @@ printf_builtin (list)
                  precision = getint ();
                }
              else
-               while (DIGIT (*fmt))
-                 fmt++;
+               {
+                 /* 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 possible format modifiers */
@@ -247,7 +336,7 @@ printf_builtin (list)
            
          if (*fmt == 0)
            {
-             builtin_error ("`%s': missing format character", start);
+             builtin_error (_("`%s': missing format character"), start);
              PRETURN (EXECUTION_FAILURE);
            }
 
@@ -288,7 +377,7 @@ printf_builtin (list)
                      bind_var_to_int (var, tw);
                    else
                      {
-                       builtin_error ("%s: invalid variable name", var);
+                       sh_invalidid (var);
                        PRETURN (EXECUTION_FAILURE);
                      }
                  }
@@ -298,21 +387,27 @@ printf_builtin (list)
            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)
+                     {
+                       sh_wrerror ();
+                       clearerr (stdout);
+                       retval = EXECUTION_FAILURE;
+                     }
                    free (xp);
                  }
 
-               if (ch)
+               if (ch || r < 0)
                  PRETURN (retval);
                break;
              }
@@ -320,15 +415,30 @@ printf_builtin (list)
            case 'q':           /* print with shell quoting */
              {
                char *p, *xp;
+               int r;
 
+               r = 0;
                p = getstr ();
-               xp = sh_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);
                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;
              }
 
@@ -336,32 +446,23 @@ printf_builtin (list)
            case 'i':
              {
                char *f;
-#if defined (HAVE_LONG_LONG)
-               if (thisch == 'l' && nextch == 'l')
-                 {
-                   long long p;
+               long p;
+               intmax_t pp;
 
-                   p = getllong ();
-                   f = mklong (start, "ll");
-                   PF(f, p);
-                 }
-               else
-#endif
-               if (thisch == 'j')
+               p = pp = getintmax ();
+               if (p != pp)
                  {
-                   intmax_t p;
-
-                   p = getintmax ();
-                   f = mklong (start, INTMAX_CONV);
-                   PF(f, p);
+                   f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2);
+                   PF (f, pp);
                  }
                else
                  {
-                   long p;
-
-                   p = getlong ();
-                   f = mklong (start, "l");
-                   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;
              }
@@ -372,31 +473,18 @@ printf_builtin (list)
            case 'X':
              {
                char *f;
-#if defined (HAVE_LONG_LONG)
-               if (thisch == 'l' && nextch == 'l')
-                 {
-                   unsigned long long p;
+               unsigned long p;
+               uintmax_t pp;
 
-                   p = getullong ();
-                   f = mklong (start, "ll");
-                   PF(f, p);
-                 }
-               else
-#endif
-               if (thisch == 'j')
+               p = pp = getuintmax ();
+               if (p != pp)
                  {
-                   uintmax_t p;
-
-                   p = getuintmax ();
-                   f = mklong (start, INTMAX_CONV);
-                   PF(f, p);
+                   f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2);
+                   PF (f, pp);
                  }
                else
                  {
-                   unsigned long p;
-
-                   p = getulong ();
-                   f = mklong (start, "l");
+                   f = mklong (start, "l", 1);
                    PF (f, p);
                  }
                break;
@@ -414,37 +502,31 @@ printf_builtin (list)
 #endif
              {
                char *f;
-#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD
-               if (thisch == 'L')
-                 {
-                   long double p;
+               floatmax_t p;
 
-                   p = getldouble ();
-                   f = mklong (start, "L");
-                   PF (f, p);
-                 }
-               else
-#endif
-                 {
-                   double p;
-
-                   p = getdouble ();
-                   f = mklong (start, "");
-                   PF (f, p);
-                 }
+               p = getfloatmax ();
+               f = mklong (start, FLOATMAX_CONV, sizeof(FLOATMAX_CONV) - 1);
+               PF (f, p);
                break;
              }
 
            /* We don't output unrecognized format characters; we print an
               error message and return a failure exit status. */
            default:
-             builtin_error ("`%c': invalid format character", convch);
+             builtin_error (_("`%c': invalid format character"), convch);
              PRETURN (EXECUTION_FAILURE);
            }
 
          modstart[0] = thisch;
          modstart[1] = nextch;
        }
+
+      if (ferror (stdout))
+       {
+         sh_wrerror ();
+         clearerr (stdout);
+         PRETURN (EXECUTION_FAILURE);
+       }
     }
   while (garglist && garglist != list->next);
 
@@ -454,8 +536,15 @@ printf_builtin (list)
   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 */
@@ -469,7 +558,11 @@ printstr (fmt, string, len, fieldwidth, precision)
   int padlen, nc, ljust, i;
   int fw, pr;                  /* fieldwidth and precision */
 
+#if 0
   if (string == 0 || *string == '\0')
+#else
+  if (string == 0 || len == 0)
+#endif
     return;
 
 #if 0
@@ -544,19 +637,22 @@ 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. */
 
@@ -566,11 +662,10 @@ printstr (fmt, string, len, fieldwidth, precision)
    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.  */
+   sequence; we also bypass the other processing specific to %b arguments.  */
 static int
-tescape (estart, trans_squote, cp, sawc)
+tescape (estart, cp, sawc)
      char *estart;
-     int trans_squote;
      char *cp;
      int *sawc;
 {
@@ -602,19 +697,13 @@ tescape (estart, trans_squote, cp, sawc)
 
       case 'v': *cp = '\v'; break;
 
-      /* %b octal constants are `\0' followed by one, two, or three
-        octal digits... */
-      case '0':
-       for (temp = 3, evalue = 0; ISOCTAL (*p) && temp--; p++)
-         evalue = (evalue * 8) + OCTVALUE (*p);
-       *cp = evalue & 0xFF;
-       break;
-
-      /* 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, evalue = c - '0'; ISOCTAL (*p) && temp--; p++)
+      /* 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;
@@ -622,11 +711,15 @@ tescape (estart, trans_squote, cp, sawc)
       /* And, as another extension, we allow \xNNN, 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 (temp == 2)
+       if (p == estart + 1)
          {
-           builtin_error ("missing hex digit for \\x");
+           builtin_error (_("missing hex digit for \\x"));
            *cp = '\\';
            return 0;
          }
@@ -637,8 +730,11 @@ tescape (estart, trans_squote, cp, sawc)
        *cp = c;
        break;
 
-      case '\'':       /* TRANS_SQUOTE != 0 means \' -> ' */
-       if (trans_squote)
+      /* 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
          {
@@ -669,7 +765,11 @@ bexpand (string, len, sawc, lenp)
   int temp;
   char *ret, *r, *s, c;
 
+#if 0
   if (string == 0 || *string == '\0')
+#else
+  if (string == 0 || len == 0)
+#endif
     {
       if (sawc)
        *sawc = 0;
@@ -688,7 +788,7 @@ bexpand (string, len, sawc, lenp)
          continue;
        }
       temp = 0;
-      s += tescape (s, 0, &c, &temp);
+      s += tescape (s, &c, &temp);
       if (temp)
        {
          if (sawc)
@@ -706,14 +806,45 @@ bexpand (string, len, sawc, lenp)
 }
 
 static char *
-mklong (str, modifiers)
+vbadd (buf, blen)
+     char *buf;
+     int blen;
+{
+  size_t nlen;
+
+  nlen = vblen + blen + 1;
+  if (nlen >= vbsize)
+    {
+      vbsize = ((nlen + 63) >> 6) << 6;
+      vbuf = (char *)xrealloc (vbuf, vbsize);
+    }
+
+  if (blen == 1)
+    vbuf[vblen++] = buf[0];
+  else
+    {
+      FASTCOPY (buf, vbuf  + vblen, blen);
+      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 vbuf;
+}
+
+static char *
+mklong (str, modifiers, mlen)
      char *str;
      char *modifiers;
+     size_t mlen;
 {
-  size_t len, slen, mlen;
+  size_t len, slen;
 
   slen = strlen (str);
-  mlen = strlen (modifiers);
   len = slen + mlen + 1;
 
   if (len > conv_bufsize)
@@ -759,152 +890,24 @@ getstr ()
 static int
 getint ()
 {
-  long ret;
+  intmax_t ret;
 
-  ret = getlong ();
+  ret = getintmax ();
 
   if (ret > INT_MAX)
     {
-      builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+      printf_erange (garglist->word->word);
       ret = INT_MAX;
     }
   else if (ret < INT_MIN)
     {
-      builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+      printf_erange (garglist->word->word);
       ret = INT_MIN;
     }
 
   return ((int)ret);
 }
 
-static long
-getlong ()
-{
-  long ret;
-  char *ep;
-
-  if (garglist == 0)
-    return (0);
-
-  if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
-    return asciicode ();
-
-  errno = 0;
-  ret = strtol (garglist->word->word, &ep, 0);
-
-  if (*ep)
-    {
-      builtin_error ("%s: invalid number", 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. */
-      ret = 0;
-      conversion_error = 1;
-    }
-  else if (errno == ERANGE)
-    builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
-
-  garglist = garglist->next;
-  return (ret);
-}
-
-static unsigned long
-getulong ()
-{
-  unsigned long ret;
-  char *ep;
-
-  if (garglist == 0)
-    return (0);
-
-  if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
-    return asciicode ();
-
-  errno = 0;
-  ret = strtoul (garglist->word->word, &ep, 0);
-  
-  if (*ep)
-    {
-      builtin_error ("%s: invalid number", garglist->word->word);
-      /* Same thing about POSIX.2 conversion error requirements as getlong(). */
-      ret = 0;
-      conversion_error = 1;
-    }
-  else if (errno == ERANGE)
-    builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
-
-  garglist = garglist->next;
-  return (ret);
-}
-
-#if defined (HAVE_LONG_LONG)
-
-static long long
-getllong ()
-{
-  long long ret;
-  char *ep;
-
-  if (garglist == 0)
-    return (0);
-
-  if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
-    return asciicode ();
-
-  errno = 0;
-  ret = strtoll (garglist->word->word, &ep, 0);
-
-  if (*ep)
-    {
-      builtin_error ("%s: invalid number", 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. */
-      ret = 0;
-      conversion_error = 1;
-    }
-  else if (errno == ERANGE)
-    builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
-
-  garglist = garglist->next;
-  return (ret);
-}
-
-static unsigned long long
-getullong ()
-{
-  unsigned long long ret;
-  char *ep;
-
-  if (garglist == 0)
-    return (0);
-
-  if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
-    return asciicode ();
-
-  errno = 0;
-  ret = strtoull (garglist->word->word, &ep, 0);
-  
-  if (*ep)
-    {
-      builtin_error ("%s: invalid number", garglist->word->word);
-      /* Same thing about POSIX.2 conversion error requirements as getlong(). */
-      ret = 0;
-      conversion_error = 1;
-    }
-  else if (errno == ERANGE)
-    builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
-
-  garglist = garglist->next;
-  return (ret);
-}
-
-#endif /* HAVE_LONG_LONG */
-
 static intmax_t
 getintmax ()
 {
@@ -922,7 +925,7 @@ getintmax ()
 
   if (*ep)
     {
-      builtin_error ("%s: invalid number", garglist->word->word);
+      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
@@ -932,7 +935,7 @@ getintmax ()
       conversion_error = 1;
     }
   else if (errno == ERANGE)
-    builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+    printf_erange (garglist->word->word);
 
   garglist = garglist->next;
   return (ret);
@@ -955,22 +958,22 @@ getuintmax ()
   
   if (*ep)
     {
-      builtin_error ("%s: invalid number", garglist->word->word);
-      /* Same thing about POSIX.2 conversion error requirements as getlong(). */
+      sh_invalidnum (garglist->word->word);
+      /* Same POSIX.2 conversion error requirements as getintmax(). */
       ret = 0;
       conversion_error = 1;
     }
   else if (errno == ERANGE)
-    builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+    printf_erange (garglist->word->word);
 
   garglist = garglist->next;
   return (ret);
 }
 
-static double
-getdouble ()
+static floatmax_t
+getfloatmax ()
 {
-  double ret;
+  floatmax_t ret;
   char *ep;
 
   if (garglist == 0)
@@ -980,52 +983,21 @@ getdouble ()
     return asciicode ();
 
   errno = 0;
-  ret = strtod (garglist->word->word, &ep);
-
-  if (*ep)
-    {
-      builtin_error ("%s: invalid number", garglist->word->word);
-      /* Same thing about POSIX.2 conversion error requirements. */
-      ret = 0;
-      conversion_error = 1;
-    }
-  else if (errno == ERANGE)
-    builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
-
-  garglist = garglist->next;
-  return (ret);
-}
-
-#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD
-static long double
-getldouble ()
-{
-  long double ret;
-  char *ep;
-
-  if (garglist == 0)
-    return (0);
-
-  if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
-    return (asciicode ());
-
-  errno = 0;
-  ret = strtold (garglist->word->word, &ep);
+  ret = strtofltmax (garglist->word->word, &ep);
 
   if (*ep)
     {
-      builtin_error ("%s: invalid number", garglist->word->word);
+      sh_invalidnum (garglist->word->word);
       /* Same thing about POSIX.2 conversion error requirements. */
       ret = 0;
       conversion_error = 1;
     }
   else if (errno == ERANGE)
-    builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+    printf_erange (garglist->word->word);
 
   garglist = garglist->next;
   return (ret);
 }
-#endif /* HAVE_LONG_DOUBLE && HAVE_DECL_STRTOLD */
 
 /* NO check is needed for garglist here. */
 static int