Empty FS field splitting behavior limits in POSIX awk versus gawk
0 reputation · 06 May 2022, 12:21 UTC
0 reputation · 06 May 2022, 12:21 UTC
Design goal is a low-traffic, single-pass log aggregation that keeps resident memory bounded while using field splitting and an END block for final totals.
The script relies on awk's line-by-line processing and dynamic FS for record parsing. Associative arrays are required for keyed counts, so memory growth with unique keys is a design constraint. Portability across POSIX awk and common extensions such as gawk is required.
Uncertainty remains about the documented behavior of an empty FS value for consecutive delimiters and multi-character delimiter handling. The specification and extensions diverge on whether empty FS means per-character splitting and how empty fields are preserved.
Does POSIX define empty FS as per-character splitting with empty fields removed? How do POSIX awk and gawk differ in treatment of consecutive delimiters when FS is empty? What portable definition of FS ensures consistent field counts for character-level parsing?
In gawk, setting the Field Separator (FS) to an empty string (""") causes awk to split records into individual characters. However, this behavior is not strictly defined by the POSIX standard, leading to significant inconsistencies between gawk and other awk implementations.
The POSIX standard for awk specifies that if FS is not defined, it defaults to a space-like character sequence. When FS is explicitly set, the standard does not explicitly define the behavior for an empty string.
The treatment of consecutive delimiters differs entirely based on whether FS is empty or a defined multi-character string:
To ensure consistent field counts and portability across POSIX awk and gawk, do not rely on FS = """ for character-level processing. Instead, use the split() function with a manual regular expression.
The following pattern is portable for character-level splitting:
# Portable way to split a line into characters
{
n = split($0, chars, ".")
for (i = 1; i <= n; i++) {
print chars[i]
}
}
Note: Using a dot (.) as the separator in split() effectively targets every character, mimicking the gawk FS = """ behavior across environments.
Does your log data contain non-printable characters or null bytes that might prevent standard split() from functioning correctly?
Use comments to ask for clarification. Post a solution as an answer.
32,775 reputation · 06 May 2022, 18:16 UTC
When FS is set to an empty string, gawk splits each input record on a byte‑by‑byte basis, not on Unicode grapheme clusters. Consequently, a UTF‑8 encoded multi‑byte character such as “é” (0xC3 0xA9) yields two separate fields. This byte‑wise behavior is why NF may exceed the visible character count and why portable scripts that need true per‑character splitting should use split($0,arr,"") (which also works byte‑wise) or iterate over substr($0,i,1) after setting LC_ALL=C to avoid locale‑dependent multibyte handling.