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.
$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
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>
#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)
#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;
{
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)
{
return (EXECUTION_SUCCESS);
format = list->word->word;
+ tw = 0;
garglist = list->next;
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;
}
if (*fmt == '%') /* %% prints a % */
{
- putchar ('%');
+ PC ('%');
continue;
}
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 */
if (*fmt == 0)
{
- builtin_error ("`%s': missing format character", start);
+ builtin_error (_("`%s': missing format character"), start);
PRETURN (EXECUTION_FAILURE);
}
bind_var_to_int (var, tw);
else
{
- builtin_error ("%s: invalid variable name", var);
+ sh_invalidid (var);
PRETURN (EXECUTION_FAILURE);
}
}
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;
}
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;
}
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;
}
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;
#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);
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 */
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
/* 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. */
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;
{
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;
/* 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;
}
*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
{
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;
continue;
}
temp = 0;
- s += tescape (s, 0, &c, &temp);
+ s += tescape (s, &c, &temp);
if (temp)
{
if (sawc)
}
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)
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 ()
{
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
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);
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)
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