awk Part II · Module 7 · Input, Output, and Multi-File Processing
print and printf
Module 6 produced numbers. Module 7 is about getting them out in a shape something else can use — a human reading a terminal, a spreadsheet, a dashboard, or the next stage of a pipeline. It starts with the two statements that do the writing. On simple output they are interchangeable, which is why most people pick one by habit and are then surprised twice: print quietly inserts your output separators and printf ignores them completely, and their argument-count rules are exact opposites — too few arguments kills the run, too many are discarded without a word.
Foundation15 min readawkPart IIOutputprintfFormatting
Part II · Module 7 · Chapter 7.1 · print and printf
1. Why This Matters
Everything in Modules 5 and 6 ended with output, and every command used whichever statement happened to fit. That was deliberate — the distinction only matters once the output has to be consumed by something, which is this module's subject.
The short version: print is for getting data out, printf is for getting it out in a specific shape. A terminal check wants print. A table that must stay aligned, a CSV a spreadsheet will load, or a number that must show exactly two decimals wants printf.
The reason the chapter is longer than that sentence is that the two differ in four ways, and two of them are silent.
2. print
print writes its arguments, separated by OFS, followed by ORS:
alu 95 1.5
fifo 72 12.25
uart 88 0.5awk '{ print $1, $2 }' cov.txtalu 95
fifo 72
uart 88The commas are not punctuation inside a function call — each one is a place where OFS goes. That is Chapter 5.2 section 8's point and it becomes important in section 5.
print with no arguments writes $0:
awk 'NR == 1 { print }' cov.txtalu 95 1.5Which is the shortest way to pass a record through, and the form Chapter 4.1 established is implied when a pattern has no action at all.
3. printf
printf takes a format string and fills in the conversions. Nothing is inserted that you did not write:
awk '{ printf "%s %s\n", $1, $2 }' cov.txtalu 95
fifo 72
uart 88Identical output to section 2 — because the format string contains a literal space and a literal \n, which is what print would have supplied from OFS and ORS.
Leave the \n out and there is no newline anywhere:
awk '{ printf "%s ", $1 }' cov.txt; echo "[END]"alu fifo uart [END]The [END] marker lands on the same line as the output, which is the proof that no newline was emitted. Without the marker, the shell prompt would appear there instead.
That is not a defect; it is the point. printf emits exactly what you asked for, which is what makes it usable for building a line in pieces.
4. The Four Differences
print | printf | |
|---|---|---|
| separator between items | inserts OFS | nothing — you write it |
| line terminator | appends ORS | nothing — you write \n |
| too few arguments | n/a | fatal error |
| too many arguments | prints them all | silently discarded |
| format reuse | n/a | does not cycle |
Sections 5 to 7 demonstrate each.
5. print Respects OFS and ORS; printf Ignores Both
This is the difference people hit first. Set a separator and use both statements:
awk 'BEGIN { OFS = "|" } { print $1, $2 }' cov.txtalu|95
fifo|72
uart|88awk 'BEGIN { OFS = "|" } { printf "%s %s\n", $1, $2 }' cov.txtalu 95
fifo 72
uart 88OFS had no effect on the printf. The space between the two values is the literal space in the format string.
ORS behaves the same way:
awk 'BEGIN { ORS = " ;" } { print $1 }' cov.txt; echo "[END]"alu ;fifo ;uart ;[END]awk 'BEGIN { ORS = " ;" } { printf "%s\n", $1 }' cov.txt; echo "[END]"alu
fifo
uart
[END]The print version ends every record with ; — including the last, which is Chapter 5.2 section 8's trailing-separator problem. The printf version ignores ORS entirely and the \n in the format decides the line endings.
6. Argument Count: Fatal One Way, Silent the Other
Too few arguments for the conversions is a fatal error:
awk 'BEGIN { printf "%s|%s|%s\n", "a" }' 2>&1awk: not enough args in printf(%s|%s|%s
)
source line number 1The run stops. (The message prints the format string including its newline, which is why it wraps oddly.)
Too many arguments is silent:
awk 'BEGIN { printf "%s\n", "a", "b", "c" }'ab and c are gone. No warning on stderr, no non-zero status, no indication that two values were dropped.
7. The Format Does Not Cycle
Anyone arriving from shell scripting expects a format to repeat over extra arguments, because the shell's printf does:
printf '%s-' a b c; echoa-b-c-awk's does not:
awk 'BEGIN { printf "%s-", "a", "b", "c"; print "" }'a-One pass through the format, extras discarded per section 6.
So where a shell script loops a format over a list, awk needs an explicit loop:
awk 'BEGIN { split("a b c", v, " "); for (i = 1; i <= 3; i++) printf "%s-", v[i]; print "" }'a-b-c-That is Chapter 5.5's for doing the repetition the format will not.
8. Which to Use
print when you are dumping data, passing a record through, or want the separator to stay configurable. It is shorter and it is the idiomatic choice for a one-liner.
printf when the output has a shape something depends on — an aligned table, a fixed number of decimals, a delimited file, or a line assembled from several statements.
Two practical notes.
A printf with no conversions is a fine way to write a fixed string with no ORS surprises:
awk 'END { printf "done\n" }' cov.txtdoneAnd printf is how you build one line from many records, since it does not terminate anything:
awk '{ printf "%s%s", sep, $1; sep = ", " } END { printf "\n" }' cov.txt | cat -ealu, fifo, uart$The primed-separator idiom from Chapter 5.2 — with printf rather than print, because print would append ORS after every record and there is no value of ORS that omits the last one.
9. Common Mistakes
Setting OFS and using printf. printf ignores it — section 5.
Forgetting \n in a printf. No newline is emitted at all.
Writing printf with too many arguments. Silently discarded, so a column disappears — section 6.
Expecting the format to cycle. It does not; awk needs an explicit loop — section 7.
Expecting print a, b to be a function call. The commas are OFS positions, not argument separators.
Using print for a delimited file. It works until someone changes OFS; printf makes the format local.
Assuming print with no arguments prints nothing. It prints $0.
10. Exercises
Exercise 1 — Predict the output
awk 'BEGIN { OFS = "-" } BEGIN { print "a", "b"; printf "%s %s\n", "a", "b" }'a-b
a bprint used OFS; printf used the literal space in its format.
Exercise 2 — Convert between the two
Rewrite this print as an equivalent printf, keeping the output identical.
alu 95 1.5
fifo 72 12.25
uart 88 0.5awk '{ print $1, $3 }' cov.txtalu 1.5
fifo 12.25
uart 0.5awk '{ printf "%s %s\n", $1, $3 }' cov.txtalu 1.5
fifo 12.25
uart 0.5The printf spells out what print took from OFS and ORS.
Exercise 3 — Debug a disappearing column
A report is supposed to show three columns and shows two.
awk '{ printf "%s %s\n", $1, $2, $3 }' cov.txtalu 95
fifo 72
uart 88Three arguments, two conversions. The third is silently discarded — section 6. Nothing on stderr and the exit status is 0.
awk '{ printf "%s %s %s\n", $1, $2, $3 }' cov.txtalu 95 1.5
fifo 72 12.25
uart 88 0.5Compare what happens when the error goes the other way:
awk '{ printf "%s %s %s %s\n", $1, $2, $3 }' cov.txt 2>&1 | head -3awk: not enough args in printf(%s %s %s %s
)
input record number 1, file cov.txtNote the message names the offending record and file, which a BEGIN-only failure cannot — there it says source line number 1 instead.
Fatal and immediate. One direction stops you; the other lets you ship.
Exercise 4 — ORS against printf
Predict both, then explain why only one is affected.
awk 'BEGIN { ORS = "|" } { print $1 }' cov.txt; echo "[END]"alu|fifo|uart|[END]awk 'BEGIN { ORS = "|" } { printf "%s|", $1 }' cov.txt; echo "[END]"alu|fifo|uart|[END]Identical output from different mechanisms — the first from ORS, the second from a literal | in the format. Changing ORS would alter the first and not the second, which is section 5's configurable-at-a-distance point.
Note both end with a trailing |. The print version cannot avoid it; the printf version can, with the primed separator from section 8.
Exercise 5 — Build one line from many records
Produce a single comma-separated line of block names with no trailing comma and a terminating newline.
awk '{ printf "%s%s", sep, $1; sep = ", " } END { printf "\n" }' cov.txt; echo "[END]"alu, fifo, uart
[END][END] is on its own line this time, because the END rule supplied the newline. sep is unset on the first record so nothing precedes alu, and is ", " thereafter — a separator between items rather than after each. print cannot do this, because ORS is appended unconditionally.
Exercise 6 — Engineering application
Emit a per-block line for a report, with the block name left-aligned in a fixed column and the runtime to two decimals — then the same data as a passthrough dump for a colleague who wants the raw rows.
awk '{ printf "%-8s %6.2f s\n", $1, $3 }' cov.txtalu 1.50 s
fifo 12.25 s
uart 0.50 sawk '{ print }' cov.txtalu 95 1.5
fifo 72 12.25
uart 88 0.5The first is printf's job — the column widths and the two decimals are part of what makes the output readable, and they must not change because someone set OFS. The second is print's — no shape is being imposed, so the shorter statement is the right one.
Note 12.25 s aligns under 1.50 s because %6.2f pads to a fixed width. Chapter 7.2 is about that padding, and about what happens when a value is wider than the field you gave it.
Exercise 7 — Challenge: a format-and-argument count check
A long printf is easy to get wrong. Write a command that reports how many conversions a format string contains, so the count can be checked against the arguments.
awk 'BEGIN { f = "%-8s %6.2f %d %s\n"; n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", f); printf "the format has %d conversion(s)\n", n }'the format has 4 conversion(s)gsub returns a count of matches (Module 8's function, used here only to count). The pattern matches a %, any flags, width or precision, then a conversion letter.
It is not trustworthy yet, and finding out why is the exercise. Try it on a format containing a literal percent sign:
awk 'BEGIN { f = "%d%% of %d bins\n"; n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", f); printf "naive count: %d (should be 2)\n", n }'naive count: 3 (should be 2)Three. The format has two real conversions, %d and %d.
The extra match is % o — from %% of. The flag character class [-+ #0-9.] contains a space, so the pattern read the second % of %%, took the following space as a flag, and accepted the o of of as a conversion letter. A perfectly reasonable-looking pattern matched four characters of English prose.
It fails the other way too, on a format where a conversion follows the escape:
awk 'BEGIN { f = "%d%%s\n"; n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", f); printf "naive count: %d (should be 1)\n", n }'naive count: 2 (should be 1)Two, where %d%%s has exactly one conversion — the %% consumed a % and the trailing s was read as a second.
Removing the escaped percents before counting fixes both:
awk 'BEGIN { f = "%d%%s\n"; g = f; gsub(/%%/, "", g); n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", g); printf "corrected count: %d (should be 1)\n", n }'corrected count: 1 (should be 1)awk 'BEGIN { f = "%d%% of %d bins\n"; g = f; gsub(/%%/, "", g); n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", g); printf "corrected count: %d (should be 2)\n", n }'corrected count: 2 (should be 2)Both right, and note g = f first so the original format is not modified — gsub edits in place.
Two lessons, and the second is the one worth keeping. A character class you did not read carefully will match things you did not imagine — the space in [-+ #0-9.] is legitimate, since % d is a real conversion with a space flag, and it is exactly what let the pattern eat prose. And a checker built from the same pattern class as the thing it checks shares its blind spots: the repair works because stripping %% is a different operation from counting conversions, not more of the same pattern. That is Chapter 5.3's positive-guard argument in a new setting.
11. Summary
printinsertsOFSbetween items andORSat the end. The commas are separator positions, not argument separators.printfinserts nothing. You get exactly the format string, including no newline unless you write\n.printfignoresOFSandORScompletely, which makes its output local and unconfigurable — the reason to use it for a format something depends on.- Too few
printfarguments is a fatal error. - Too many are silently discarded, so an edited format can drop a column with no warning.
- That asymmetry is the inverse of the function-argument rule in Chapter 6.5, where too many warns and too few is silent.
- The format does not cycle over extra arguments the way the shell's
printfdoes. Use an explicit loop. printwith no arguments prints$0.- Use
printfor data and a configurable separator;printfwhen the shape is part of the contract.
Both statements are now distinguishable. What printf does with its conversions — widths, alignment, decimal places, and what happens when a value does not fit the field you gave it — is Chapter 7.2 — Output Formatting, which also covers the rounding rule that turns 2.5 into 2.
Where this fits
Part of the awk curriculum.
