awk Part IV · Module 14 · AWK Debugging, Review, and Engineering Mastery
Interview Problems: Beginner
These are the ten awk questions that get asked first, and the one-liner each of them wants is three seconds of typing. What the interview is actually measuring is the follow-up — the second question that asks what happens when the field is missing, the file is empty, the threshold is quoted, or the array is only tested. Every problem here is paired with that follow-up and with its measured answer, because in most of them the follow-up result is not what the one-liner leads you to expect.
Foundation20 min readawkPart IVInterviewFundamentals
Part IV · Module 14 · Chapter 14.7 · Interview Problems: Beginner
1. How to Read This Chapter
Each problem below has three parts: the question as asked, the answer expected, and the follow-up. The follow-up is the part that matters. A candidate who can type awk '{ print $2 }' has shown nothing; a candidate who knows what $2 is on a one-field record has shown they have used awk on real data.
Every follow-up result in this chapter was run before it was written, and most of them are not what the one-liner suggests.
cat > cells.rpt <<'EOF'
u_alu AND2_X1 1024.500 3094
u_ctrl DFF_X2 512.250 1180
u_io INV_X1 256.125 402
u_alu DFF_X2 880.000 2100
u_mem AND2_X1 64.000 120
EOF
printf ' fixture: %s records, %s fields each\n' \
"$(awk 'END { print NR }' cells.rpt)" "$(awk 'NR == 1 { print NF }' cells.rpt)" fixture: 5 records, 4 fields eachFive records: module name, cell type, area, instance count.
2. Problem 1 — Print One Field
"Print the cell type of every row."
awk '{ print $2 }' cells.rpt | sed 's/^/ /' AND2_X1
DFF_X2
INV_X1
DFF_X2
AND2_X1Follow-up: what does $2 give you on a record that has only one field?
printf 'u_alu\n' > short.rpt
awk '{ printf " [%s] NF=%d\n", $2, NF }' short.rpt [] NF=1The empty string, not an error. awk has no concept of an out-of-range field reference on read — $2, $9 and $400 on a one-field record all give "".
That is the answer the follow-up wants, and the reason it is asked is what happens when you then compare it. Exercise 1 measures the full table; the short version is that a missing field is not equal to 0 — $2 == 0 is false for it, because the comparison is "" against "0" as strings — while $2 + 0 == 0 is true for it, and also true for a real zero. So one of the two obvious tests distinguishes absent from zero and the other conflates them, and NF settles it without either.
3. Problem 2 — Sum a Column
"Total the area column."
awk '{ s += $3 } END { printf " total area: %.3f\n", s }' cells.rpt total area: 2736.875Follow-up: what does it print on an empty file?
: > empty.rpt
awk '{ s += $3 } END { printf " s=[%s] and s+0=[%s]\n", s, s+0 }' empty.rpt
awk 'END { printf " END still ran, with NR=%d\n", NR }' empty.rpt s=[] and s+0=[0]
END still ran, with NR=0Two facts, and both get asked about.
END runs even when no records were read, with NR at 0 — so an END block cannot assume it has data.
An unset variable prints as the empty string and converts to 0. s was never assigned, so %s gives nothing at all and %d or + 0 gives zero. A report line reading total area: with nothing after it is the signature, and Chapter 10.5 measured the version of this that is actually dangerous: an unseeded running minimum reports 0.000, which in a timing report means "exactly met" rather than "no data".
The distinction the follow-up is probing is that a sum over no records is not zero — it is undefined, and printing 0 for it is a choice the program should make explicitly.
4. Problem 3 — Count the Records
"How many lines are in the file?"
printf " awk END NR : %s\n" "$(awk 'END { print NR }' cells.rpt)"
printf " wc -l : %s\n" "$(wc -l < cells.rpt | tr -d ' ')" awk END NR : 5
wc -l : 5Follow-up: can those two ever disagree?
printf 'a\nb\nc' > nonl.rpt
printf " awk END NR : %s wc -l : %s\n" \
"$(awk 'END { print NR }' nonl.rpt)" "$(wc -l < nonl.rpt | tr -d ' ')" awk END NR : 3 wc -l : 2Yes — on a file with no final newline. wc -l counts newline characters and there are two; awk counts records and end-of-file terminates the third one.
Chapter 14.4 section 5 uses this disagreement as a reason to refuse to parse, and measures why it is not more than that: it decides only whether the last byte is a newline, so a complete file written without one reads as truncated, and a truncation that landed on a newline reads as complete. The question is worth asking because a candidate who says "they are the same" has not met a file written by a process that was killed — and the better answer names what the difference actually proves.
5. Problem 4 — The Last Field
"Print the instance count, which is the last column."
awk '{ print $NF }' cells.rpt | sed 's/^/ /' 3094
1180
402
2100
120Follow-up: what is the difference between NF and $NF?
awk 'NR == 1 { printf " NF=%s $NF=%s $(NF-1)=%s\n", NF, $NF, $(NF-1) }' cells.rpt NF=4 $NF=3094 $(NF-1)=1024.500NF is a number — the field count. $NF is the field at that position. $(NF-1) is the second-to-last, and the parentheses are required because $NF-1 would be $NF minus one, giving 3093.
This is the most mechanical follow-up in the chapter and the most frequently missed, because $NF is memorised as an idiom rather than read as an expression. $ takes an expression: Chapter 14.5 section 8 used $CLOCK with CLOCK = 4, which works for the same reason.
6. Problem 5 — Unique Values
"List each cell type once."
awk '!seen[$2]++ { print " " $2 }' cells.rpt AND2_X1
DFF_X2
INV_X1The idiom is worth decomposing: seen[$2]++ returns the value before incrementing, so the first time a key appears it returns 0 (false), !0 is true, and the record prints. Every later time it returns a non-zero count and the record is skipped. Output is in first-seen order, not sorted.
Follow-up: if I only test the array without assigning, does the array still grow?
awk '{ if (t[$2] != "") x++ } END { n = 0; for (k in t) n++; printf " tested with != \"\" -> %d key(s) in the array\n", n }' cells.rpt
awk '{ if ($2 in t) x++ } END { n = 0; for (k in t) n++; printf " tested with in -> %d key(s) in the array\n", n }' cells.rpt tested with != "" -> 3 key(s) in the array
tested with in -> 0 key(s) in the array7. Problem 6 — A Threshold
"Print the modules with area above 500."
awk '$3 > 500 { printf " %s %s\n", $1, $3 }' cells.rpt u_alu 1024.500
u_ctrl 512.250
u_alu 880.000Follow-up: what if I write the threshold in quotes?
awk '$3 > "500" { n++ } END { printf " $3 > \"500\" : %d record(s)\n", n+0 }' cells.rpt
awk '$3 > 500 { n++ } END { printf " $3 > 500 : %d record(s)\n", n+0 }' cells.rpt
awk '$3 > "500" { s = s sep $3; sep = " " } END { printf " quoted picks : [%s]\n", s }' cells.rpt
awk '$3 > 500 { s = s sep $3; sep = " " } END { printf " numeric picks: [%s]\n", s }' cells.rpt $3 > "500" : 3 record(s)
$3 > 500 : 3 record(s)
quoted picks : [512.250 880.000 64.000]
numeric picks: [1024.500 512.250 880.000]8. Problem 7 — Two Files
"Process two files and tell me which file each record came from."
head -2 cells.rpt > a.rpt; tail -3 cells.rpt > b.rpt
awk '{ printf " NR=%d FNR=%d %s %s\n", NR, FNR, FILENAME, $1 }' a.rpt b.rpt NR=1 FNR=1 a.rpt u_alu
NR=2 FNR=2 a.rpt u_ctrl
NR=3 FNR=1 b.rpt u_io
NR=4 FNR=2 b.rpt u_alu
NR=5 FNR=3 b.rpt u_memNR is cumulative across all input; FNR restarts at each file. The third row is the one to point at: NR=3 FNR=1.
Follow-up: what is NR == FNR used for?
It is the standard idiom for "I am still reading the first file", which is how a lookup table gets loaded before the data is processed. Chapter 7.5 measured its failure mode, which is the follow-up behind the follow-up: if the first file is empty, NR == FNR is never true for it and the condition then matches the first record of the second file instead, silently loading data as keys. A record count on the key file is not enough to catch it — only if (!loaded) is.
9. Problem 8 — Field Separators
"The file is space-aligned. How does awk split it?"
printf 'a b\t\tc \n' > ws.rpt
awk '{ printf " default FS : NF=%d [%s][%s][%s]\n", NF, $1, $2, $3 }' ws.rpt
awk -F' ' '{ printf " -F space : NF=%d [%s][%s][%s]\n", NF, $1, $2, $3 }' ws.rpt
awk -F'\t' '{ printf " -F tab : NF=%d [%s][%s][%s]\n", NF, $1, $2, $3 }' ws.rpt default FS : NF=3 [a][b][c]
-F space : NF=3 [a][b][c]
-F tab : NF=3 [a b][][c ]The default separator is "any run of blanks", which covers spaces and tabs together, and it also strips leading and trailing whitespace from the record before splitting — which is why trailing space after c does not produce a fourth empty field.
-F' ' behaves identically here, because a single space given as FS is special-cased to mean the default rule rather than a literal space.
Follow-up: so when is -F the wrong choice?
The third row answers it. -F'\t' gives NF=3 as well — the same count — with completely different fields: [a b], an empty $2 from the doubled tab, and [c ] retaining its trailing space. Chapter 14.4 section 8 measured the practical version on a report with mixed tab and space rows, where -F'\t' broke the row the default handled.
Name the separator only when it is significant — a delimited interface where an empty field must survive. On human-formatted output, the default is the robust choice.
10. Problem 9 — print and printf
"What is the difference between
printf?"
awk 'NR == 1 { print $1, $3 }' cells.rpt | sed 's/^/ print : /'
awk 'NR == 1 { printf "%s %s\n", $1, $3 }' cells.rpt | sed 's/^/ printf : /' print : u_alu 1024.500
printf : u_alu 1024.500print joins its arguments with OFS and appends ORS. printf appends nothing — the \n is yours to supply, and forgetting it is the most common first-day mistake.
Follow-up: what happens if the format string and the argument list disagree?
awk 'NR == 1 { printf "[%s][%s]\n", $1 }' cells.rpt > o.txt 2> e.txt
printf " too few args : rc=%s stdout=[%s]\n" "$?" "$(tr -d '\n' < o.txt)"
printf " stderr begins: %s\n" "$(head -1 e.txt)"
awk 'NR == 1 { printf "[%s]\n", $1, $3 }' cells.rpt > o.txt 2> e.txt
printf " too many args: rc=%s stdout=[%s] stderr=[%s]\n" "$?" "$(tr -d '\n' < o.txt)" "$(tr -d '\n' < e.txt)" too few args : rc=2 stdout=[]
stderr begins: awk: not enough args in printf([%s][%s]
too many args: rc=0 stdout=[[u_alu]] stderr=[]Too few is fatal; too many is silent. Not enough arguments aborts the program with rc=2 and no output at all; a surplus argument is discarded with no diagnostic and rc=0.
That asymmetry is worth knowing because it is the inverse of how awk treats user-defined functions, where a call with too few arguments is fine — the missing parameters become uninitialised locals — and too many is a syntax error. Chapter 7.1 measured both directions.
The practical consequence: a printf whose format gained a column while the argument list did not will kill the program loudly, which is the good case. One whose argument list gained a column silently drops it, and the report looks right.
11. Problem 10 — BEGIN and END
"When do
BEGINandENDrun?"
awk 'BEGIN { print " BEGIN ran" } END { printf " END ran, NR=%d\n", NR }' cells.rpt BEGIN ran
END ran, NR=5BEGIN before any input is read, END after all of it. Both are optional and either can be the whole program.
Follow-up: if the program is only a BEGIN block, does awk read the input at all?
awk 'BEGIN { print "BEGIN ran" }' nosuchfile.rpt > o.txt 2> e.txt
printf " BEGIN only : rc=%s stdout=[%s] stderr=[%s]\n" "$?" "$(tr -d '\n' < o.txt)" "$(tr -d '\n' < e.txt)"
awk 'BEGIN { print "BEGIN ran" } END { print "END ran" }' nosuchfile.rpt > o.txt 2> e.txt
printf " BEGIN + END : rc=%s stdout=[%s] stderr=[%s]\n" "$?" "$(tr -d '\n' < o.txt)" "$(tr -d '\n' < e.txt)"
awk 'BEGIN { print "BEGIN ran" } { n++ }' nosuchfile.rpt > o.txt 2> e.txt
printf " BEGIN + main rule: rc=%s stdout=[%s] stderr=[%s]\n" "$?" "$(tr -d '\n' < o.txt)" "$(tr -d '\n' < e.txt)" BEGIN only : rc=0 stdout=[BEGIN ran] stderr=[]
BEGIN + END : rc=2 stdout=[BEGIN ran] stderr=[awk: can't open file nosuchfile.rpt source line number 1]
BEGIN + main rule: rc=2 stdout=[BEGIN ran] stderr=[awk: can't open file nosuchfile.rpt source line number 1]12. Common Mistakes
Treating a missing field as an error. It is the empty string; $2 == 0 is true for it and for a real zero.
Assuming END has data. It runs with NR=0 on an empty file.
Printing an unset accumulator with %s. It renders as nothing; + 0 is what makes it zero, and zero may be the wrong answer.
Believing wc -l and NR always agree. 2 against 3 on a file with no final newline.
Reading $NF as an idiom rather than an expression. $NF-1 is the last field minus one; $(NF-1) is the previous field.
Testing an array with a[k] != "". It creates the element — three keys against zero.
Quoting a numeric threshold. $3 > "500" selected three records and two of them were different ones.
Checking a count instead of the records. Both threshold forms returned 3.
Adding -F'\t' to be explicit. The default separator handles spaces and tabs together; naming the tab broke the row that was fine.
Assuming a printf mismatch is always caught. Too few is fatal, too many is silent — and the silent direction drops a column.
Using a BEGIN-only program to check a file. It never opens the file and exits 0.
13. Exercises
Exercise 1 — Absent, zero, and unset are three different things
printf 'u_alu 1024.500\nu_ctrl\nu_io 0\nu_x 0.000\n' > m.rpt
awk '{ printf " NR=%d NF=%d [%s] $2==0:%-5s $2==\"\":%-5s $2+0==0:%s\n", NR, NF, $2,
($2 == 0 ? "T" : "F"), ($2 == "" ? "T" : "F"), ($2 + 0 == 0 ? "T" : "F") }' m.rpt
awk 'BEGIN { printf " unset variable v==0:%s v==\"\":%s v+0==0:%s\n",
(v == 0 ? "T" : "F"), (v == "" ? "T" : "F"), (v + 0 == 0 ? "T" : "F") }' NR=1 NF=2 [1024.500] $2==0:F $2=="":F $2+0==0:F
NR=2 NF=1 [] $2==0:F $2=="":T $2+0==0:T
NR=3 NF=2 [0] $2==0:T $2=="":F $2+0==0:T
NR=4 NF=2 [0.000] $2==0:T $2=="":F $2+0==0:T
unset variable v==0:T v=="":T v+0==0:TRead the $2==0 column against $2+0==0:
$2 == 0is FALSE for the absent field (record 2) and TRUE for both real zeros (records 3 and 4). It distinguishes them correctly, because the absent field is the empty string and"" == "0"is a string comparison that fails.$2 + 0 == 0is TRUE for all three. Forcing the conversion is exactly what destroys the distinction.- An unset variable is equal to
0and to""simultaneously — it is neither a string nor a number until something assigns to it. A missing field is not the same object: it behaves as the empty string.
So the common advice to "add + 0 to be safe" is wrong here in a specific way: it makes a missing field indistinguishable from a measured zero, which Chapter 11.4 showed matters because n/a and 0.00% mean different things in a coverage report. NF is the test that needs no reasoning at all.
Exercise 2 — The array that grew
awk '{ if (t[$2] != "") x++ }
END { n = 0; for (k in t) n++; printf " != \"\" : array=%d answer-correct=%s\n", n, (x == 0 ? "yes" : "no") }' cells.rpt
awk '{ if ($2 in t) x++ }
END { n = 0; for (k in t) n++; printf " in : array=%d answer-correct=%s\n", n, (x == 0 ? "yes" : "no") }' cells.rpt != "" : array=3 answer-correct=yes
in : array=0 answer-correct=yesBoth give the correct answer and the arrays differ by three elements. That is what makes the defect survive review: nothing in the result is wrong.
Exercise 3 — See both verdicts on every record
awk '$3 > "500" { printf " quoted flags %-8s %s\n", $1, $3 }' cells.rpt
awk '$3 > 500 { printf " numeric flags %-8s %s\n", $1, $3 }' cells.rpt quoted flags u_ctrl 512.250
quoted flags u_alu 880.000
quoted flags u_mem 64.000
numeric flags u_alu 1024.500
numeric flags u_ctrl 512.250
numeric flags u_alu 880.000Three each. u_ctrl and the second u_alu appear in both lists; u_mem appears only in the quoted one and the first u_alu only in the numeric one.
That is readable because the lists are short. On a report with four hundred paths it is not, so the useful form puts both verdicts on one line per record:
awk '{ v = $3 + 0; printf " %-8s raw=[%-9s] v=%9.3f numeric>500=%-3s text>\"500\"=%s\n", $1, $3, v,
(v > 500 ? "YES" : "no"), ($3 > "500" ? "YES" : "no") }' cells.rpt u_alu raw=[1024.500 ] v= 1024.500 numeric>500=YES text>"500"=no
u_ctrl raw=[512.250 ] v= 512.250 numeric>500=YES text>"500"=YES
u_io raw=[256.125 ] v= 256.125 numeric>500=no text>"500"=no
u_alu raw=[880.000 ] v= 880.000 numeric>500=YES text>"500"=YES
u_mem raw=[64.000 ] v= 64.000 numeric>500=no text>"500"=YESFive records, both verdicts, and the converted value printed between them so there is nothing to infer. The two columns disagree on exactly two rows:
1024.500— numerically over the threshold, textually under it, because1sorts before5.64.000— numerically well under, textually over, because6sorts after5.
And they agree on the other three, which is why the counts match at 3 and 3. Printing the converted value in the same row as the verdicts is what makes the comparison decidable rather than a matter of trusting one of two commands.
Exercise 4 — The rule stated as a test
awk 'BEGIN {
printf " \"-0.018\" < \"-0.050\" : %s (two string constants)\n", ("-0.018" < "-0.050" ? "TRUE" : "false")
printf " -0.018 < -0.050 : %s (two numbers)\n", (-0.018 < -0.050 ? "TRUE" : "false")
}'
awk 'NR == 1 { printf " $3 < $4 : %s (two fields -- numeric)\n", ($3 < $4 ? "TRUE" : "false") }' cells.rpt
awk 'NR == 1 { printf " $3 < \"2000\" : %s (field vs string constant)\n", ($3 < "2000" ? "TRUE" : "false") }' cells.rpt
awk 'NR == 1 { printf " $3 < 2000 : %s (field vs number)\n", ($3 < 2000 ? "TRUE" : "false") }' cells.rpt "-0.018" < "-0.050" : TRUE (two string constants)
-0.018 < -0.050 : false (two numbers)
$3 < $4 : TRUE (two fields -- numeric)
$3 < "2000" : TRUE (field vs string constant)
$3 < 2000 : TRUE (field vs number)Five comparisons. The first two are the trap in its purest form: the same two values, opposite answers, decided entirely by the quotes.
Row three is the reassuring one — two fields compare numerically, so a running minimum written as if ($3 < w) w = $3 is safe. Chapter 10.3 measured this and it is worth knowing precisely, because the natural assumption after seeing rows one and two is that every comparison involving a field is suspect.
Rows four and five both read TRUE and prove nothing on their own: 1024.500 < 2000 is true numerically, and "1024.500" < "2000" is also true lexically because 1 precedes 2. A test that passes under both interpretations cannot tell you which one ran — which is exactly how the quoted-threshold defect survives casual checking, and why section 7's fixture needed a value like 64.000 to separate them.
Exercise 5 — Engineering application
#!/bin/sh
# firstlook.sh — answer the ten beginner questions about an unfamiliar report
# in one pass, before writing any parser for it.
#
# Usage: sh firstlook.sh <report>
#
# Exit: 0 the file looks parseable, 2 it cannot be read or is truncated.
#
# Every check here is one of this chapter's follow-ups, because those are the
# facts that decide how the parser must be written. The file-level checks run
# first and exit 2, because a truncated file invalidates every field-level
# answer below it.
f=$1
[ -n "$f" ] || { echo "usage: firstlook.sh <report>" >&2; exit 2; }
[ -r "$f" ] || { echo "UNREADABLE: $f" >&2; exit 2; }
[ -s "$f" ] || { echo "EMPTY: $f" >&2; exit 2; }
nl=$(wc -l < "$f" | tr -d ' ')
nr=$(awk 'END { print NR }' "$f")
printf "records: awk=%s newlines=%s %s\n" "$nr" "$nl" \
"$([ "$nl" = "$nr" ] && echo complete || echo "TRUNCATED -- no final newline")"
[ "$nl" = "$nr" ] || exit 2
awk '
{ if ($0 ~ /\r/) cr++ }
NF == 0 { blank++; next }
{ w[NF]++
for (i = 1; i <= NF; i++)
if ($i ~ /[0-9]/ && $i + 0 != $i) nonnum[i]++ }
END {
printf "field widths:"
for (k = 1; k <= 40; k++) if (k in w) printf " %d fields x%d", k, w[k]
printf "\n"
printf "blank records: %d\n", blank + 0
printf "crlf records : %d\n", cr + 0
n = 0
for (i = 1; i <= 40; i++) if (i in nonnum) { n++
printf "field %d looks numeric and is NOT (x%d)\n", i, nonnum[i] }
if (n == 0) printf "no field looks numeric while failing conversion\n"
}
' "$f"printf 'u_alu 75.00%% 1\nu_ctrl 40.00%% 2\n' > pct.rpt
for f in cells.rpt pct.rpt nonl.rpt; do
printf "== %s\n" "$f"
sh firstlook.sh "$f" > o.txt 2>&1; printf " rc=%s\n" "$?"; sed 's/^/ /' o.txt
done== cells.rpt
rc=0
records: awk=5 newlines=5 complete
field widths: 4 fields x5
blank records: 0
crlf records : 0
field 2 looks numeric and is NOT (x5)
== pct.rpt
rc=0
records: awk=2 newlines=2 complete
field widths: 3 fields x2
blank records: 0
crlf records : 0
field 2 looks numeric and is NOT (x2)
== nonl.rpt
rc=2
records: awk=3 newlines=2 TRUNCATED -- no final newlineThree files. nonl.rpt is refused with rc=2 before any field is examined, which is the correct precedence.
The other two produce the identical finding — field 2 looks numeric and is NOT — and Exercise 6 is about why that is a problem.
Exercise 6 — Challenge: measure the heuristic's precision
The same finding fired on both readable files. Determine whether both are real.
awk 'NR <= 2 { printf " cells.rpt $2=[%-9s] has-digit=%s converts=%s\n", $2, ($2 ~ /[0-9]/ ? "y" : "n"), ($2 + 0 == $2 ? "y" : "n") }' cells.rpt
awk 'NR <= 2 { printf " pct.rpt $2=[%-9s] has-digit=%s converts=%s\n", $2, ($2 ~ /[0-9]/ ? "y" : "n"), ($2 + 0 == $2 ? "y" : "n") }' pct.rpt
awk 'NR == 1 { printf " cells.rpt $2+0=%s pct.rpt-style compare $2<90: n/a\n", $2 + 0 }' cells.rpt
awk 'NR == 1 { printf " pct.rpt $2+0=%s and $2 < 90 is %s\n", $2 + 0, ($2 < 90 ? "BELOW -- lexical" : "ok") }' pct.rpt cells.rpt $2=[AND2_X1 ] has-digit=y converts=n
cells.rpt $2=[DFF_X2 ] has-digit=y converts=n
pct.rpt $2=[75.00% ] has-digit=y converts=n
pct.rpt $2=[40.00% ] has-digit=y converts=n
cells.rpt $2+0=0 pct.rpt-style compare $2<90: n/a
pct.rpt $2+0=75 and $2 < 90 is BELOW -- lexicalFour of the six lines read has-digit=y converts=n — the heuristic's condition — and they come from two files where it means opposite things:
AND2_X1is a cell-type name. It was never meant to be a number, it will never be compared against one, and the finding is noise.75.00%is a percentage. It will be compared against a gate, and$2 < 90on it isBELOWby a lexical comparison — Chapter 14.6's second specimen, where a block at100.00%failed a 90% gate. The finding is real.
The heuristic's precision on these two files is one in two, and nothing in its output distinguishes them. has-digit=y converts=n is true of both. What separates them is knowing what the column is for, which no inspection of the file can supply.
That is the honest limit of a first-look tool, and it is the same boundary Chapter 14.6 measured for review.sh: the mechanisable checks report properties of the text, and deciding whether a property matters requires the intent. A finding from this script is a question to answer before writing the parser, not a defect — and answering it for cells.rpt takes two seconds and prevents nothing, which is exactly the right outcome for a check that costs one pass.
14. Summary
- A missing field is the empty string, and
$2 == 0is FALSE for it while true for a real zero — the string comparison"" == "0"fails.$2 + 0 == 0is true for both, so adding+ 0destroys the distinction.NFneeds no reasoning. - An unset variable equals
0and""at the same time. A missing field does not; the two are different objects. ENDruns withNR=0on empty input, and an unset accumulator prints as nothing while+ 0makes it zero — which may be the wrong answer.wc -landNRdisagree by one on a file with no final newline — a reason to refuse to parse, not a proof of truncation (Chapter 14.4 section 5 measures both failure directions).NFis a number and$NFis a field.$(NF-1)needs the parentheses;$takes any expression.!seen[$2]++prints in first-seen order, not sorted.- Reading an array element creates it.
t[$2] != ""left three keys where$2 in tleft zero — and both gave the right answer. - A quoted threshold is a lexical comparison.
$3 > "500"and$3 > 500both selected three records and differed on two of them:64.000admitted,1024.500excluded. - Two numeric-looking fields compared to each other are numeric. The trap needs a quoted constant.
NRis cumulative,FNRrestarts, andNR == FNRfails open when the first file is empty.- The default
FSis any run of blanks, spaces and tabs together, with leading and trailing whitespace stripped.-F'\t'gave the same field count and different fields. printfwith too few arguments is fatal; too many is silent — the inverse of user-defined functions.- A
BEGIN-only program never opens its input:rc=0on a missing file, andrc=2as soon as anENDblock or a main rule exists. - Print the converted value beside both verdicts.
raw=[64.000] v=64.000 numeric>500=no text>"500"=YESleaves nothing to infer. - A test that passes under both interpretations proves nothing.
$3 < "2000"and$3 < 2000are both true on1024.500, which is how the defect survives casual checking. - A text-level finding is a question, not a defect.
has-digit=y converts=nfired on a percentage that will break a gate and on a cell-type name that never will.
Chapter 14.8 — Interview Problems: Intermediate moves to the problems with state: grouping, two-file joins, running extrema, and the ones where the expected answer is a design choice rather than a one-liner.
Where this fits
Part of the awk curriculum.
