Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 19 Oct 2014 12:53:40 +0000
From:      bugzilla-noreply@freebsd.org
To:        freebsd-bugs@FreeBSD.org
Subject:   [Bug 194466] New: Update printf(1) to support %T and %q bashisms [patch]
Message-ID:  <bug-194466-8@https.bugs.freebsd.org/bugzilla/>

next in thread | raw e-mail | index | archive | help
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@FreeBSD.org
          Reporter: mi@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.



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?bug-194466-8>