diff options
Diffstat (limited to 'shell_cmds/printf')
21 files changed, 1222 insertions, 0 deletions
diff --git a/shell_cmds/printf/printf.1 b/shell_cmds/printf/printf.1 new file mode 100644 index 0000000..226b87b --- /dev/null +++ b/shell_cmds/printf/printf.1 @@ -0,0 +1,418 @@ +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)printf.1 8.1 (Berkeley) 6/6/93 +.\" $FreeBSD$ +.\" +.Dd July 1, 2020 +.Dt PRINTF 1 +.Os +.Sh NAME +.Nm printf +.Nd formatted output +.Sh SYNOPSIS +.Nm +.Ar format Op Ar arguments ... +.Sh DESCRIPTION +The +.Nm +utility formats and prints its arguments, after the first, under control +of the +.Ar format . +The +.Ar 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 +.Ar argument . +.Pp +The +.Ar arguments +after the first are treated as strings if the corresponding format is +either +.Cm c , b +or +.Cm s ; +otherwise it is evaluated as a C constant, with the following extensions: +.Pp +.Bl -bullet -offset indent -compact +.It +A leading plus or minus sign is allowed. +.It +If the leading character is a single or double quote, the value is the +character code of the next character. +.El +.Pp +The format string is reused as often as necessary to satisfy the +.Ar arguments . +Any extra format specifications are evaluated with zero or the null +string. +.Pp +Character escape sequences are in backslash notation as defined in the +.St -ansiC , +with extensions. +The characters and their meanings +are as follows: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Cm \ea +Write a <bell> character. +.It Cm \eb +Write a <backspace> character. +.It Cm \ef +Write a <form-feed> character. +.It Cm \en +Write a <new-line> character. +.It Cm \er +Write a <carriage return> character. +.It Cm \et +Write a <tab> character. +.It Cm \ev +Write a <vertical tab> character. +.It Cm \e\' +Write a <single quote> character. +.It Cm \e\e +Write a backslash character. +.It Cm \e Ns Ar num +Write a byte whose +value is the 1-, 2-, or 3-digit +octal number +.Ar num . +Multibyte characters can be constructed using multiple +.Cm \e Ns Ar num +sequences. +.El +.Pp +Each format specification is introduced by the percent character +(``%''). +The remainder of the format specification includes, +in the following order: +.Bl -tag -width Ds +.It "Zero or more of the following flags:" +.Bl -tag -width Ds +.It Cm # +A `#' character +specifying that the value should be printed in an ``alternate form''. +For +.Cm b , c , d , s +and +.Cm u +formats, this option has no effect. +For the +.Cm o +formats the precision of the number is increased to force the first +character of the output string to a zero. +For the +.Cm x +.Pq Cm X +format, a non-zero result has the string +.Li 0x +.Pq Li 0X +prepended to it. +For +.Cm a , A , e , E , f , F , g +and +.Cm G +formats, the result will always contain a decimal point, even if no +digits follow the point (normally, a decimal point only appears in the +results of those formats if a digit follows the decimal point). +For +.Cm g +and +.Cm G +formats, trailing zeros are not removed from the result as they +would otherwise be; +.It Cm \&\- +A minus sign `\-' which specifies +.Em left adjustment +of the output in the indicated field; +.It Cm \&+ +A `+' character specifying that there should always be +a sign placed before the number when using signed formats. +.It Sq \&\ \& +A space specifying that a blank should be left before a positive number +for a signed format. +A `+' overrides a space if both are used; +.It Cm \&0 +A zero `0' character indicating that zero-padding should be used +rather than blank-padding. +A `\-' overrides a `0' if both are used; +.El +.It "Field Width:" +An optional digit string specifying a +.Em field width ; +if the output string has fewer bytes than the field width it will +be blank-padded on the left (or right, if the left-adjustment indicator +has been given) to make up the field width (note that a leading zero +is a flag, but an embedded zero is part of a field width); +.It Precision: +An optional period, +.Sq Cm \&.\& , +followed by an optional digit string giving a +.Em precision +which specifies the number of digits to appear after the decimal point, +for +.Cm e +and +.Cm f +formats, or the maximum number of bytes to be printed +from a string; if the digit string is missing, the precision is treated +as zero; +.It Format: +A character which indicates the type of format to use (one of +.Cm diouxXfFeEgGaAcsb ) . +The uppercase formats differ from their lowercase counterparts only in +that the output of the former is entirely in uppercase. +The floating-point format specifiers +.Pq Cm fFeEgGaA +may be prefixed by an +.Cm L +to request that additional precision be used, if available. +.El +.Pp +A field width or precision may be +.Sq Cm \&* +instead of a digit string. +In this case an +.Ar argument +supplies the field width or precision. +.Pp +The format characters and their meanings are: +.Bl -tag -width Fl +.It Cm diouXx +The +.Ar argument +is printed as a signed decimal (d or i), unsigned octal, unsigned decimal, +or unsigned hexadecimal (X or x), respectively. +.It Cm fF +The +.Ar argument +is printed in the style `[\-]ddd.ddd' where the number of d's +after the decimal point is equal to the precision specification for +the argument. +If the precision is missing, 6 digits are given; if the precision +is explicitly 0, no digits and no decimal point are printed. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm eE +The +.Ar argument +is printed in the style +.Cm e +.Sm off +.Sq Op - Ar d.ddd No \(+- Ar dd +.Sm on +where there +is one digit before the decimal point and the number after is equal to +the precision specification for the argument; when the precision is +missing, 6 digits are produced. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm gG +The +.Ar argument +is printed in style +.Cm f +.Pq Cm F +or in style +.Cm e +.Pq Cm E +whichever gives full precision in minimum space. +.It Cm aA +The +.Ar argument +is printed in style +.Sm off +.Sq Op - Ar h.hhh No \(+- Li p Ar d +.Sm on +where there is one digit before the hexadecimal point and the number +after is equal to the precision specification for the argument; +when the precision is missing, enough digits are produced to convey +the argument's exact double-precision floating-point representation. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm c +The first byte of +.Ar argument +is printed. +.It Cm s +Bytes from the string +.Ar argument +are printed until the end is reached or until the number of bytes +indicated by the precision specification is reached; however if the +precision is 0 or missing, the string is printed entirely. +.It Cm b +As for +.Cm s , +but interpret character escapes in backslash notation in the string +.Ar argument . +The permitted escape sequences are slightly different in that +octal escapes are +.Cm \e0 Ns Ar num +instead of +.Cm \e Ns Ar num +and that an additional escape sequence +.Cm \ec +stops further output from this +.Nm +invocation. +.It Cm n$ +Allows reordering of the output according to +.Ar argument . +.It Cm \&% +Print a `%'; no argument is used. +.El +.Pp +The decimal point +character is defined in the program's locale (category +.Dv LC_NUMERIC ) . +.Pp +In no case does a non-existent or small field width cause truncation of +a field; padding takes place only if the specified field width exceeds +the actual width. +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Print the string +.Qq hello : +.Bd -literal -offset indent +$ printf "%s\en" hello +hello +.Ed +.Pp +Same as above, but notice that the format string is not quoted and hence we +do not get the expected behavior: +.Bd -literal -offset indent +$ printf %s\en hello +hellon$ +.Ed +.Pp +Print arguments forcing sign only for the first argument: +.Bd -literal -offset indent +$ printf "%+d\en%d\en%d\en" 1 -2 13 ++1 +-2 +13 +.Ed +.Pp +Same as above, but the single format string will be applied to the three +arguments: +.Bd -literal -offset indent +$ printf "%+d\en" 1 -2 13 ++1 +-2 ++13 +.Ed +.Pp +Print number using only two digits after the decimal point: +.Bd -literal -offset indent +$ printf "%.2f\en" 31.7456 +31.75 +.Ed +.Sh COMPATIBILITY +The traditional +.Bx +behavior of converting arguments of numeric formats not beginning +with a digit to the ASCII +code of the first character is not supported. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr echo 1 , +.Xr sh 1 , +.Xr printf 3 +.Sh STANDARDS +The +.Nm +command is expected to be compatible with the +.St -p1003.2 +specification. +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.3 Reno . +It is modeled +after the standard library function, +.Xr printf 3 . +.Sh CAVEATS +ANSI hexadecimal character constants were deliberately not provided. +.Pp +Trying to print a dash ("-") as the first character causes +.Nm +to interpret the dash as a program argument. +.Nm -- +must be used before +.Ar format . +.Pp +If the locale contains multibyte characters +(such as UTF-8), +the +.Cm c +format and +.Cm b +and +.Cm s +formats with a precision +may not operate as expected. +.Sh BUGS +Since the floating point numbers are translated from ASCII +to floating-point and then back again, floating-point precision may be lost. +(By default, the number is translated to an IEEE-754 double-precision +value before being printed. +The +.Cm L +modifier may produce additional precision, depending on the hardware platform.) +.Pp +The escape sequence \e000 is the string terminator. +When present in the argument for the +.Cm b +format, the argument will be truncated at the \e000 character. +.Pp +Multibyte characters are not recognized in format strings (this is only +a problem if +.Ql % +can appear inside a multibyte character). diff --git a/shell_cmds/printf/printf.c b/shell_cmds/printf/printf.c new file mode 100644 index 0000000..6cc09ae --- /dev/null +++ b/shell_cmds/printf/printf.c @@ -0,0 +1,697 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> + * Copyright 2014 Garrett D'Amore <garrett@damore.org> + * Copyright 2010 Nexenta Systems, Inc. All rights reserved. + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * Important: This file is used both as a standalone program /usr/bin/printf + * and as a builtin for /bin/sh (#define SHELL). + */ + +#ifndef SHELL +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1989, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ +#endif + +#ifndef lint +#if 0 +static char const sccsid[] = "@(#)printf.c 8.1 (Berkeley) 7/20/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/types.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> + +#ifdef SHELL +#define main printfcmd +#include "bltin/bltin.h" +#include "options.h" +#endif + +#define PF(f, func) do { \ + if (havewidth) \ + if (haveprec) \ + (void)printf(f, fieldwidth, precision, func); \ + else \ + (void)printf(f, fieldwidth, func); \ + else if (haveprec) \ + (void)printf(f, precision, func); \ + else \ + (void)printf(f, func); \ +} while (0) + +static int asciicode(void); +static char *printf_doformat(char *, int *); +static int escape(char *, int, size_t *); +static int getchr(void); +static int getfloating(long double *, int); +static int getint(int *); +static int getnum(intmax_t *, uintmax_t *, int); +static const char + *getstr(void); +static char *mknum(char *, char); +static void usage(void); + +static const char digits[] = "0123456789"; + +static char end_fmt[1]; + +static int myargc; +static char **myargv; +static char **gargv; +static char **maxargv; + +int +main(int argc, char *argv[]) +{ + size_t len; + int end, rval; + char *format, *fmt, *start; +#ifndef SHELL + int ch; + + (void) setlocale(LC_ALL, ""); +#endif + +#ifdef SHELL + nextopt(""); + argc -= argptr - argv; + argv = argptr; +#else + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + usage(); + return (1); + } + argc -= optind; + argv += optind; +#endif + + if (argc < 1) { + usage(); + return (1); + } + +#ifdef SHELL + INTOFF; +#endif + /* + * Basic algorithm is to scan the format string for conversion + * specifications -- once one is found, find out if the field + * width or precision is a '*'; if it is, gather up value. Note, + * format strings are reused as necessary to use up the provided + * arguments, arguments of zero/null string are provided to use + * up the format string. + */ + fmt = format = *argv; + escape(fmt, 1, &len); /* backslash interpretation */ + rval = end = 0; + gargv = ++argv; + + for (;;) { + maxargv = gargv; + + myargv = gargv; + for (myargc = 0; gargv[myargc]; myargc++) + /* nop */; + start = fmt; + while (fmt < format + len) { + if (fmt[0] == '%') { + fwrite(start, 1, fmt - start, stdout); + if (fmt[1] == '%') { + /* %% prints a % */ + putchar('%'); + fmt += 2; + } else { + fmt = printf_doformat(fmt, &rval); + if (fmt == NULL || fmt == end_fmt) { +#ifdef SHELL + INTON; +#endif + return (fmt == NULL ? 1 : rval); + } + end = 0; + } + start = fmt; + } else + fmt++; + if (gargv > maxargv) + maxargv = gargv; + } + gargv = maxargv; + + if (end == 1) { + warnx("missing format character"); +#ifdef SHELL + INTON; +#endif + return (1); + } + fwrite(start, 1, fmt - start, stdout); + if (!*gargv) { +#ifdef SHELL + INTON; +#endif + return (rval); + } + /* Restart at the beginning of the format string. */ + fmt = format; + end = 1; + } + /* NOTREACHED */ +} + + +static char * +printf_doformat(char *fmt, int *rval) +{ + static const char skip1[] = "#'-+ 0"; + int fieldwidth, haveprec, havewidth, mod_ldbl, precision; + char convch, nextch; + char start[strlen(fmt) + 1]; + char **fargv; + char *dptr; + int l; + + dptr = start; + *dptr++ = '%'; + *dptr = 0; + + fmt++; + + /* look for "n$" field index specifier */ + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + if (gargv > maxargv) + maxargv = gargv; + fmt += l + 1; + + /* save format argument */ + fargv = gargv; + } else { + fargv = NULL; + } + + /* skip to field width */ + while (*fmt && strchr(skip1, *fmt) != NULL) { + *dptr++ = *fmt++; + *dptr = 0; + } + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&fieldwidth)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + havewidth = 1; + + *dptr++ = '*'; + *dptr = 0; + } else { + havewidth = 0; + + /* skip to possible '.', get following precision */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + + if (*fmt == '.') { + /* precision present? */ + fmt++; + *dptr++ = '.'; + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&precision)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + haveprec = 1; + *dptr++ = '*'; + *dptr = 0; + } else { + haveprec = 0; + + /* skip to conversion char */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + } else + haveprec = 0; + if (!*fmt) { + warnx("missing format character"); + return (NULL); + } + *dptr++ = *fmt; + *dptr = 0; + + /* + * Look for a length modifier. POSIX doesn't have these, so + * we only support them for floating-point conversions, which + * are extensions. This is useful because the L modifier can + * be used to gain extra range and precision, while omitting + * it is more likely to produce consistent results on different + * architectures. This is not so important for integers + * because overflow is the only bad thing that can happen to + * them, but consider the command printf %a 1.1 + */ + if (*fmt == 'L') { + mod_ldbl = 1; + fmt++; + if (!strchr("aAeEfFgG", *fmt)) { + warnx("bad modifier L for %%%c", *fmt); + return (NULL); + } + } else { + mod_ldbl = 0; + } + + /* save the current arg offset, and set to the format arg */ + if (fargv != NULL) { + gargv = fargv; + } + + convch = *fmt; + nextch = *++fmt; + + *fmt = '\0'; + switch (convch) { + case 'b': { + size_t len; + char *p; + int getout; + + /* Convert "b" to "s" for output. */ + start[strlen(start) - 1] = 's'; + if ((p = strdup(getstr())) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + getout = escape(p, 0, &len); + /* + * Special-case conversion of zero; print it instead of + * feeding an empty string to printf("%s") which would not + * print anything. + */ + if (len == 1 && p[0] == '\0') { + putchar(p[0]); + } else { + PF(start, p); + } + /* Restore format for next loop. */ + + free(p); + if (getout) + return (end_fmt); + break; + } + case 'c': { + char p; + + p = getchr(); + if (p != '\0') + PF(start, p); + break; + } + case 's': { + const char *p; + + p = getstr(); + PF(start, p); + break; + } + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { + char *f; + intmax_t val; + uintmax_t uval; + int signedconv; + + signedconv = (convch == 'd' || convch == 'i'); + if ((f = mknum(start, convch)) == NULL) + return (NULL); + if (getnum(&val, &uval, signedconv)) + *rval = 1; + if (signedconv) + PF(f, val); + else + PF(f, uval); + break; + } + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'a': case 'A': { + long double p; + + if (getfloating(&p, mod_ldbl)) + *rval = 1; + if (mod_ldbl) + PF(start, p); + else + PF(start, (double)p); + break; + } + default: + warnx("illegal format character %c", convch); + return (NULL); + } + *fmt = nextch; + /* return the gargv to the next element */ + return (fmt); +} + +static char * +mknum(char *str, char ch) +{ + static char *copy; + static size_t copy_size; + char *newcopy; + size_t len, newlen; + + len = strlen(str) + 2; + if (len > copy_size) { + newlen = ((len + 1023) >> 10) << 10; + if ((newcopy = realloc(copy, newlen)) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + copy = newcopy; + copy_size = newlen; + } + + memmove(copy, str, len - 3); + copy[len - 3] = 'j'; + copy[len - 2] = ch; + copy[len - 1] = '\0'; + return (copy); +} + +static int +escape(char *fmt, int percent, size_t *len) +{ + char *save, *store, c; + int value; + + for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) { + if (c != '\\') { + *store = c; + continue; + } + switch (*++fmt) { + case '\0': /* EOS, user error */ + *store = '\\'; + *++store = '\0'; + *len = store - save; + return (0); + case '\\': /* backslash */ + case '\'': /* single quote */ + *store = *fmt; + break; + case 'a': /* bell/alert */ + *store = '\a'; + break; + case 'b': /* backspace */ + *store = '\b'; + break; + case 'c': + if (!percent) { + *store = '\0'; + *len = store - save; + return (1); + } + *store = 'c'; + break; + case 'f': /* form-feed */ + *store = '\f'; + break; + case 'n': /* newline */ + *store = '\n'; + break; + case 'r': /* carriage-return */ + *store = '\r'; + break; + case 't': /* horizontal tab */ + *store = '\t'; + break; + case 'v': /* vertical tab */ + *store = '\v'; + break; + /* octal constant */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c = (!percent && *fmt == '0') ? 4 : 3; + for (value = 0; + c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) { + value <<= 3; + value += *fmt - '0'; + } + --fmt; + if (percent && value == '%') { + *store++ = '%'; + *store = '%'; + } else + *store = (char)value; + break; + default: + *store = *fmt; + break; + } + } + *store = '\0'; + *len = store - save; + return (0); +} + +static int +getchr(void) +{ + if (!*gargv) + return ('\0'); + return ((int)**gargv++); +} + +static const char * +getstr(void) +{ + if (!*gargv) + return (""); + return (*gargv++); +} + +static int +getint(int *ip) +{ + intmax_t val; + uintmax_t uval; + int rval; + + if (getnum(&val, &uval, 1)) + return (1); + rval = 0; + if (val < INT_MIN || val > INT_MAX) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + *ip = (int)val; + return (rval); +} + +static int +getnum(intmax_t *ip, uintmax_t *uip, int signedconv) +{ + char *ep; + int rval; + + if (!*gargv) { + *ip = *uip = 0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + if (signedconv) + *ip = asciicode(); + else + *uip = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (signedconv) + *ip = strtoimax(*gargv, &ep, 0); + else + *uip = strtoumax(*gargv, &ep, 0); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } + else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +getfloating(long double *dp, int mod_ldbl) +{ + char *ep; + int rval; + + if (!*gargv) { + *dp = 0.0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + *dp = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (mod_ldbl) + *dp = strtold(*gargv, &ep); + else + *dp = strtod(*gargv, &ep); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +asciicode(void) +{ + int ch; + wchar_t wch; + mbstate_t mbs; + + ch = (unsigned char)**gargv; + if (ch == '\'' || ch == '"') { + memset(&mbs, 0, sizeof(mbs)); + switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) { + case (size_t)-2: + case (size_t)-1: + wch = (unsigned char)gargv[0][1]; + break; + case 0: + wch = 0; + break; + } + ch = wch; + } + ++gargv; + return (ch); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: printf format [arguments ...]\n"); +} diff --git a/shell_cmds/printf/printf.plist.part b/shell_cmds/printf/printf.plist.part new file mode 100644 index 0000000..a040cf7 --- /dev/null +++ b/shell_cmds/printf/printf.plist.part @@ -0,0 +1,18 @@ + <dict> + <key>OpenSourceProject</key> + <string>printf</string> + <key>OpenSourceVersion</key> + <string>2020-07-01</string> + <key>OpenSourceWebsiteURL</key> + <string>https://github.com/freebsd/freebsd/tree/master/usr.bin/printf</string> + <key>OpenSourceSCM</key> + <string>https://github.com/freebsd/freebsd/tree/master/usr.bin/printf</string> + <key>OpenSourceImportDate</key> + <string>2020-07-23</string> + <key>OpenSourceModifications</key> + <array> + <string>65959587: UNIX Conformance | /usr/bin/printf, multiple format assertion failures in VSC</string> + </array> + <key>OpenSourceLicense</key> + <string>bsd</string> + </dict> diff --git a/shell_cmds/printf/tests/Makefile b/shell_cmds/printf/tests/Makefile new file mode 100644 index 0000000..b4ea79b --- /dev/null +++ b/shell_cmds/printf/tests/Makefile @@ -0,0 +1,23 @@ +# $FreeBSD$ + +PACKAGE= tests + +TAP_TESTS_SH= legacy_test + +${PACKAGE}FILES+= regress.b.out +${PACKAGE}FILES+= regress.bwidth.out +${PACKAGE}FILES+= regress.d.out +${PACKAGE}FILES+= regress.f.out +${PACKAGE}FILES+= regress.l1.out +${PACKAGE}FILES+= regress.l2.out +${PACKAGE}FILES+= regress.m1.out +${PACKAGE}FILES+= regress.m2.out +${PACKAGE}FILES+= regress.m3.out +${PACKAGE}FILES+= regress.m4.out +${PACKAGE}FILES+= regress.m5.out +${PACKAGE}FILES+= regress.missingpos1.out +${PACKAGE}FILES+= regress.s.out +${PACKAGE}FILES+= regress.sh +${PACKAGE}FILES+= regress.zero.out + +.include <bsd.test.mk> diff --git a/shell_cmds/printf/tests/Makefile.depend b/shell_cmds/printf/tests/Makefile.depend new file mode 100644 index 0000000..f80275d --- /dev/null +++ b/shell_cmds/printf/tests/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/shell_cmds/printf/tests/legacy_test.sh b/shell_cmds/printf/tests/legacy_test.sh new file mode 100644 index 0000000..a919deb --- /dev/null +++ b/shell_cmds/printf/tests/legacy_test.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# $FreeBSD$ + +SRCDIR="$(dirname "${0}")"; export SRCDIR + +m4 /AppleInternal/Tests/shell_cmds/regress.m4 "${SRCDIR}/regress.sh" | sh diff --git a/shell_cmds/printf/tests/regress.b.out b/shell_cmds/printf/tests/regress.b.out new file mode 100644 index 0000000..0373d93 --- /dev/null +++ b/shell_cmds/printf/tests/regress.b.out @@ -0,0 +1 @@ +abcdef diff --git a/shell_cmds/printf/tests/regress.bwidth.out b/shell_cmds/printf/tests/regress.bwidth.out new file mode 100644 index 0000000..9e2ff61 --- /dev/null +++ b/shell_cmds/printf/tests/regress.bwidth.out @@ -0,0 +1 @@ + a diff --git a/shell_cmds/printf/tests/regress.d.out b/shell_cmds/printf/tests/regress.d.out new file mode 100644 index 0000000..da83c08 --- /dev/null +++ b/shell_cmds/printf/tests/regress.d.out @@ -0,0 +1 @@ +123, 123,00123,00123,00123 diff --git a/shell_cmds/printf/tests/regress.f.out b/shell_cmds/printf/tests/regress.f.out new file mode 100644 index 0000000..127cb8a --- /dev/null +++ b/shell_cmds/printf/tests/regress.f.out @@ -0,0 +1 @@ +42.250000,-42.250 ,inf,nan diff --git a/shell_cmds/printf/tests/regress.l1.out b/shell_cmds/printf/tests/regress.l1.out new file mode 100644 index 0000000..9be0dc9 --- /dev/null +++ b/shell_cmds/printf/tests/regress.l1.out @@ -0,0 +1 @@ +228 diff --git a/shell_cmds/printf/tests/regress.l2.out b/shell_cmds/printf/tests/regress.l2.out new file mode 100644 index 0000000..9be0dc9 --- /dev/null +++ b/shell_cmds/printf/tests/regress.l2.out @@ -0,0 +1 @@ +228 diff --git a/shell_cmds/printf/tests/regress.m1.out b/shell_cmds/printf/tests/regress.m1.out Binary files differnew file mode 100644 index 0000000..a9e063e --- /dev/null +++ b/shell_cmds/printf/tests/regress.m1.out diff --git a/shell_cmds/printf/tests/regress.m2.out b/shell_cmds/printf/tests/regress.m2.out new file mode 100644 index 0000000..cf14915 --- /dev/null +++ b/shell_cmds/printf/tests/regress.m2.out @@ -0,0 +1,2 @@ +abc +cdef
\ No newline at end of file diff --git a/shell_cmds/printf/tests/regress.m3.out b/shell_cmds/printf/tests/regress.m3.out new file mode 100644 index 0000000..bbe4e70 --- /dev/null +++ b/shell_cmds/printf/tests/regress.m3.out @@ -0,0 +1,4 @@ +%abc +%def +%ghi +%jkl diff --git a/shell_cmds/printf/tests/regress.m4.out b/shell_cmds/printf/tests/regress.m4.out new file mode 100644 index 0000000..5e0b5d4 --- /dev/null +++ b/shell_cmds/printf/tests/regress.m4.out @@ -0,0 +1 @@ +0,0.000000,, diff --git a/shell_cmds/printf/tests/regress.m5.out b/shell_cmds/printf/tests/regress.m5.out new file mode 100644 index 0000000..2838468 --- /dev/null +++ b/shell_cmds/printf/tests/regress.m5.out @@ -0,0 +1 @@ +-d diff --git a/shell_cmds/printf/tests/regress.missingpos1.out b/shell_cmds/printf/tests/regress.missingpos1.out new file mode 100644 index 0000000..3b04f03 --- /dev/null +++ b/shell_cmds/printf/tests/regress.missingpos1.out @@ -0,0 +1 @@ +printf: incomplete use of n$ diff --git a/shell_cmds/printf/tests/regress.s.out b/shell_cmds/printf/tests/regress.s.out new file mode 100644 index 0000000..3d572b9 --- /dev/null +++ b/shell_cmds/printf/tests/regress.s.out @@ -0,0 +1 @@ +abc,abc diff --git a/shell_cmds/printf/tests/regress.sh b/shell_cmds/printf/tests/regress.sh new file mode 100644 index 0000000..a2cf5fb --- /dev/null +++ b/shell_cmds/printf/tests/regress.sh @@ -0,0 +1,32 @@ +# $FreeBSD$ + +REGRESSION_START($1) + +echo '1..24' + +REGRESSION_TEST(`b', `printf "abc%b%b" "def\n" "\cghi"') +REGRESSION_TEST(`d', `printf "%d,%5d,%.5d,%0*d,%.*d\n" 123 123 123 5 123 5 123') +REGRESSION_TEST(`f', `printf "%f,%-8.3f,%f,%f\n" +42.25 -42.25 inf nan') +REGRESSION_TEST(`l1', `LC_ALL=en_US.ISO8859-1 printf "%d\n" $(printf \"\\344)') +REGRESSION_TEST(`l2', `LC_ALL=en_US.UTF-8 printf "%d\n" $(printf \"\\303\\244)') +REGRESSION_TEST(`m1', `printf "%c%%%d\0\045\n" abc \"abc') +REGRESSION_TEST(`m2', `printf "abc\n\cdef"') +REGRESSION_TEST(`m3', `printf "%%%s\n" abc def ghi jkl') +REGRESSION_TEST(`m4', `printf "%d,%f,%c,%s\n"') +REGRESSION_TEST(`m5', `printf -- "-d\n"') +REGRESSION_TEST(`s', `printf "%.3s,%-5s\n" abcd abc') +REGRESSION_TEST('zero', `printf "%u%u\n" 15') +REGRESSION_TEST('zero', `printf "%d%d\n" 15') +REGRESSION_TEST('zero', `printf "%d%u\n" 15') +REGRESSION_TEST('zero', `printf "%u%d\n" 15') +REGRESSION_TEST(`missingpos1', `printf "%1\$*s" 1 1 2>&1') +REGRESSION_TEST(`missingpos1', `printf "%*1\$s" 1 1 2>&1') +REGRESSION_TEST(`missingpos1', `printf "%1\$*.*s" 1 1 1 2>&1') +REGRESSION_TEST(`missingpos1', `printf "%*1\$.*s" 1 1 1 2>&1') +REGRESSION_TEST(`missingpos1', `printf "%*.*1\$s" 1 1 1 2>&1') +REGRESSION_TEST(`missingpos1', `printf "%1\$*2\$.*s" 1 1 1 2>&1') +REGRESSION_TEST(`missingpos1', `printf "%*1\$.*2\$s" 1 1 1 2>&1') +REGRESSION_TEST(`missingpos1', `printf "%1\$*.*2\$s" 1 1 1 2>&1') +REGRESSION_TEST(`bwidth', `printf "%8.2b" "a\nb\n"') + +REGRESSION_END() diff --git a/shell_cmds/printf/tests/regress.zero.out b/shell_cmds/printf/tests/regress.zero.out new file mode 100644 index 0000000..fa8f08c --- /dev/null +++ b/shell_cmds/printf/tests/regress.zero.out @@ -0,0 +1 @@ +150 |