summaryrefslogtreecommitdiffstats
path: root/shell_cmds/printf
diff options
context:
space:
mode:
authorCameron Katri <me@cameronkatri.com>2021-05-09 14:20:58 -0400
committerCameron Katri <me@cameronkatri.com>2021-05-09 14:20:58 -0400
commit5fd83771641d15c418f747bd343ba6738d3875f7 (patch)
tree5abf0f78f680d9837dbd93d4d4c3933bb7509599 /shell_cmds/printf
downloadapple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.gz
apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.zst
apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.zip
Import macOS userland
adv_cmds-176 basic_cmds-55 bootstrap_cmds-116.100.1 developer_cmds-66 diskdev_cmds-667.40.1 doc_cmds-53.60.1 file_cmds-321.40.3 mail_cmds-35 misc_cmds-34 network_cmds-606.40.1 patch_cmds-17 remote_cmds-63 shell_cmds-216.60.1 system_cmds-880.60.2 text_cmds-106
Diffstat (limited to 'shell_cmds/printf')
-rw-r--r--shell_cmds/printf/printf.1418
-rw-r--r--shell_cmds/printf/printf.c697
-rw-r--r--shell_cmds/printf/printf.plist.part18
-rw-r--r--shell_cmds/printf/tests/Makefile23
-rw-r--r--shell_cmds/printf/tests/Makefile.depend11
-rw-r--r--shell_cmds/printf/tests/legacy_test.sh6
-rw-r--r--shell_cmds/printf/tests/regress.b.out1
-rw-r--r--shell_cmds/printf/tests/regress.bwidth.out1
-rw-r--r--shell_cmds/printf/tests/regress.d.out1
-rw-r--r--shell_cmds/printf/tests/regress.f.out1
-rw-r--r--shell_cmds/printf/tests/regress.l1.out1
-rw-r--r--shell_cmds/printf/tests/regress.l2.out1
-rw-r--r--shell_cmds/printf/tests/regress.m1.outbin0 -> 7 bytes
-rw-r--r--shell_cmds/printf/tests/regress.m2.out2
-rw-r--r--shell_cmds/printf/tests/regress.m3.out4
-rw-r--r--shell_cmds/printf/tests/regress.m4.out1
-rw-r--r--shell_cmds/printf/tests/regress.m5.out1
-rw-r--r--shell_cmds/printf/tests/regress.missingpos1.out1
-rw-r--r--shell_cmds/printf/tests/regress.s.out1
-rw-r--r--shell_cmds/printf/tests/regress.sh32
-rw-r--r--shell_cmds/printf/tests/regress.zero.out1
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
new file mode 100644
index 0000000..a9e063e
--- /dev/null
+++ b/shell_cmds/printf/tests/regress.m1.out
Binary files differ
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