Skip to content

v7.1.00

Choose a tag to compare

@github-actions github-actions released this 20 Aug 13:27
· 25 commits to main since this release

Jawk 7.1 makes Jawk easy to install — curl -fsSL https://jawk.io/get | sh and you have a
jawk command — and takes a deep pass over the numeric core: printf/sprintf are now
implemented natively with POSIX AWK / gawk semantics, integral arithmetic is computed exactly in
64 bits, and getline follows gawk down to which variables each form sets. Along the way, scripts
run measurably faster.

📦 One-command installation

  • Install the jawk executable with one command — curl -fsSL https://jawk.io/get | sh on
    Linux/macOS, irm https://jawk.io/get.ps1 | iex on Windows. No sudo: the launcher goes into
    ~/.local/bin (or %LOCALAPPDATA%\Jawk), and it locates a Java runtime (Java 8 or later) at
    run time, so upgrading Java never requires a reinstall. JAWK_VERSION pins a release,
    JAWK_INSTALL_DIR changes where the launcher lands. Every release now also publishes a
    version-less jawk-standalone.jar alias with .sha256 checksums, so
    releases/latest/download/jawk-standalone.jar is a stable download URL
    by @bertysentry in #584
  • JAWK_CLASSPATH in the launchers — set it and the launcher runs the CLI with the standalone
    jar plus your entries on the JVM class path, so custom
    extension jars load with -l <fully.qualified.ClassName>.
    Previously the launcher always used java -jar, which ignores every class path setting
    by @bertysentry in #588
  • CI smoke tests for both installers — the real scripts are exercised end to end on every
    change, so a broken jawk.io/get cannot reach the site
    by @bertysentry in #587

🖨️ Native printf with AWK semantics

printf/sprintf no longer delegate to the Printf4J library, which emulated glibc's printf();
they are implemented in the new io.jawk.jrt.AwkPrintf with the semantics POSIX specifies and gawk
implements, verified against gawk 5.0/5.1
by @bertysentry in #543

  • %s uses AWK's number-to-string rules — integral values print without a fractional part
    (printf "%s", i after i++ prints 1, not 1.0), non-integral values honor the script's
    current CONVFMT.
  • %c prints the character for a numeric code point — numbers, numeric strings, and fields
    alike — or the first character of a string value.
  • Dynamic precision %.*f joins dynamic width %*d, negative values included; gawk
    positional specifiers
    (%2$s) and the ' grouping flag (%'d) are supported.
  • Out-of-range integers follow gawk — negative values wrap to unsigned 64-bit for
    %u/%o/%x/%X, and values beyond 64 bits print their full decimal expansion for %d/%i.
  • NaN and infinities print as nan, inf, -inf instead of Java's NaN/Infinity — in
    print, printf, and number-to-string conversions.
  • Correct rounding — %e/%f/%g round the exact binary value of the double, halfway cases
    to even, like the C library gawk uses (printf "%.0f", 2.5 prints 2), and the same rounding
    applies through CONVFMT and OFMT (#548).
  • Too few arguments is a fatal error, as in gawk; unknown conversions — including %n, which
    Printf4J turned into a newline — print verbatim without consuming an argument.
  • New JavaStringFormatAwkSink for Java embedders: a sink whose printf/sprintf use
    String.format(...), giving scripts Java-only conversions such as %,d and %tY.

🔢 Exact numbers

  • Integral arithmetic is computed exactly in 64 bits — +, -, *, an evenly-dividing /,
    %, unary ±, ++/-- and the compound assignments, on variables, array elements, and fields
    alike, keep all their digits beyond 2^53: print 9007199254740992 + 1 prints 9007199254740993,
    where gawk computes in doubles and prints 9007199254740992 unless run with -M. Overflow falls
    back to floating point, and comparisons between two exact integers are exact as well. Values
    beyond the 64-bit range are no longer saturated either: print 2^100 prints the full
    1267650600228229401496703205376
    by @bertysentry in #552
  • Fields keep their numeric value through += and ++, like = always did, and rebuilding
    $0 converts numeric fields with CONVFMT, as POSIX requires and gawk does:
    { $1 = 0.1 + 0.2; print } prints 0.3 ... instead of the raw 0.30000000000000004 ...
    by @bertysentry in #552
  • Exponent literals are lexed as one number — print 2e3 prints 2000; previously the lexer
    stopped before the exponent, so 2e3 parsed as 2 concatenated with an uninitialized variable
    e3. Long numeric strings now keep all their digits instead of stopping after 26 characters, an
    integer literal too large for 64 bits is a floating-point constant instead of a
    NumberFormatException, and substr()'s length argument accepts every AWK number form
    by @bertysentry in #544
  • Array subscripts convert with the CONVFMT in effect when the subscript is used, as POSIX
    requires and gawk does, so a later CONVFMT change no longer retroactively rewrites existing
    keys — and integral subscripts beyond the 64-bit range key as their exact digits
    by @bertysentry in #551
  • Blank-padded numeric strings are recognized as strnums — the record " 12 " compares
    numerically ($0 == 12 is true), as in gawk; string constants such as x = " 12 " still compare
    as strings (#550)
    by @bertysentry in #573

📥 getline, redirections, and special filenames

  • Redirected getline sets exactly the variables gawk documents — getline < file and
    cmd | getline set $0 and NF only; getline var < file and cmd | getline var set var
    only. Previously every redirected form also advanced NR, so a side read shifted the record
    numbers of the whole main input, and took over FILENAME
    by @bertysentry in #572
  • A getline that cannot open its file returns -1 with ERRNO carrying the gawk-style
    description, so the idiomatic while ((getline line < f) > 0) guard and probing for optional
    files work; previously the underlying Java IOException aborted the whole script
    by @bertysentry in #572
  • A getline that reads nothing leaves its target untouched, as POSIX requires — no more
    clearing $0/NF or assigning the empty string at end of input or on error
    by @bertysentry in #572
  • getline into a field — getline $n < file, cmd | getline $n, and plain getline $n —
    stores the record into that field and rebuilds $0; previously any of these forms corrupted the
    interpreter's operand stack and crashed the script on the first record
    by @bertysentry in #572
  • The gawk special filenames /dev/stdout, /dev/stderr, /dev/stdin and their /dev/fd/N
    spellings route to the streams the process already holds open, instead of being opened — and
    truncated — as regular files. close() flushes the redirection and reports success without ever
    closing the underlying stream
    by @bertysentry in #564
  • /dev/null designates the null device on Windows (NUL) in redirections, in getline, and
    as an input operand, as gawk's Windows port does. Previously it was opened as a relative path,
    creating and truncating dev\null — and keeping the output that was meant to be discarded
    by @bertysentry in #568

🖥️ CLI and process integration

  • POSIX attached option-arguments — -fprog.awk, -vx=1, -F: and the Jawk-specific -L,
    -K, -l now work like their spaced forms, as in gawk, mawk, BWK awk, and goawk; previously the
    glued form was rejected with Unknown parameter
    by @bertysentry in #576
  • -V / --version prints the Jawk version and the Java runtime that executes it, and the
    usage printed by -h now names the command you actually typed — the installed launchers pass it
    through the new JAWK_PROGRAM_NAME variable — instead of always describing a java -jar
    invocation the user never made
    by @bertysentry in #593
  • system() and command-pipe children inherit the standard input, as POSIX requires and gawk,
    mawk, and BWK awk do: echo hi | jawk 'BEGIN { "sort" | getline l; print l }' prints hi, and
    terminal-aware commands such as "stty size" | getline can reach the controlling terminal.
    Previously the child's standard input was always closed, so stdin filters read nothing
    by @bertysentry in #577

🧩 Language and correctness

  • next is accepted inside user-defined functions, as in gawk, mawk, and BWK awk: called from
    an input rule it abandons the current record and unwinds the active calls; called from BEGIN,
    END, BEGINFILE, or ENDFILE it is a runtime fatal error, matching gawk. Previously any next
    inside a function was rejected at compile time
    by @bertysentry in #582
  • Conditional expressions feeding a further operation now optimize correctly — shapes like
    (v ? v : 24) * 2, -(v ? 5 : 24), or $(v ? 1 : 2). The peephole literal fold used to merge
    the false branch's literal with the operation at the branches' join point, so (60 ? 60 : 24) * 2
    yielded 48 and leaked a value onto the operand stack that displaced later print operands
    by @bertysentry in #579
  • --profile no longer leaks function timings on nextfile — a call abandoned by nextfile is
    recorded as exited with its actual duration, instead of leaking a stack entry (unbounded memory
    growth on long profiled runs) and being reported with wildly wrong durations at program exit
    by @bertysentry in #581

⚡ Performance

  • Flat Object[] operand stack replaces the boxed ArrayDeque in the AVM: push/pop are
    single array accesses the JIT can fully inline, and argument slices are bulk-copied. −13.5% on a
    tight arithmetic loop, −5% to −14% across the expression benchmarks
    by @bertysentry in #555
  • AwkPrintf hot paths — per-locale symbol cache, parsed-format cache, and a guarded rounding
    fast path: 1.3×–9.8× faster formatting (%f of 0.1 3.0×, CONVFMT of 3.14159 3.2×), with
    output verified bit-identical — 19.6M rounding comparisons against the exact-expansion reference
    and 600k differential-fuzz cases, zero mismatches
    by @bertysentry in #560
  • Non-throwing associative-array key normalization — every non-integer key (all SUBSEP-joined
    arr[x,y] keys, all string keys) used to allocate a NumberFormatException and fill in its stack
    trace, ~600 ns on every get, put, and in test; it is now a hand-rolled scan with parity to
    Long.parseLong locked in by tests
    by @bertysentry in #561
  • AVM.executeTuples kept under HotSpot's huge-method limit — the dispatch loop sat at 7994
    bytecodes (8013 under ECJ), right at the 8000-bytecode cliff past which HotSpot never JIT-compiles
    a method at all: crossing it made every AWK script ~4× slower, silently. It is now 6728 bytecodes,
    with a test that fails the build above 7500
    by @bertysentry in #563

⚠️ Breaking changes

  • AwkSink.printf(...) and AwkSink.sprintf(...) now take CONVFMT — printf receives it
    between ofmt and format, and sprintf(convfmt, format, values...) is the single sprintf
    method and the customization point. Custom sinks must be updated. The org.metricshub:printf4j
    dependency is gone (#543).
  • Programs precompiled with an earlier version are rejected and must be recompiled: they may
    carry constants folded in floating point (#552).
  • The broken stdin extension is removed, along with the BlockObject/BlockManager API whose
    only user it was. Its StdinHasInput() and StdinBlock() never reported a pending end of input,
    hanging every read loop that guarded its StdinGetline() calls, and no other AWK implementation
    provides these functions. -l stdin is now rejected with Unknown extension 'stdin'; read
    standard input through the regular input loop, getline, or /dev/stdin
    by @bertysentry in #594

Every user-visible change above is described with its before/after on the
behavior changes page.

📚 Documentation

  • Pre-release documentation audit — the site and README now lead with the jawk command, the
    installation guide is simplified, and over-broad statements were tightened
    by @bertysentry in #589
  • Release plumbing for v7.1.00-SNAPSHOT by @github-actions[bot] in #542

Full Changelog: v7.0.01...v7.1.00

Dependabot

  • build(deps): bump org.apache.maven:apache-maven from 3.8.6 to 3.9.16 by @dependabot[bot] in #554