[PATCH v2] awk: add '*' as a valid specifier for width and precision in printf

Denys Vlasenko vda.linux at googlemail.com
Thu Jul 2 03:02:32 UTC 2026


Thank you, this looks interesting.

I used this patch as a base and committed a fix based on it,
then added another change to fix printf "%d" to correctly print large numbers.

Unfortunately, it adds a bit of bloat, but probably worth it.

Please try current git.

On Tue, Jun 30, 2026 at 8:33 AM Anubhav Kokane via busybox
<busybox at busybox.net> wrote:
>
> Parse flags, width, precision into a local fmt_buf, resolving each
> '*' via nextarg() and substituting the values into fmt_buf.
> If '*' is present, a new format string is built using literal
> prefix and fmt_buf. And if '*' is absent original format string
> is used without any changes.
>
> Signed-off-by: Anubhav Kokane <dev.anubhavk at gmail.com>
> ---
>  editors/awk.c       | 80 ++++++++++++++++++++++++++++++++++++++++-----
>  testsuite/awk.tests | 22 +++++++++++++
>  2 files changed, 94 insertions(+), 8 deletions(-)
>
> diff --git a/editors/awk.c b/editors/awk.c
> index dd8f4ac42..22a606c1d 100644
> --- a/editors/awk.c
> +++ b/editors/awk.c
> @@ -2443,9 +2443,17 @@ static char *awk_printf(node *n, size_t *len)
>                 char sv;
>                 var *arg;
>                 size_t slen;
> +               char fmt_buf[64];
> +               char *fmt_str;
> +               char *out;
> +               char *p;
> +               int has_star;
> +               int w;
>
>                 /* Find end of the next format spec, or end of line */
>                 s = f;
> +               out = fmt_buf;
> +               has_star = 0;
>                 while (1) {
>                         c = *f;
>                         if (!c) /* no percent chars found at all */
> @@ -2456,6 +2464,8 @@ static char *awk_printf(node *n, size_t *len)
>                 }
>                 /* we are past % in "....%..." */
>                 c = *f;
> +               p = f - 1; /* points to '%' */
> +               *out++ = '%';
>                 if (!c) /* "....%" */
>                         goto nul;
>                 if (c == '%') { /* "....%%...." */
> @@ -2464,11 +2474,50 @@ static char *awk_printf(node *n, size_t *len)
>                         f++;
>                         goto append; /* print "....%" part verbatim */
>                 }
> +
> +               /* flags */
> +               while (c && strchr("+- 0#", c)) {
> +                       *out++ = c;
> +                       c = *++f;
> +               }
> +
> +               /* width */
> +               if (c == '*') {
> +                       has_star = 1;
> +                       w = (int)getvar_i(evaluate(nextarg(&n), TMPVAR));
> +                       out += sprintf(out, "%d", w);
> +                       c = *++f;
> +                       if (c >= '0' && c <= '9') /* invalidate "^^^%*6d^^^" */
> +                               syntax_error("invalid format specifier");
> +               } else {
> +                       while (c >= '0' && c <= '9') {
> +                               *out++ = c;
> +                               c = *++f;
> +                       }
> +               }
> +
> +               /* precision */
> +               if (c == '.') {
> +                       *out++ = '.';
> +                       c = *++f;
> +                       if (c == '*') {
> +                               has_star = 1;
> +                               w = (int)getvar_i(evaluate(nextarg(&n), TMPVAR));
> +                               out += sprintf(out, "%d", w);
> +                               c = *++f;
> +                               if (c >= '0' && c <= '9') /* invalidate "^^^%5.*8f^^^" */
> +                                       syntax_error("invalid format specifier");
> +                       } else {
> +                               while (c >= '0' && c <= '9') {
> +                                       *out++ = c;
> +                                       c = *++f;
> +                               }
> +                       }
> +               }
> +
>                 while (1) {
>                         if (isalpha(c))
>                                 break;
> -                       if (c == '*') /* gawk supports %*d and %*.*f, we don't... */
> -                               syntax_error("%*x formats are not supported");
>                         c = *++f;
>                         if (!c) { /* "....%...." and no letter found after % */
>                                 /* Example: awk 'BEGIN { printf "^^^%^^^\n"; }' */
> @@ -2484,18 +2533,31 @@ static char *awk_printf(node *n, size_t *len)
>                 /* Result can be arbitrarily long. Example:
>                  *  printf "%99999s", "BOOM"
>                  */
> +               *out++ = c;
> +               *out = '\0';
>                 sv = *++f;
>                 *f = '\0';
> +
> +               if (has_star) {
> +                       size_t prefix_len = p - s;
> +                       size_t fmt_buf_len = strlen(fmt_buf);
> +                       fmt_str = xmalloc(prefix_len + fmt_buf_len + 1);
> +                       memcpy(fmt_str, s, prefix_len);
> +                       strcpy(fmt_str + prefix_len, fmt_buf);
> +               } else {
> +                       fmt_str = s;
> +               }
> +
>                 if (c == 'c') {
>                         char cc = is_numeric(arg) ? getvar_i(arg) : *getvar_s(arg);
> -                       char *r = xasprintf(s, cc ? cc : '^' /* else strlen will be wrong */);
> +                       char *r = xasprintf(fmt_str, cc ? cc : '^' /* else strlen will be wrong */);
>                         slen = strlen(r);
>                         if (cc == '\0') /* if cc is NUL, re-format the string with it */
> -                               sprintf(r, s, cc);
> +                               sprintf(r, fmt_str, cc);
>                         s = r;
>                 } else {
>                         if (c == 's') {
> -                               s = xasprintf(s, getvar_s(arg));
> +                               s = xasprintf(fmt_str, getvar_s(arg));
>                         } else {
>                                 double d = getvar_i(arg);
>                                 if (strchr("diouxX", c)) {
> @@ -2506,17 +2568,19 @@ static char *awk_printf(node *n, size_t *len)
>  //but some replacements are not equivalent:
>  //%09d -> %09s: breaks zero-padding;
>  //%+d -> %+s: won't prepend +; etc
> -                                       s = xasprintf(s, (int)d);
> +                                       s = xasprintf(fmt_str, (int)d);
>                                 } else if (strchr("eEfFgGaA", c)) {
> -                                       s = xasprintf(s, d);
> +                                       s = xasprintf(fmt_str, d);
>                                 } else {
>                                         /* gawk 5.1.1 printf("%W") prints "%W", does not error out */
> -                                       s = xstrndup(s, f - s);
> +                                       s = xstrdup(fmt_str);
>                                 }
>                         }
>                         slen = strlen(s);
>                 }
>                 *f = sv;
> +               if (has_star)
> +                       free(fmt_str);
>   append:
>                 if (i == 0) {
>                         b = s;
> diff --git a/testsuite/awk.tests b/testsuite/awk.tests
> index df1078bdb..76eb0f920 100755
> --- a/testsuite/awk.tests
> +++ b/testsuite/awk.tests
> @@ -24,6 +24,28 @@ testing "awk if operator >= "  "awk 'BEGIN{if(23>=23) print \"foo\"}'" "foo\n" "
>  testing "awk if operator < "   "awk 'BEGIN{if(2 < 13) print \"foo\"}'" "foo\n" "" ""
>  testing "awk if string == "    "awk 'BEGIN{if(\"a\"==\"ab\") print \"bar\"}'" "" "" ""
>
> +# width and precision
> +testing "awk only width" \
> +"awk 'BEGIN { printf \"%*d\", 10, -42 }'" "       -42" "" ""
> +
> +testing "awk negative width" \
> +"awk 'BEGIN { printf \"%*s\", -10, \"hello\" }'" "hello     " "" ""
> +
> +testing "awk only precision" \
> +"awk 'BEGIN { printf \"%.*f\n\", 2, 3.14159 }'" "3.14\n" "" ""
> +
> +testing "awk width and precision" \
> +"awk 'BEGIN { printf \"%*.*f\", 10, 2, 3.14159 }'" "      3.14" "" ""
> +
> +testing "awk 0 padding with width and precision" \
> +"awk 'BEGIN { printf \"%0*.*f\", 10, 2, 3.14 }'" "0000003.14" "" ""
> +
> +testing "awk multiple format specifications" \
> +"awk 'BEGIN { printf \"%d %*d %d\", 1, 5, 2, 3 }'" "1     2 3" "" ""
> +
> +testing "awk width with char" \
> +"awk 'BEGIN { printf \"%*c\", 5, 65 }'" "    A" "" ""
> +
>  # 4294967295 = 0xffffffff
>  testing "awk bitwise op"  "awk '{ print or(4294967295,1) }'" "4294967295\n" "" "\n"
>
> --
> 2.43.0
>
> _______________________________________________
> busybox mailing list
> busybox at busybox.net
> https://lists.busybox.net/mailman/listinfo/busybox


More information about the busybox mailing list