← Commit list

9958bbd70

Denys Vlasenko
Denys Vlasenko
2026-08-30
SUCCESS Duration: 150s

Subject

awk: disallow OFMT/CONVFMT to have more than one %SPEC, implement %s

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
gcc -Wp,-MD,applets/.usage.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer      -Iinclude -Iinclude -o applets/usage applets/usage.c

Build Log

(log yok)

Commit Diff GitHub →

editors/awk.c+49 -5
@@ -967,20 +967,64 @@ static double my_strtod_or_hexoct(char **pp)
/* -------- working with variables (set/get/copy/etc) -------- */
+/* Perform OFMT or CONVFMT float-to-string conversion.
+ * According to POSIX, awk's behavior is undefined if OFMT
+ * contains anything but a floating-point conversion specification.
+ * However, GNU awk 5.3.0 allows prefix and suffix: "N:%.1f!"
+ * integer formats (%d et al), %s formats.
+ * It even allows %c, but we don't implement this.
+ * GNU awk also supports %ld, %lx, we don't (yet). GNU awk doesn't support %lld.
+ */
static const char *fmt_num(const char *format, double n)
{
- if (n == (long long)n) {
- snprintf(g_buf, MAXVARFMT, "%lld", (long long)n);
+ if (n == trunc(n)) {
+ /* No fractionals? Try: awk -v OFMT='R:%d!' 'BEGIN { print 2**128 }' */
+ snprintf(g_buf, MAXVARFMT, "%.0f", n);
} else {
const char *s = format;
char c;
- do { c = *s; } while (c && *++s);
+ /* Find %SPEC in format string */
+ for (;;) {
+ s = strchr(s, '%');
+ if (!s)
+ goto print_verbatim;
+ if (s[1] != '%')
+ break;
+ s += 2; /* skip "%%" */
+ }
+ if (strchr(s + 1, '%') != NULL)
+//TODO: allow "%%"s in the suffix too?
+ goto INV_FMT; /* more than one %SPEC */
+
+//TODO: factor out awk_printf machinery to make %d print large numbers too. Example:
+// awk -v OFMT='R:%d!' 'BEGIN { print 184467440737095.1 }'
+// R:184467440737095!
+ /* Find the format letter */
+ for (;;) {
+ c = *++s;
+ if (!c) /* unterminated "....%SPEC" */
+ goto print_verbatim;
+ if (c == '*')
+ goto INV_FMT; /* CONVFMT/OFMT='%*f' is not allowed */
+ if (isalpha(c))
+ break;
+ }
+
if (strchr("diouxX", c)) {
snprintf(g_buf, MAXVARFMT, format, (int)n);
} else if (strchr("eEfFgGaA", c)) {
+ print_verbatim:
snprintf(g_buf, MAXVARFMT, format, n);
+ } else if (c == 's') {
+ /* GNU awk OFMT="%..s" formats number through "%.6g",
+ * then prints string through %s:
+ */
+ char nbuf[sizeof(n)*3 + 12]; /* +5 should do? +12 is paranoia */
+ sprintf(nbuf, "%.6g", n);
+ snprintf(g_buf, MAXVARFMT, format, nbuf);
} else {
+ INV_FMT:
syntax_error(EMSG_INV_FMT);
}
}
@@ -2507,6 +2551,7 @@ static char *awk_printf(node *n, size_t *len)
}
goto tail; /* append remaining string, exit loop */
}
+//TODO: GNU awk 5.3.0 also supports %ld, %lx, we don't (yet). It does not support %lld.
if (isalpha(c))
break;
starred |= (c == '*');
@@ -3307,8 +3352,7 @@ static var *evaluate(node *op, var *res)
for (;;) {
var *v = evaluate(nextarg(&op1), TMPVAR0);
if (v->type & VF_NUMBER) {
- fputs(fmt_num(getvar_s(intvar[OFMT]), getvar_i(v)),
- F);
+ fputs(fmt_num(getvar_s(intvar[OFMT]), getvar_i(v)), F);
} else {
fputs(getvar_s(v), F);
}
testsuite/awk.tests+26 -1
@@ -75,7 +75,7 @@ testing "awk %.3d 99999999999999000.1234" \
testing "awk %.*d 3 99999999999999000.1234" \
"awk 'BEGIN { printf \"%.*d\n\", 3, 99999999999999000.1234; }'" "99999999999999008\n" "" ""
-# fractioals are rounded towards 0
+# fractionals are rounded towards 0
testing "awk %d -1.9" \
"awk 'BEGIN { printf \"%d\n\", -1.9; }'" "-1\n" "" ""
testing "awk %u -1.9" \
@@ -85,6 +85,31 @@ testing "awk %x -1.9" \
testing "awk %o -1.9" \
"awk 'BEGIN { printf \"%o\n\", -1.9; }'" "1777777777777777777777\n" "" ""
+# OFMT can contain literals before and after %SPEC:
+testing "awk OFMT='R%%:%f!' -1.9" \
+"awk -v OFMT='R%%:%f!' 'BEGIN { print -1.9; }'" "R%:-1.900000!\n" "" ""
+
+# OFMT is ignored if the value has no fractionals (even if it does not fit into uint64)
+testing "awk OFMT='R%%:%f!' 2**128" \
+"awk -v OFMT='R%%:%f!' 'BEGIN { print 2**128; }'" "340282366920938463463374607431768211456\n" "" ""
+testing "awk OFMT='R%%:%f!' 2**128" \
+"awk -v OFMT='R%%:%f!' 'BEGIN { print -2**129; }'" "-680564733841876926926749214863536422912\n" "" ""
+
+# OFMT can contain no %SPEC (be a literal):
+testing "awk OFMT='No formats, %% allowed!'" \
+"awk -v OFMT='No formats, %% allowed!' 'BEGIN { print -1.9; }'" "No formats, % allowed!\n" "" ""
+
+# OFMT can't have more than one %SPEC:
+testing "awk OFMT='%f%s' is not allowed" \
+"awk -v OFMT='R%%:%f!%s!' 'BEGIN { print -1.9; }' 2>&1" \
+"awk: cmd. line:1: Invalid format specifier\n" "" ""
+
+# OFMT %s:
+testing "awk OFMT='%-22s' -1.9" \
+"awk -v OFMT='R:%-22s!' 'BEGIN { print -1.9; }'" \
+"R:-1.9 !\n" "" ""
+
+
# 4294967295 = 0xffffffff
testing "awk bitwise op" "awk '{ print or(4294967295,1) }'" "4294967295\n" "" "\n"