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 2, 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>
#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 "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 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 *, 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, fmtlen;
- 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)
{
return (EXECUTION_SUCCESS);
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
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 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. */
+#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;
}
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 */
- if (*fmt == 'l' || *fmt == 'L' || *fmt == 'h')
- {
- fmt++;
- foundmod = 1;
- }
+ 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);
}
convch = *fmt;
- nextch = fmt[1];
- fmt[1] = '\0';
+ thisch = modstart[0];
+ nextch = modstart[1];
+ modstart[0] = convch;
+ modstart[1] = '\0';
+
switch(convch)
{
case 'c':
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;
}
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;
}
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 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);
}
- fmt[1] = nextch;
+ modstart[0] = thisch;
+ modstart[1] = nextch;
+ }
+
+ if (ferror (stdout))
+ {
+ /* PRETURN will print error message. */
+ PRETURN (EXECUTION_FAILURE);
}
}
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 */
#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;
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 */
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;
}
}
/* 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)
/* 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
-
-#define isoctal(c) ((c) >= '0' && (c) <= '7')
-
-#define OCTVALUE(c) ((c) - '0')
-
-#ifndef isxdigit
-# define isxdigit(c) (isdigit((c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
-#endif
-
-#define HEXVALUE(c) \
- ((c) >= 'a' && (c) <= 'f' ? (c)-'a'+10 : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0')
-
/* 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. */
+ sequence; we also bypass the other processing specific to %b arguments. */
static int
-tescape (estart, trans_squote, cp, sawc)
+tescape (estart, cp, lenp, sawc)
char *estart;
- int trans_squote;
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++)
{
case 'b': *cp = '\b'; break;
- case 'e': *cp = '\033'; break; /* ESC -- non-ANSI */
+ case 'e':
+ case 'E': *cp = '\033'; break; /* ESC -- non-ANSI */
case 'f': *cp = '\f'; break;
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++)
+ /* 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;
+ *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++)
- evalue = (evalue * 8) + OCTVALUE (*p);
- *cp = evalue;
- 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':
- for (temp = 3, evalue = 0; isxdigit (*p) && temp--; p++)
+ for (temp = 2, evalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++)
evalue = (evalue * 16) + HEXVALUE (*p);
- if (temp == 3)
+ if (p == estart + 1)
{
- builtin_error ("missing hex digit for \\x");
+ builtin_error (_("missing hex digit for \\x"));
*cp = '\\';
return 0;
}
- *cp = evalue;
+ *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;
- 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 defined (HANDLE_MULTIBYTE)
+ char mbch[25];
+ int mbind, mblen;
+#endif
- if (string == 0 || *string == '\0')
+ if (string == 0 || len == 0)
{
if (sawc)
*sawc = 0;
return ((char *)NULL);
}
- ret = xmalloc (len + 1);
+ ret = (char *)xmalloc (len + 1);
for (r = ret, s = string; s && *s; )
{
c = *s++;
continue;
}
temp = 0;
- s += tescape (s, 0, &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)
break;
}
+#if defined (HANDLE_MULTIBYTE)
+ for (mbind = 0; mbind < mblen; mbind++)
+ *r++ = mbch[mbind];
+#else
*r++ = c;
+#endif
}
*r = '\0';
}
static char *
-mklong (str, ch)
+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 if (blen > 1)
+ {
+ 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 int
+#if defined (PREFER_STDARG)
+vbprintf (const char *format, ...)
+#else
+vbprintf (format, va_alist)
+ const char *format;
+ va_dcl
+#endif
+{
+ va_list args;
+ size_t nlen;
+ int blen;
+
+ SH_VA_START (args, format);
+ blen = vsnprintf (vbuf + vblen, vbsize - vblen, format, args);
+ va_end (args);
+
+ 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);
+ }
+
+ 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, 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
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;
- /* If we use 0 as the third argument, we can handle octal and hex, which
- legal_number does not. (This was
- if (legal_number (garglist->word->word, &ret) == 0)
- ) */
- ret = strtol (garglist->word->word, &ep, 0);
- if (*ep != '\0')
+ ret = strtoimax (garglist->word->word, &ep, 0);
+
+ if (*ep)
{
- builtin_error ("%s: invalid 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: invalid 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;
+}