[PATCH v2] awk: add '*' as a valid specifier for width and precision in printf
Anubhav Kokane via busybox <[email protected]> Tue, 30 Jun 2026 06:32:17 +0000
| Newsgroups | gmane.linux.busybox |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> --- 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