Skip to content
VLSI Mentor

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  fixture: 5 records, 4 fields each

Five records: module name, cell type, area, instance count.

2. Problem 1 — Print One Field

"Print the cell type of every row."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ print $2 }' cells.rpt | sed 's/^/  /'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  AND2_X1
  DFF_X2
  INV_X1
  DFF_X2
  AND2_X1

Follow-up: what does $2 give you on a record that has only one field?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf 'u_alu\n' > short.rpt
awk '{ printf "  [%s] NF=%d\n", $2, NF }' short.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  [] NF=1

The 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."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ s += $3 } END { printf "  total area: %.3f\n", s }' cells.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  total area: 2736.875

Follow-up: what does it print on an empty file?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
: > 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  s=[] and s+0=[0]
  END still ran, with NR=0

Two 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?"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf "  awk END NR : %s\n" "$(awk 'END { print NR }' cells.rpt)"
printf "  wc -l      : %s\n" "$(wc -l < cells.rpt | tr -d ' ')"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  awk END NR : 5
  wc -l      : 5

Follow-up: can those two ever disagree?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 ' ')"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  awk END NR : 3   wc -l : 2

Yes — 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."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ print $NF }' cells.rpt | sed 's/^/  /'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  3094
  1180
  402
  2100
  120

Follow-up: what is the difference between NF and $NF?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR == 1 { printf "  NF=%s  $NF=%s  $(NF-1)=%s\n", NF, $NF, $(NF-1) }' cells.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  NF=4  $NF=3094  $(NF-1)=1024.500

NF 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."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '!seen[$2]++ { print "  " $2 }' cells.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  AND2_X1
  DFF_X2
  INV_X1

The 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?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  tested with != "" -> 3 key(s) in the array
  tested with in      -> 0 key(s) in the array

7. Problem 6 — A Threshold

"Print the modules with area above 500."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '$3 > 500 { printf "  %s %s\n", $1, $3 }' cells.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu 1024.500
  u_ctrl 512.250
  u_alu 880.000

Follow-up: what if I write the threshold in quotes?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  $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."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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_mem

NR 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?"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 print and printf?"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 : /'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  print  : u_alu 1024.500
  printf : u_alu 1024.500

print 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?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 BEGIN and END run?"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { print "  BEGIN ran" } END { printf "  END ran, NR=%d\n", NR }' cells.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  BEGIN ran
  END ran, NR=5

BEGIN 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?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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") }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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:T

Read the $2==0 column against $2+0==0:

  • $2 == 0 is 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 == 0 is TRUE for all three. Forcing the conversion is exactly what destroys the distinction.
  • An unset variable is equal to 0 and 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  != "" : array=3  answer-correct=yes
  in     : array=0  answer-correct=yes

Both 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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.000

Three 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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"=YES

Five 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, because 1 sorts before 5.
  • 64.000 — numerically well under, textually over, because 6 sorts after 5.

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  "-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

Azvya Education Pvt. Ltd.VLSI Mentor
firstlook.sh — synthetic teaching script.
#!/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"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
== 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 newline

Three 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 -- lexical

Four 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_X1 is 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 < 90 on it is BELOW by a lexical comparison — Chapter 14.6's second specimen, where a block at 100.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 == 0 is FALSE for it while true for a real zero — the string comparison "" == "0" fails. $2 + 0 == 0 is true for both, so adding + 0 destroys the distinction. NF needs no reasoning.
  • An unset variable equals 0 and "" at the same time. A missing field does not; the two are different objects.
  • END runs with NR=0 on empty input, and an unset accumulator prints as nothing while + 0 makes it zero — which may be the wrong answer.
  • wc -l and NR disagree 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).
  • NF is a number and $NF is 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 t left zero — and both gave the right answer.
  • A quoted threshold is a lexical comparison. $3 > "500" and $3 > 500 both selected three records and differed on two of them: 64.000 admitted, 1024.500 excluded.
  • Two numeric-looking fields compared to each other are numeric. The trap needs a quoted constant.
  • NR is cumulative, FNR restarts, and NR == FNR fails open when the first file is empty.
  • The default FS is any run of blanks, spaces and tabs together, with leading and trailing whitespace stripped. -F'\t' gave the same field count and different fields.
  • printf with too few arguments is fatal; too many is silent — the inverse of user-defined functions.
  • A BEGIN-only program never opens its input: rc=0 on a missing file, and rc=2 as soon as an END block or a main rule exists.
  • Print the converted value beside both verdicts. raw=[64.000] v=64.000 numeric>500=no text>"500"=YES leaves nothing to infer.
  • A test that passes under both interpretations proves nothing. $3 < "2000" and $3 < 2000 are both true on 1024.500, which is how the defect survives casual checking.
  • A text-level finding is a question, not a defect. has-digit=y converts=n fired 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.