Subject: bug#16468: join

From: Pádraig Brady <[email-masked]>
Date: Thu, 01 May 2014 00:53:16 +0100

On 01/17/2014 12:00 AM, Bernhard Voelker wrote:
> On 01/16/2014 07:10 PM, Eric Blake wrote:
>> On 01/16/2014 10:46 AM, barry kesner wrote:
>>>   How do you tell join this without resorting.  The files are huge!
>>
>> Unfortunately, there isn't any really good way, short of re-processing
>> the files to make the data appear sorted in the order join expects.
>> That said, it certainly appears that for your given data, you can write
>> a sed filter that can reprocess on a line-by-line basis, and feed that
>> into join, without the penalty of having to re-sort the entire file and
>> without having to have the processed file stored in your file system all
>> at once.  It also seems possible to write a post filter to get back to
>> the style of the line in the original file.  Here, extensions such as bash's
>>   join <(infilter file1) <(infilter file2) | outfilter
>> make it easier to type (where the trick is to now write the correct sed
>> scripts to serve as infilter and outfilter) than the alternative of
>> having to use named fifos for limiting yourself to just POSIX semantics.
> 
> Hum, isn't such number conversion filtering exactly what numfmt
> wasn't designed for?  But wait ...
> 
>   $ numfmt --field 1 --format='%020f' < f2
>               99980081    1
>              100002129   1
>              100002136   2
>              100002162   3
> 
> ... it doesn't support leading zeros, unfortunately. ;-/
> Wouldn't this be a nice enhancement?

I've needed this a few times so I added it in the attached.

thanks,
Pádraig.

>From a6c62fba1867f1df9b8d2a8c9264606784fddc77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Wed, 30 Apr 2014 15:05:15 +0100
Subject: [PATCH] numfmt: support zero padding using --format="%010f"

* src/numfmt.c (setup_padding_buffer): Simplify the code by not
explicitly dealing with heap exhaustion.
(parse_format_string): Likewise.  Handle multiple grouping
modifiers as does the standard printf.  Handle the new leading
zero --format modifier.
(double_to_human): Use more defensive coding against overwriting
stack buffers.  Honor the leading zeros width.
(usage): Mention the leading zero --format modifier.
(main): Allow --padding in combo with a --format (width),
as the number of leading zeros are useful independent of
the main field width.
* doc/coreutils.texi (numfmt invocation): Likewise.
* tests/misc/numfmt.pl: Add new test cases.
* NEWS: Mention the improvement.
---
 NEWS                 |    3 ++
 doc/coreutils.texi   |    6 ++-
 src/numfmt.c         |   94 ++++++++++++++++++++++++++++++++------------------
 tests/misc/numfmt.pl |   25 +++++++++++---
 4 files changed, 87 insertions(+), 41 deletions(-)

diff --git a/NEWS b/NEWS
index 7855a48..904aace 100644
--- a/NEWS
+++ b/NEWS
@@ -66,6 +66,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   causing name look-up errors.  Also look-ups are first done outside the chroot,
   in case the look-up within the chroot fails due to library conflicts etc.
 
+  numfmt supports zero padding of numbers using the standard --printf
+  syntax of a leading zero, for example --format="%010f".
+
   shred now supports multiple passes on GNU/Linux tape devices by rewinding
   the tape before each pass, avoids redundant writes to empty files,
   uses direct I/O for all passes where possible, and attempts to clear
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 12002fc..a949ffc 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -2293,10 +2293,12 @@ Convert the number in input field @var{n} (default: 1).
 @item --format=@var{format}
 @opindex --format
 Use printf-style floating FORMAT string.  The @var{format} string must contain
-one @samp{%f} directive, optionally with @samp{'}, @samp{-}, or width
+one @samp{%f} directive, optionally with @samp{'}, @samp{-}, @samp{0}, or width
 modifiers.  The @samp{'} modifier will enable @option{--grouping}, the @samp{-}
 modifier will enable left-aligned @option{--padding} and the width modifier will
-enable right-aligned @option{--padding}.
+enable right-aligned @option{--padding}.  The @samp{0} width modifier
+(without the @samp{-} modifier) will generate leading zeros on the number,
+up to the specified width.
 
 @item --from=@var{unit}
 @opindex --from
diff --git a/src/numfmt.c b/src/numfmt.c
index 63411f3..c744875 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -169,6 +169,7 @@ static int grouping = 0;
 static char *padding_buffer = NULL;
 static size_t padding_buffer_size = 0;
 static long int padding_width = 0;
+static long int zero_padding_width = 0;
 static const char *format_str = NULL;
 static char *format_str_prefix = NULL;
 static char *format_str_suffix = NULL;
@@ -272,7 +273,7 @@ suffix_power (const char suf)
 }
 
 static inline const char *
-suffix_power_character (unsigned int power)
+suffix_power_char (unsigned int power)
 {
   switch (power)
     {
@@ -705,6 +706,21 @@ double_to_human (long double val, int precision,
                  char *buf, size_t buf_size,
                  enum scale_type scale, int group, enum round_type round)
 {
+  int num_size;
+  char fmt[64];
+  verify (sizeof (fmt) > (INT_BUFSIZE_BOUND (zero_padding_width)
+                          + INT_BUFSIZE_BOUND (precision)
+                          + 10 /* for %.Lf  etc.  */));
+
+  char *pfmt = fmt;
+  *pfmt++ = '%';
+
+  if (group)
+    *pfmt++ = '\'';
+
+  if (zero_padding_width)
+    pfmt += snprintf (pfmt, sizeof (fmt) - 1, "0%ld", zero_padding_width);
+
   devmsg ("double_to_human:\n");
 
   if (scale == scale_none)
@@ -717,9 +733,10 @@ double_to_human (long double val, int precision,
               "  no scaling, returning (grouped) value: %'.*Lf\n" :
               "  no scaling, returning value: %.*Lf\n", precision, val);
 
-      int i = snprintf (buf, buf_size, (group) ? "%'.*Lf" : "%.*Lf",
-                        precision, val);
-      if (i < 0 || i >= (int) buf_size)
+      stpcpy (pfmt, ".*Lf");
+
+      num_size = snprintf (buf, buf_size, fmt, precision, val);
+      if (num_size < 0 || num_size >= (int) buf_size)
         error (EXIT_FAILURE, 0,
                _("failed to prepare value '%Lf' for printing"), val);
       return;
@@ -761,11 +778,16 @@ double_to_human (long double val, int precision,
 
   devmsg ("  after rounding, value=%Lf * %0.f ^ %d\n", val, scale_base, power);
 
-  snprintf (buf, buf_size, (show_decimal_point) ? "%.1Lf%s" : "%.0Lf%s",
-            val, suffix_power_character (power));
+  stpcpy (pfmt, show_decimal_point ? ".1Lf%s" : ".0Lf%s");
+
+  /* buf_size - 1 used here to ensure place for possible scale_IEC_I suffix.  */
+  num_size = snprintf (buf, buf_size - 1, fmt, val, suffix_power_char (power));
+  if (num_size < 0 || num_size >= (int) buf_size - 1)
+    error (EXIT_FAILURE, 0,
+           _("failed to prepare value '%Lf' for printing"), val);
 
   if (scale == scale_IEC_I && power > 0)
-    strncat (buf, "i", buf_size - strlen (buf) - 1);
+    strncat (buf, "i", buf_size - num_size - 1);
 
   devmsg ("  returning value: %s\n", quote (buf));
 
@@ -798,10 +820,7 @@ setup_padding_buffer (size_t min_size)
     return;
 
   padding_buffer_size = min_size + 1;
-  padding_buffer = realloc (padding_buffer, padding_buffer_size);
-  if (!padding_buffer)
-    error (EXIT_FAILURE, 0, _("out of memory (requested %zu bytes)"),
-           padding_buffer_size);
+  padding_buffer = xrealloc (padding_buffer, padding_buffer_size);
 }
 
 void
@@ -906,8 +925,8 @@ UNIT options:\n"), stdout);
       fputs (_("\n\
 FORMAT must be suitable for printing one floating-point argument '%f'.\n\
 Optional quote (%'f) will enable --grouping (if supported by current locale).\n\
-Optional width value (%10f) will pad output. Optional negative width values\n\
-(%-10f) will left-pad output.\n\
+Optional width value (%10f) will pad output. Optional zero (%010f) width\n\
+will zero pad the number. Optional negative values (%-10f) will left align.\n\
 "), stdout);
 
       printf (_("\n\
@@ -967,6 +986,7 @@ parse_format_string (char const *fmt)
   size_t suffix_pos;
   long int pad = 0;
   char *endptr = NULL;
+  bool zero_padding = false;
 
   for (i = 0; !(fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1)
     {
@@ -977,13 +997,24 @@ parse_format_string (char const *fmt)
     }
 
   i++;
-  i += strspn (fmt + i, " ");
-  if (fmt[i] == '\'')
+  while (true)
     {
-      grouping = 1;
-      i++;
+      size_t skip = strspn (fmt + i, " ");
+      i += skip;
+      if (fmt[i] == '\'')
+        {
+          grouping = 1;
+          i++;
+        }
+      else if (fmt[i] == '0')
+        {
+          zero_padding = true;
+          i++;
+        }
+      else if (! skip)
+        break;
     }
-  i += strspn (fmt + i, " ");
+
   errno = 0;
   pad = strtol (fmt + i, &endptr, 10);
   if (errno == ERANGE)
@@ -992,6 +1023,9 @@ parse_format_string (char const *fmt)
 
   if (endptr != (fmt + i) && pad != 0)
     {
+      if (debug && padding_width && !(zero_padding && pad > 0))
+        error (0, 0, _("--format padding overridding --padding"));
+
       if (pad < 0)
         {
           padding_alignment = MBS_ALIGN_LEFT;
@@ -999,8 +1033,12 @@ parse_format_string (char const *fmt)
         }
       else
         {
-          padding_width = pad;
+          if (zero_padding)
+            zero_padding_width = pad;
+          else
+            padding_width = pad;
         }
+
     }
   i = endptr - fmt;
 
@@ -1009,7 +1047,7 @@ parse_format_string (char const *fmt)
 
   if (fmt[i] != 'f')
     error (EXIT_FAILURE, 0, _("invalid format %s,"
-                              " directive must be %%['][-][N]f"),
+                              " directive must be %%[0]['][-][N]f"),
            quote (fmt));
   i++;
   suffix_pos = i;
@@ -1020,19 +1058,9 @@ parse_format_string (char const *fmt)
              quote (fmt));
 
   if (prefix_len)
-    {
-      format_str_prefix = xstrndup (fmt, prefix_len);
-      if (!format_str_prefix)
-        error (EXIT_FAILURE, 0, _("out of memory (requested %zu bytes)"),
-               prefix_len + 1);
-    }
+    format_str_prefix = xstrndup (fmt, prefix_len);
   if (fmt[suffix_pos] != '\0')
-    {
-      format_str_suffix = strdup (fmt + suffix_pos);
-      if (!format_str_suffix)
-        error (EXIT_FAILURE, 0, _("out of memory (requested %zu bytes)"),
-               strlen (fmt + suffix_pos));
-    }
+    format_str_suffix = xstrdup (fmt + suffix_pos);
 
   devmsg ("format String:\n  input: %s\n  grouping: %s\n"
                    "  padding width: %ld\n  alignment: %s\n"
@@ -1462,8 +1490,6 @@ main (int argc, char **argv)
 
   if (format_str != NULL && grouping)
     error (EXIT_FAILURE, 0, _("--grouping cannot be combined with --format"));
-  if (format_str != NULL && padding_width > 0)
-    error (EXIT_FAILURE, 0, _("--padding cannot be combined with --format"));
 
   /* Warn about no-op.  */
   if (debug && scale_from == scale_none && scale_to == scale_none
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index ca3c896..dfb4b2e 100755
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -695,11 +695,11 @@ my @Tests =
              {EXIT=>1}],
      ['fmt-err-4', '--format "%d"',
              {ERR=>"$prog: invalid format '%d', " .
-                   "directive must be %['][-][N]f\n"},
+                   "directive must be %[0]['][-][N]f\n"},
              {EXIT=>1}],
      ['fmt-err-5', '--format "% -43 f"',
              {ERR=>"$prog: invalid format '% -43 f', " .
-                   "directive must be %['][-][N]f\n"},
+                   "directive must be %[0]['][-][N]f\n"},
              {EXIT=>1}],
      ['fmt-err-6', '--format "%f %f"',
              {ERR=>"$prog: format '%f %f' has too many % directives\n"},
@@ -708,9 +708,6 @@ my @Tests =
              {ERR=>"$prog: invalid format '%123456789012345678901234567890f'".
                    " (width overflow)\n"},
              {EXIT=>1}],
-     ['fmt-err-8', '--format "%f" --padding 20',
-             {ERR=>"$prog: --padding cannot be combined with --format\n"},
-             {EXIT=>1}],
      ['fmt-err-9', '--format "%f" --grouping',
              {ERR=>"$prog: --grouping cannot be combined with --format\n"},
              {EXIT=>1}],
@@ -748,6 +745,17 @@ my @Tests =
      ['fmt-15', '--format "--%100000f--" --to=si 4200',
                   {OUT=>"--" . " " x 99996 . "4.2K--" }],
 
+     # --format padding overrides --padding
+     ['fmt-16', '--format="%6f" --padding=66 1234',{OUT=>"  1234"}],
+
+     # zero padding
+     ['fmt-17', '--format="%06f" 1234',{OUT=>"001234"}],
+     # also support spaces (which are ignored as spacing is handled separately)
+     ['fmt-18', '--format="%0 6f" 1234',{OUT=>"001234"}],
+     # handle generic padding in combination
+     ['fmt-22', '--format="%06f" --padding=7 1234',{OUT=>" 001234"}],
+     ['fmt-23', '--format="%06f" --padding=-7 1234',{OUT=>"001234 "}],
+
 
      ## Check all errors again, this time with --invalid=fail
      ##  Input will be printed without conversion,
@@ -881,6 +889,13 @@ my @Locale_Tests =
      ['lcl-fmt-4', '--format "--%-10f--" --to=si 5000000',
              {OUT=>"--5,0M      --"},
              {ENV=>"LC_ALL=$locale"}],
+     # handle zero/grouping in combination
+     ['lcl-fmt-5', '--format="%\'06f" 1234',{OUT=>"01 234"},
+             {ENV=>"LC_ALL=$locale"}],
+     ['lcl-fmt-6', '--format="%0\'6f" 1234',{OUT=>"01 234"},
+             {ENV=>"LC_ALL=$locale"}],
+     ['lcl-fmt-7', '--format="%0\'\'6f" 1234',{OUT=>"01 234"},
+             {ENV=>"LC_ALL=$locale"}],
 
   );
 if ($locale ne 'C')
-- 
1.7.7.6