[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