[Bug 194466] New: Update printf(1) to support %T and %q bashisms [patch]
bugzilla-noreply at freebsd.org
bugzilla-noreply at freebsd.org
Sun Oct 19 12:53:40 UTC 2014
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=194466
Bug ID: 194466
Summary: Update printf(1) to support %T and %q bashisms [patch]
Product: Base System
Version: 9.3-PRERELEASE
Hardware: Any
OS: Any
Status: Needs Triage
Severity: Affects Only Me
Priority: Normal
Component: bin
Assignee: freebsd-bugs at FreeBSD.org
Reporter: mi at ALDAN.algebra.com
The printf-implementation built into recent bash offers a very useful
feature:
%(datefmt)T
causes printf to output the date-time string resulting
from using datefmt as a format string for strftime(3).
The corresponding argument is an integer representing the
number of seconds since the epoch. Two special argument
values may be used: -1 represents the current time, and
-2 represents the time the shell was invoked.
The feature makes it easier for script to produce time-stamped logs --
it can now be done without spawning a separate date(1) process for each
log-entry.
The enclosed patch updates our printf to support it. My patch one-ups
bash-implementation by making the format string optional -- if simple
%T is used, the "%a %b %d %T %Z %Y" is used by default (same as
date(1)).
My implementation also provides for future additions of other
format-characters, which may need their own (sub)formats. For example,
some day we may extend the built-in printf to print information about
the process itself and any of the jobs -- using the same
format-flexibility as ps(1):
printf %(%cpu %mem)P -1
The patch also implements the %q feature of bash's printf to help
escape special characters -- for completeness.
While here, I also update the oft-used PF-macro to simply use
printf(3), instead of the asprintf->fputs->free sequence.
This hunk is separate from the new functionality, though, and
can be omitted, if the current code is correct.
Environment:
System: FreeBSD narawntapu 9.3-PRERELEASE
Fix:
Index: printf.1
===================================================================
--- printf.1 (revision 273142)
+++ printf.1 (working copy)
@@ -290,6 +290,29 @@
.Cm \e0 Ns Ar num
instead of
.Cm \e Ns Ar num .
+.It Cm q
+As for
+.Cm s ,
+but with certain characters (such as braces, blanks, parentheses and the like)
+escaped. This makes the result (re)usable as shell argument or input.
+.It Cm [(format)]T
+Interpret the argument as the number of seconds since Unix epoch and turn
+it into a timestamp according to
+.Ar format .
+If format is omitted, the string "%a %b %d %T %Z %Y" will be used -- this
+is, what date(1) does by default. The specified format is passed to
strftime(3).
+As with
+.Nm bash ,
+two special values for seconds-value are recognized to mean:
+.Bl -tag -width Ds
+.It -1
+current time. This allows scripts to log messages with timestamps without
+executing date(1) for each message.
+.It -2
+time, when the current sh-process was started. When using the standalone
+.Nm
+(rather than the sh-builtin), -2 and -1 are identical.
+.El
.It Cm \&%
Print a `%'; no argument is used.
.El
@@ -322,6 +345,7 @@
.Xr echo 1 ,
.Xr sh 1 ,
.Xr printf 3
+.Xr strftime 3
.Sh STANDARDS
The
.Nm
Index: printf.c
===================================================================
--- printf.c (revision 273142)
+++ printf.c (working copy)
@@ -47,7 +47,12 @@
"$FreeBSD$";
#endif /* not lint */
+#include <sys/time.h>
#include <sys/types.h>
+#ifdef SHELL
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#endif
#include <err.h>
#include <errno.h>
@@ -67,20 +72,15 @@
#endif
#define PF(f, func) do { \
- char *b = NULL; \
if (havewidth) \
if (haveprec) \
- (void)asprintf(&b, f, fieldwidth, precision, func); \
+ (void)printf(f, fieldwidth, precision, func); \
else \
- (void)asprintf(&b, f, fieldwidth, func); \
+ (void)printf(f, fieldwidth, func); \
else if (haveprec) \
- (void)asprintf(&b, f, precision, func); \
+ (void)printf(f, precision, func); \
else \
- (void)asprintf(&b, f, func); \
- if (b) { \
- (void)fputs(b, stdout); \
- free(b); \
- } \
+ (void)printf(f, func); \
} while (0)
static int asciicode(void);
@@ -93,6 +93,9 @@
static const char
*getstr(void);
static char *mknum(char *, char);
+#if defined(SHELL)
+static void getstarttime(struct timeval *ptv);
+#endif
static void usage(void);
static char **gargv;
@@ -191,7 +194,10 @@
{
static const char skip1[] = "#'-+ 0";
static const char skip2[] = "0123456789";
+ static const char needescaping[] = "\n\t !\"$&'()*,;<>?[\\]^`{|}";
+
char *fmt;
+ static char *subfmt;
int fieldwidth, haveprec, havewidth, mod_ldbl, precision;
char convch, nextch;
@@ -281,6 +287,25 @@
PF(start, p);
break;
}
+ case 'q': {
+ const char *s, *p;
+
+ s = getstr();
+ /*
+ * #-signs only need escaping, if found at the beginning.
+ * ~ is only escaped at the beginning or after ':' or '='
+ * Other special characters are always escaped:
+ */
+ for (p = s; *p != '\0'; p++) {
+ if ((*p == '#' && p == s) ||
+ (*p == '~' &&
+ (p == s || p[-1] == ':' || p[-1] == '=')) ||
+ index(needescaping, *p)) {
+ putchar('\\');
+ }
+ putchar(*p);
+ }
+ }
case 's': {
const char *p;
@@ -319,6 +344,98 @@
PF(start, (double)p);
break;
}
+ case '(': {
+ char *end = index(fmt + 1, ')');
+
+ if (end == NULL) {
+ warnx("No closing ')' detected");
+ return (NULL);
+ }
+
+ if (subfmt != NULL) {
+ warnx("Subformat \"%s\" already set", subfmt);
+ return NULL;
+ }
+ if (end == fmt)
+ break; /* Empty format -- presume default */
+ asprintf(&subfmt, "%c%.*s", nextch,
+ (int)(end - fmt - 1), fmt + 1);
+ if (subfmt == NULL) {
+ warnx("%s", strerror(ENOMEM));
+ return (NULL);
+ }
+ *fmt = nextch;
+ *end = '%';
+ return (end);
+ }
+ case 'T': {
+ char *timebuf = NULL;
+ size_t buflen, timelen;
+ const char *timefmt;
+ intmax_t val;
+ uintmax_t uval;
+ struct timeval tv;
+
+ if (subfmt == NULL)
+ timefmt = "%a %b %d %T %Z %Y"; /* date(1)'s default */
+ else
+ timefmt = subfmt;
+ if (getnum(&val, &uval, 1) || (tv.tv_sec = val) != val) {
+ if (subfmt != NULL) {
+ free(subfmt);
+ subfmt = NULL;
+ }
+ *rval = 1;
+ return NULL;
+ }
+ switch (tv.tv_sec) {
+ case -2:
+#if defined(SHELL)
+ getstarttime(&tv);
+ break;
+ /* Outside of shell, -2 and -1 both mean current time */
+#endif
+ case -1:
+ gettimeofday(&tv, NULL);
+ break;
+ default:
+ if (tv.tv_sec < 0) {
+ warnx("Can not turn %jd into date/time", val);
+ if (subfmt != NULL) {
+ free(subfmt);
+ subfmt = NULL;
+ }
+ *rval = 1;
+ return NULL;
+ }
+ }
+ /*
+ * No (easy) way to know, how big the buffer needs to be.
+ * Start small and double as necessary:
+ */
+ buflen = strlen(timefmt) * 4; /* harmless overestimate */
+ do {
+ buflen *= 2;
+ timebuf = realloc(timebuf, buflen);
+ if (timebuf == NULL) {
+ if (subfmt != NULL) {
+ free(subfmt);
+ subfmt = NULL;
+ }
+ warnx("%s", strerror(ENOMEM));
+ return (NULL);
+ }
+ timelen = strftime(timebuf, buflen, timefmt,
+ localtime(&tv.tv_sec));
+ } while(timelen == 0);
+ fputs(timebuf, stdout);
+ free(timebuf);
+ if (subfmt != NULL) {
+ free(subfmt);
+ subfmt = NULL;
+ }
+ break;
+ }
default:
warnx("illegal format character %c", convch);
return (NULL);
@@ -559,7 +676,43 @@
return (ch);
}
+#ifdef SHELL
static void
+getstarttime(struct timeval *ptv)
+{
+ static time_t start; /* We cache this value */
+ int mib[4];
+ size_t len;
+ struct kinfo_proc kp;
+
+ ptv->tv_sec = 0;
+ ptv->tv_usec = 0; /* Not using microseconds at all */
+
+ if (start != 0) {
+ ptv->tv_sec = start;
+ return;
+ }
+
+ len = 4;
+ if (sysctlnametomib("kern.proc.pid", mib, &len) != 0) {
+ warn("Unable to obtain script start-time: %s",
+ "sysctlnametomib");
+ return;
+ }
+ mib[3] = getpid();
+ len = sizeof(kp);
+ if (sysctl(mib, 4, &kp, &len, NULL, 0) != 0) {
+ warn("Unable to obtain script start-time: %s",
+ "sysctl");
+ return;
+ }
+ /* XXX Should we check, whether len still equals sizeof(kinfo_proc)? */
+
+ start = ptv->tv_sec = kp.ki_start.tv_sec;
+}
+#endif
+
+static void
usage(void)
{
(void)fprintf(stderr, "usage: printf format [arguments ...]\n");
--
You are receiving this mail because:
You are the assignee for the bug.
More information about the freebsd-bugs
mailing list